Merge branch 'master' into port-field
This commit is contained in:
@@ -8,6 +8,7 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.pooling.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
@@ -15,9 +16,9 @@ public class Bar extends Element{
|
||||
private static Rect scissor = new Rect();
|
||||
|
||||
private Floatp fraction;
|
||||
private String name = "";
|
||||
private float value, lastValue, blink;
|
||||
private Color blinkColor = new Color();
|
||||
private CharSequence name = "";
|
||||
private float value, lastValue, blink, outlineRadius;
|
||||
private Color blinkColor = new Color(), outlineColor = new Color();
|
||||
|
||||
public Bar(String name, Color color, Floatp fraction){
|
||||
this.fraction = fraction;
|
||||
@@ -27,21 +28,13 @@ public class Bar extends Element{
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
public Bar(Prov<String> name, Prov<Color> color, Floatp fraction){
|
||||
public Bar(Prov<CharSequence> name, Prov<Color> color, Floatp fraction){
|
||||
this.fraction = fraction;
|
||||
try{
|
||||
lastValue = value = Mathf.clamp(fraction.get());
|
||||
}catch(Exception e){ //getting the fraction may involve referring to invalid data
|
||||
lastValue = value = 0f;
|
||||
}
|
||||
lastValue = value = Mathf.clamp(fraction.get());
|
||||
update(() -> {
|
||||
try{
|
||||
this.name = name.get();
|
||||
this.blinkColor.set(color.get());
|
||||
setColor(color.get());
|
||||
}catch(Exception e){ //getting the fraction may involve referring to invalid data
|
||||
this.name = "";
|
||||
}
|
||||
this.name = name.get();
|
||||
this.blinkColor.set(color.get());
|
||||
setColor(color.get());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,6 +54,20 @@ public class Bar extends Element{
|
||||
update(() -> this.name = name.get());
|
||||
}
|
||||
|
||||
public void snap(){
|
||||
lastValue = value = fraction.get();
|
||||
}
|
||||
|
||||
public Bar outline(Color color, float stroke){
|
||||
outlineColor.set(color);
|
||||
outlineRadius = Scl.scl(stroke);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void flash(){
|
||||
blink = 1f;
|
||||
}
|
||||
|
||||
public Bar blink(Color color){
|
||||
blinkColor.set(color);
|
||||
return this;
|
||||
@@ -70,12 +77,8 @@ public class Bar extends Element{
|
||||
public void draw(){
|
||||
if(fraction == null) return;
|
||||
|
||||
float computed;
|
||||
try{
|
||||
computed = Mathf.clamp(fraction.get());
|
||||
}catch(Exception e){ //getting the fraction may involve referring to invalid data
|
||||
computed = 0f;
|
||||
}
|
||||
float computed = Mathf.clamp(fraction.get());
|
||||
|
||||
|
||||
if(lastValue > computed){
|
||||
blink = 1f;
|
||||
@@ -94,9 +97,16 @@ public class Bar extends Element{
|
||||
|
||||
Drawable bar = Tex.bar;
|
||||
|
||||
if(outlineRadius > 0){
|
||||
Draw.color(outlineColor);
|
||||
bar.draw(x - outlineRadius, y - outlineRadius, width + outlineRadius*2, height + outlineRadius*2);
|
||||
}
|
||||
|
||||
Draw.colorl(0.1f);
|
||||
Draw.alpha(parentAlpha);
|
||||
bar.draw(x, y, width, height);
|
||||
Draw.color(color, blinkColor, blink);
|
||||
Draw.alpha(parentAlpha);
|
||||
|
||||
Drawable top = Tex.barTop;
|
||||
float topWidth = width * value;
|
||||
@@ -116,8 +126,10 @@ public class Bar extends Element{
|
||||
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);
|
||||
font.setColor(1f, 1f, 1f, 1f);
|
||||
font.getCache().clear();
|
||||
font.getCache().addText(name, x + width / 2f - lay.width / 2f, y + height / 2f + lay.height / 2f + 1);
|
||||
font.getCache().draw(parentAlpha);
|
||||
|
||||
Pools.free(lay);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ package mindustry.ui;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
public class BorderImage extends Image{
|
||||
public float thickness = 4f;
|
||||
public float thickness = 4f, pad = 0f;
|
||||
public Color borderColor = Pal.gray;
|
||||
|
||||
public BorderImage(){
|
||||
@@ -28,6 +29,10 @@ public class BorderImage extends Image{
|
||||
thickness = thick;
|
||||
}
|
||||
|
||||
public BorderImage(Drawable region){
|
||||
super(region);
|
||||
}
|
||||
|
||||
public BorderImage border(Color color){
|
||||
this.borderColor = color;
|
||||
return this;
|
||||
@@ -40,7 +45,7 @@ public class BorderImage extends Image{
|
||||
Draw.color(borderColor);
|
||||
Draw.alpha(parentAlpha);
|
||||
Lines.stroke(Scl.scl(thickness));
|
||||
Lines.rect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY);
|
||||
Lines.rect(x + imageX - pad, y + imageY - pad, imageWidth * scaleX + pad*2, imageHeight * scaleY + pad*2);
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,15 @@ public class CoreItemsDisplay extends Table{
|
||||
|
||||
public void resetUsed(){
|
||||
usedItems.clear();
|
||||
background(null);
|
||||
}
|
||||
|
||||
void rebuild(){
|
||||
clear();
|
||||
background(Styles.black6);
|
||||
margin(4);
|
||||
if(usedItems.size > 0){
|
||||
background(Styles.black6);
|
||||
margin(4);
|
||||
}
|
||||
|
||||
update(() -> {
|
||||
core = Vars.player.team().core();
|
||||
@@ -38,9 +41,9 @@ public class CoreItemsDisplay extends Table{
|
||||
|
||||
for(Item item : content.items()){
|
||||
if(usedItems.contains(item)){
|
||||
image(item.icon(Cicon.small)).padRight(3);
|
||||
image(item.uiIcon).size(iconSmall).padRight(3).tooltip(t -> t.background(Styles.black6).margin(4f).add(item.localizedName).style(Styles.outlineLabel));
|
||||
//TODO leaks garbage
|
||||
label(() -> core == null ? "0" : UI.formatAmount(core.items.get(item))).padRight(3).left();
|
||||
label(() -> core == null ? "0" : UI.formatAmount(core.items.get(item))).padRight(3).minWidth(52f).left().tooltip(t -> t.background(Styles.black6).margin(4f).label(() -> core == null ? "0" : core.items.get(item) + "").style(Styles.outlineLabel));
|
||||
|
||||
if(++i % 4 == 0){
|
||||
row();
|
||||
|
||||
@@ -4,5 +4,9 @@ import arc.scene.ui.layout.*;
|
||||
|
||||
/** An interface for things that can be displayed when hovered over. */
|
||||
public interface Displayable{
|
||||
default boolean displayable(){
|
||||
return true;
|
||||
}
|
||||
|
||||
void display(Table table);
|
||||
}
|
||||
|
||||
@@ -3,14 +3,11 @@ package mindustry.ui;
|
||||
import arc.*;
|
||||
import arc.Graphics.Cursor.*;
|
||||
import arc.assets.*;
|
||||
import arc.assets.loaders.*;
|
||||
import arc.assets.loaders.resolvers.*;
|
||||
import arc.files.*;
|
||||
import arc.freetype.*;
|
||||
import arc.freetype.FreeTypeFontGenerator.*;
|
||||
import arc.freetype.FreetypeFontLoader.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Pixmap.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.graphics.g2d.Font.*;
|
||||
@@ -23,30 +20,20 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class Fonts{
|
||||
private static final String mainFont = "fonts/font.woff";
|
||||
private static final ObjectSet<String> unscaled = ObjectSet.with("iconLarge");
|
||||
private static ObjectIntMap<String> unicodeIcons = new ObjectIntMap<>();
|
||||
private static IntMap<String> unicodeToName = new IntMap<>();
|
||||
private static ObjectMap<String, String> stringIcons = new ObjectMap<>();
|
||||
private static ObjectMap<String, TextureRegion> largeIcons = new ObjectMap<>();
|
||||
private static TextureRegion[] iconTable;
|
||||
private static int lastCid;
|
||||
|
||||
public static Font def;
|
||||
public static Font outline;
|
||||
public static Font chat;
|
||||
public static Font icon;
|
||||
public static Font iconLarge;
|
||||
public static Font tech;
|
||||
|
||||
public static TextureRegion logicIcon(int id){
|
||||
return iconTable[id];
|
||||
}
|
||||
public static Font def, outline, icon, iconLarge, tech, logic;
|
||||
|
||||
public static int getUnicode(String content){
|
||||
return unicodeIcons.get(content, 0);
|
||||
@@ -56,6 +43,10 @@ public class Fonts{
|
||||
return stringIcons.get(content, "");
|
||||
}
|
||||
|
||||
public static boolean hasUnicodeStr(String content){
|
||||
return stringIcons.containsKey(content);
|
||||
}
|
||||
|
||||
/** Called from a static context to make the cursor appear immediately upon startup.*/
|
||||
public static void loadSystemCursors(){
|
||||
SystemCursor.arrow.set(Core.graphics.newCursor("cursor", cursorScale()));
|
||||
@@ -73,20 +64,33 @@ public class Fonts{
|
||||
largeIcons.clear();
|
||||
FreeTypeFontParameter param = fontParameter();
|
||||
|
||||
Core.assets.load("default", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = f -> Fonts.def = (Font)f;
|
||||
Core.assets.load("chat", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = f -> Fonts.chat = (Font)f;
|
||||
Core.assets.load("default", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = f -> Fonts.def = f;
|
||||
|
||||
Core.assets.load("icon", Font.class, new FreeTypeFontLoaderParameter("fonts/icon.ttf", new FreeTypeFontParameter(){{
|
||||
size = 30;
|
||||
incremental = true;
|
||||
characters = "\0";
|
||||
}})).loaded = f -> Fonts.icon = (Font)f;
|
||||
}})).loaded = f -> Fonts.icon = f;
|
||||
|
||||
Core.assets.load("iconLarge", Font.class, new FreeTypeFontLoaderParameter("fonts/icon.ttf", new FreeTypeFontParameter(){{
|
||||
size = 48;
|
||||
incremental = false;
|
||||
characters = "\0" + Iconc.all;
|
||||
borderWidth = 5f;
|
||||
borderColor = Color.darkGray;
|
||||
}})).loaded = f -> Fonts.iconLarge = (Font)f;
|
||||
}})).loaded = f -> Fonts.iconLarge = f;
|
||||
|
||||
Core.assets.load("logic", Font.class, new FreeTypeFontLoaderParameter("fonts/logic.ttf", new FreeTypeFontParameter(){{
|
||||
size = 16;
|
||||
//generated all at once, it's fast enough anyway
|
||||
incremental = false;
|
||||
//ASCII only
|
||||
characters = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890\"!`?'.,;:()[]{}<>|/@\\^$€-%+=#_&~*";
|
||||
}})).loaded = f -> Fonts.logic = f;
|
||||
}
|
||||
|
||||
public static @Nullable String unicodeToName(int unicode){
|
||||
return unicodeToName.get(unicode, () -> Iconc.codeToName.get(unicode));
|
||||
}
|
||||
|
||||
public static TextureRegion getLargeIcon(String name){
|
||||
@@ -94,7 +98,7 @@ public class Fonts{
|
||||
var region = new TextureRegion();
|
||||
int code = Iconc.codes.get(name, '\uF8D4');
|
||||
var glyph = iconLarge.getData().getGlyph((char)code);
|
||||
if(glyph == null) return Core.atlas.find("error");
|
||||
if(glyph == null) return Core.atlas.find(name);
|
||||
region.set(iconLarge.getRegion().texture);
|
||||
region.set(glyph.u, glyph.v2, glyph.u2, glyph.v);
|
||||
return region;
|
||||
@@ -102,13 +106,13 @@ public class Fonts{
|
||||
}
|
||||
|
||||
public static void loadContentIcons(){
|
||||
Seq<Font> fonts = Seq.with(Fonts.chat, Fonts.def, Fonts.outline);
|
||||
Seq<Font> fonts = Seq.with(Fonts.def, Fonts.outline);
|
||||
Texture uitex = Core.atlas.find("logo").texture;
|
||||
int size = (int)(Fonts.def.getData().lineHeight/Fonts.def.getData().scaleY);
|
||||
|
||||
try(Scanner scan = new Scanner(Core.files.internal("icons/icons.properties").read(512))){
|
||||
while(scan.hasNextLine()){
|
||||
String line = scan.nextLine();
|
||||
try(var reader = Core.files.internal("icons/icons.properties").reader(Vars.bufferSize)){
|
||||
String line;
|
||||
while((line = reader.readLine()) != null){
|
||||
String[] split = line.split("=");
|
||||
String[] nametex = split[1].split("\\|");
|
||||
String character = split[0], texture = nametex[1];
|
||||
@@ -117,18 +121,20 @@ public class Fonts{
|
||||
|
||||
if(region.texture != uitex){
|
||||
continue;
|
||||
//throw new IllegalArgumentException("Font icon '" + texture + "' is not in the UI texture.");
|
||||
}
|
||||
|
||||
unicodeIcons.put(nametex[0], ch);
|
||||
stringIcons.put(nametex[0], ((char)ch) + "");
|
||||
unicodeToName.put(ch, texture);
|
||||
|
||||
Vec2 out = Scaling.fit.apply(region.width, region.height, size, size);
|
||||
|
||||
Glyph glyph = new Glyph();
|
||||
glyph.id = ch;
|
||||
glyph.srcX = 0;
|
||||
glyph.srcY = 0;
|
||||
glyph.width = size;
|
||||
glyph.height = (int)((float)region.height / region.width * size);
|
||||
glyph.width = (int)out.x;
|
||||
glyph.height = (int)out.y;
|
||||
glyph.u = region.u;
|
||||
glyph.v = region.v2;
|
||||
glyph.u2 = region.u2;
|
||||
@@ -141,30 +147,47 @@ public class Fonts{
|
||||
glyph.page = 0;
|
||||
fonts.each(f -> f.getData().setGlyph(ch, glyph));
|
||||
}
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
iconTable = new TextureRegion[512];
|
||||
iconTable[0] = Core.atlas.find("error");
|
||||
lastCid = 1;
|
||||
stringIcons.put("alphachan", stringIcons.get("alphaaaa"));
|
||||
|
||||
Vars.content.each(c -> {
|
||||
if(c instanceof UnlockableContent u){
|
||||
TextureRegion region = Core.atlas.find(u.name + "-icon-logic");
|
||||
if(region.found()){
|
||||
iconTable[u.iconId = lastCid++] = region;
|
||||
}
|
||||
for(Team team : Team.baseTeams){
|
||||
team.emoji = stringIcons.get(team.name, "");
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadContentIconsHeadless(){
|
||||
try(var reader = Core.files.internal("icons/icons.properties").reader(Vars.bufferSize)){
|
||||
String line;
|
||||
while((line = reader.readLine()) != null){
|
||||
String[] split = line.split("=");
|
||||
String[] nametex = split[1].split("\\|");
|
||||
String character = split[0];
|
||||
int ch = Integer.parseInt(character);
|
||||
|
||||
unicodeIcons.put(nametex[0], ch);
|
||||
stringIcons.put(nametex[0], ((char)ch) + "");
|
||||
}
|
||||
});
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
stringIcons.put("alphachan", stringIcons.get("alphaaaa"));
|
||||
|
||||
for(Team team : Team.baseTeams){
|
||||
team.emoji = stringIcons.get(team.name, "");
|
||||
}
|
||||
}
|
||||
|
||||
/** Called from a static context for use in the loading screen.*/
|
||||
public static void loadDefaultFont(){
|
||||
int max = Gl.getInt(Gl.maxTextureSize);
|
||||
|
||||
UI.packer = new PixmapPacker(max >= 4096 ? 4096 : 2048, 2048, Format.rgba8888, 2, true);
|
||||
FileHandleResolver resolver = new InternalFileHandleResolver();
|
||||
Core.assets.setLoader(FreeTypeFontGenerator.class, new FreeTypeFontGeneratorLoader(resolver));
|
||||
Core.assets.setLoader(Font.class, null, new FreetypeFontLoader(resolver){
|
||||
UI.packer = new PixmapPacker(max >= 4096 ? 4096 : 2048, 2048, 2, true);
|
||||
Core.assets.setLoader(FreeTypeFontGenerator.class, new FreeTypeFontGeneratorLoader(Core.files::internal));
|
||||
Core.assets.setLoader(Font.class, null, new FreetypeFontLoader(Core.files::internal){
|
||||
ObjectSet<FreeTypeFontParameter> scaled = new ObjectSet<>();
|
||||
|
||||
@Override
|
||||
@@ -192,19 +215,19 @@ public class Fonts{
|
||||
size = 18;
|
||||
}};
|
||||
|
||||
Core.assets.load("outline", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = t -> Fonts.outline = (Font)t;
|
||||
Core.assets.load("outline", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = t -> Fonts.outline = t;
|
||||
|
||||
Core.assets.load("tech", Font.class, new FreeTypeFontLoaderParameter("fonts/tech.ttf", new FreeTypeFontParameter(){{
|
||||
size = 18;
|
||||
}})).loaded = f -> {
|
||||
Fonts.tech = (Font)f;
|
||||
((Font)f).getData().down *= 1.5f;
|
||||
Fonts.tech = f;
|
||||
Fonts.tech.getData().down *= 1.5f;
|
||||
};
|
||||
}
|
||||
|
||||
/** Merges the UI and font atlas together for better performance. */
|
||||
public static void mergeFontAtlas(TextureAtlas atlas){
|
||||
//grab all textures from the ui page, remove all the regions assigned to it, then copy them over to Fonts.packer and replace the texture in this atlas.
|
||||
//grab all textures from the ui page, remove all the regions assigned to it, then copy them over to UI.packer and replace the texture in this atlas.
|
||||
|
||||
//grab old UI texture and regions...
|
||||
Texture texture = atlas.find("logo").texture;
|
||||
@@ -215,13 +238,16 @@ public class Fonts{
|
||||
for(AtlasRegion region : regions){
|
||||
//get new pack rect
|
||||
page.setDirty(false);
|
||||
Rect rect = UI.packer.pack(region.name + (region.splits != null ? ".9" : ""), atlas.getPixmap(region));
|
||||
Rect rect = UI.packer.pack(region.name, atlas.getPixmap(region), region.splits, region.pads);
|
||||
|
||||
//set new texture
|
||||
region.texture = UI.packer.getPages().first().getTexture();
|
||||
//set its new position
|
||||
region.set((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
|
||||
//add old texture
|
||||
atlas.getTextures().add(region.texture);
|
||||
//clear it
|
||||
region.pixmapRegion = null;
|
||||
}
|
||||
|
||||
//remove old texture, it will no longer be used
|
||||
@@ -234,8 +260,12 @@ public class Fonts{
|
||||
}
|
||||
|
||||
public static TextureRegionDrawable getGlyph(Font font, char glyph){
|
||||
Glyph g = font.getData().getGlyph(glyph);
|
||||
if(g == null) throw new IllegalArgumentException("No glyph: " + glyph + " (" + (int)glyph + ")");
|
||||
Glyph found = font.getData().getGlyph(glyph);
|
||||
if(found == null){
|
||||
Log.warn("No icon found for glyph: @ (@)", glyph, (int)glyph);
|
||||
found = font.getData().getGlyph('F');
|
||||
}
|
||||
Glyph g = found;
|
||||
|
||||
float size = Math.max(g.width, g.height);
|
||||
TextureRegionDrawable draw = new TextureRegionDrawable(new TextureRegion(font.getRegion().texture, g.u, g.v2, g.u2, g.v)){
|
||||
@@ -258,7 +288,7 @@ public class Fonts{
|
||||
cy = (int)cy;
|
||||
originX = g.width/2f;
|
||||
originY = g.height/2f;
|
||||
Draw.rect(region, cx + g.width/2f, cy + g.height/2f, g.width, g.height, originX, originY, rotation);
|
||||
Draw.rect(region, cx + g.width/2f, cy + g.height/2f, g.width * scaleX, g.height * scaleY, originX, originY, rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package mindustry.ui;
|
||||
|
||||
public enum IconSize{
|
||||
def(48),
|
||||
small(32),
|
||||
smaller(30),
|
||||
tiny(16);
|
||||
|
||||
public final int size;
|
||||
|
||||
IconSize(int size){
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package mindustry.ui;
|
||||
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
/** 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)));
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package mindustry.ui;
|
||||
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class ItemImage extends Stack{
|
||||
|
||||
public ItemImage(TextureRegion region, int amount){
|
||||
|
||||
add(new Table(o -> {
|
||||
o.left();
|
||||
o.add(new Image(region)).size(32f);
|
||||
}));
|
||||
|
||||
add(new Table(t -> {
|
||||
t.left().bottom();
|
||||
t.add(amount > 1000 ? UI.formatAmount(amount) : amount + "");
|
||||
t.pack();
|
||||
}));
|
||||
}
|
||||
|
||||
public ItemImage(TextureRegion region){
|
||||
Table t = new Table().left().bottom();
|
||||
|
||||
add(new Image(region));
|
||||
add(t);
|
||||
}
|
||||
|
||||
public ItemImage(ItemStack stack){
|
||||
|
||||
add(new Table(o -> {
|
||||
o.left();
|
||||
o.add(new Image(stack.item.icon(Cicon.medium))).size(32f);
|
||||
}));
|
||||
|
||||
if(stack.amount != 0){
|
||||
add(new Table(t -> {
|
||||
t.left().bottom();
|
||||
t.add(stack.amount > 1000 ? UI.formatAmount(stack.amount) : stack.amount + "").style(Styles.outlineLabel);
|
||||
t.pack();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import static mindustry.Vars.*;
|
||||
|
||||
/** Displays a list of items, e.g. launched items.*/
|
||||
public class ItemsDisplay extends Table{
|
||||
boolean collapsed;
|
||||
|
||||
public ItemsDisplay(){
|
||||
rebuild(new ItemSeq());
|
||||
@@ -29,6 +30,8 @@ public class ItemsDisplay extends Table{
|
||||
top().left();
|
||||
margin(0);
|
||||
|
||||
if(items == null) return;
|
||||
|
||||
table(Tex.button, c -> {
|
||||
c.margin(10).marginLeft(12).marginTop(15f);
|
||||
c.marginRight(12f);
|
||||
@@ -41,7 +44,7 @@ public class ItemsDisplay extends Table{
|
||||
if(!items.has(item)) continue;
|
||||
|
||||
Label label = t.add(UI.formatAmount(items.get(item))).left().get();
|
||||
t.image(item.icon(Cicon.small)).size(8 * 3).padLeft(4).padRight(4);
|
||||
t.image(item.uiIcon).size(8 * 3).padLeft(4).padRight(4);
|
||||
t.add(item.localizedName).color(Color.lightGray).left();
|
||||
t.row();
|
||||
|
||||
@@ -50,10 +53,13 @@ public class ItemsDisplay extends Table{
|
||||
label.actions(Actions.color(Color.white, 0.75f, Interp.fade));
|
||||
}
|
||||
}
|
||||
}).get().setScrollingDisabled(true, false), false).setDuration(0.3f);
|
||||
}).scrollX(false), false).setDuration(0.3f);
|
||||
|
||||
c.button("@globalitems", Icon.downOpen, Styles.clearTogglet, col::toggle).update(t -> {
|
||||
col.setCollapsed(collapsed, false);
|
||||
|
||||
c.button("@globalitems", Icon.downOpen, Styles.flatTogglet, col::toggle).update(t -> {
|
||||
t.setChecked(col.isCollapsed());
|
||||
collapsed = col.isCollapsed();
|
||||
((Image)t.getChildren().get(1)).setDrawable(col.isCollapsed() ? Icon.upOpen : Icon.downOpen);
|
||||
}).padBottom(4).left().fillX().margin(12f).minWidth(200f);
|
||||
c.row();
|
||||
|
||||
@@ -26,7 +26,7 @@ public class Links{
|
||||
new LinkEntry("f-droid", "https://f-droid.org/packages/io.anuke.mindustry/", Icon.android, Color.valueOf("026aa7")),
|
||||
new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Icon.github, Color.valueOf("24292e")),
|
||||
new LinkEntry("dev-builds", "https://github.com/Anuken/MindustryBuilds", Icon.githubSquare, Color.valueOf("fafbfc")),
|
||||
new LinkEntry("bug", report(), Icon.wrench, Color.valueOf("cbd97f"))
|
||||
new LinkEntry("bug", "https://github.com/Anuken/Mindustry/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml", Icon.wrench, Color.valueOf("cbd97f"))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,23 +52,4 @@ public class Links{
|
||||
this.title = Core.bundle.get("link." + name + ".title", Strings.capitalize(name.replace("-", " ")));
|
||||
}
|
||||
}
|
||||
|
||||
private static String report(){
|
||||
return "https://github.com/Anuken/Mindustry/issues/new?assignees=&labels=bug&body=" +
|
||||
Strings.encode(Strings.format(
|
||||
"**Platform**: `@`\n" +
|
||||
"\n**Build**: `@`\n" +
|
||||
"\n**Issue**: *Explain your issue in detail.*\n" +
|
||||
"\n**Steps to reproduce**: *How you happened across the issue, and what exactly you did to make the bug happen.*\n" +
|
||||
"\n**Link(s) to mod(s) used**: `@`\n" +
|
||||
"\n**Save file**: *The (zipped) save file you were playing on when the bug happened. THIS IS REQUIRED FOR ANY ISSUE HAPPENING IN-GAME, REGARDLESS OF WHETHER YOU THINK IT HAPPENS EVERYWHERE. DO NOT DELETE OR OMIT THIS LINE UNLESS YOU ARE SURE THAT THE ISSUE DOES NOT HAPPEN IN-GAME.*\n" +
|
||||
"\n**Crash report**: *The contents of relevant crash report files. REQUIRED if you are reporting a crash.*\n" +
|
||||
"\n---\n" +
|
||||
"\n*Place an X (no spaces) between the brackets to confirm that you have read the line below.*" +
|
||||
"\n- [ ] **I have updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.**" +
|
||||
"\n- [ ] **I have searched the closed and open issues to make sure that this problem has not already been reported.**",
|
||||
OS.isAndroid ? "Android " + Core.app.getVersion() : (System.getProperty("os.name") + (OS.is64Bit ? " x64" : " x32")),
|
||||
Version.combined(),
|
||||
Vars.mods.list().any() ? Vars.mods.list().select(LoadedMod::enabled).map(l -> l.meta.author + "/" + l.name + ":" + l.meta.version) : "none"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package mindustry.ui;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
/** An ItemDisplay, but for liquids. */
|
||||
public class LiquidDisplay extends Table{
|
||||
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)).style(Styles.outlineLabel);
|
||||
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).style(Styles.outlineLabel);
|
||||
}
|
||||
|
||||
add(liquid.localizedName);
|
||||
}
|
||||
}
|
||||
187
core/src/mindustry/ui/Menus.java
Normal file
187
core/src/mindustry/ui/Menus.java
Normal file
@@ -0,0 +1,187 @@
|
||||
package mindustry.ui;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Class for handling menus and notifications across the network. Unstable API! */
|
||||
public class Menus{
|
||||
private static final Seq<MenuListener> menuListeners = new Seq<>();
|
||||
private static final Seq<TextInputListener> textInputListeners = new Seq<>();
|
||||
|
||||
/** Register a *global* menu listener. If no option is chosen, the option is returned as -1. */
|
||||
public static int registerMenu(MenuListener listener){
|
||||
menuListeners.add(listener);
|
||||
return menuListeners.size - 1;
|
||||
}
|
||||
|
||||
/** Register a *global* text input listener. If no text is provided, the text is returned as null. */
|
||||
public static int registerTextInput(TextInputListener listener){
|
||||
textInputListeners.add(listener);
|
||||
return textInputListeners.size - 1;
|
||||
}
|
||||
|
||||
//do not invoke any of the methods below directly, use Call
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void menu(int menuId, String title, String message, String[][] options){
|
||||
if(title == null) title = "";
|
||||
if(message == null) message = "";
|
||||
if(options == null) options = new String[0][0];
|
||||
|
||||
ui.showMenu(title, message, options, (option) -> Call.menuChoose(player, menuId, option));
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void followUpMenu(int menuId, String title, String message, String[][] options){
|
||||
if(title == null) title = "";
|
||||
if(message == null) message = "";
|
||||
if(options == null) options = new String[0][0];
|
||||
|
||||
ui.showFollowUpMenu(menuId, title, message, options, (option) -> Call.menuChoose(player, menuId, option));
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void hideFollowUpMenu(int menuId) {
|
||||
ui.hideFollowUpMenu(menuId);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.both)
|
||||
public static void menuChoose(@Nullable Player player, int menuId, int option){
|
||||
if(player != null){
|
||||
Events.fire(new MenuOptionChooseEvent(player, menuId, option));
|
||||
if(menuId >= 0 && menuId < menuListeners.size){
|
||||
menuListeners.get(menuId).get(player, option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void textInput(int textInputId, String title, String message, int textLength, String def, boolean numeric){
|
||||
textInput(textInputId, title, message, textLength, def, numeric, false);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void textInput(int textInputId, String title, String message, int textLength, String def, boolean numeric, boolean allowEmpty){
|
||||
if(title == null) title = "";
|
||||
if(message == null) message = "";
|
||||
if(def == null) def = "";
|
||||
|
||||
ui.showTextInput(title, message, textLength, def, numeric, allowEmpty, (text) -> {
|
||||
Call.textInputResult(player, textInputId, text);
|
||||
}, () -> {
|
||||
Call.textInputResult(player, textInputId, null);
|
||||
});
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.both)
|
||||
public static void textInputResult(@Nullable Player player, int textInputId, @Nullable String text){
|
||||
if(player != null){
|
||||
Events.fire(new TextInputEvent(player, textInputId, text));
|
||||
if(textInputId >= 0 && textInputId < textInputListeners.size){
|
||||
textInputListeners.get(textInputId).get(player, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both, unreliable = true)
|
||||
public static void setHudText(String message){
|
||||
if(message == null) return;
|
||||
|
||||
ui.hudfrag.setHudText(message);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void hideHudText(){
|
||||
ui.hudfrag.toggleHudText(false);
|
||||
}
|
||||
|
||||
/** TCP version */
|
||||
@Remote(variants = Variant.both)
|
||||
public static void setHudTextReliable(String message){
|
||||
setHudText(message);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void announce(String message){
|
||||
if(message == null) return;
|
||||
|
||||
ui.announce(message);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void infoMessage(String message){
|
||||
if(message == null) return;
|
||||
|
||||
ui.showText("", message);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both, unreliable = true)
|
||||
public static void infoPopup(String message, float duration, int align, int top, int left, int bottom, int right){
|
||||
if(message == null) return;
|
||||
|
||||
ui.showInfoPopup(message, duration, align, top, left, bottom, right);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both, unreliable = true)
|
||||
public static void label(String message, float duration, float worldx, float worldy){
|
||||
if(message == null) return;
|
||||
|
||||
ui.showLabel(message, duration, worldx, worldy);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void infoPopupReliable(String message, float duration, int align, int top, int left, int bottom, int right){
|
||||
if(message == null) return;
|
||||
|
||||
ui.showInfoPopup(message, duration, align, top, left, bottom, right);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void labelReliable(String message, float duration, float worldx, float worldy){
|
||||
label(message, duration, worldx, worldy);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void infoToast(String message, float duration){
|
||||
if(message == null) return;
|
||||
|
||||
ui.showInfoToast(message, duration);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void warningToast(int unicode, String text){
|
||||
if(text == null || Fonts.icon.getData().getGlyph((char)unicode) == null) return;
|
||||
|
||||
ui.hudfrag.showToast(Fonts.getGlyph(Fonts.icon, (char)unicode), text);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void openURI(String uri){
|
||||
if(uri == null) return;
|
||||
|
||||
ui.showConfirm(Core.bundle.format("linkopen", uri), () -> Core.app.openURI(uri));
|
||||
}
|
||||
|
||||
//internal use only
|
||||
@Remote
|
||||
public static void removeWorldLabel(int id){
|
||||
var label = Groups.label.getByID(id);
|
||||
if(label != null){
|
||||
label.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public interface MenuListener{
|
||||
void get(Player player, int option);
|
||||
}
|
||||
|
||||
public interface TextInputListener{
|
||||
void get(Player player, @Nullable String text);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@ package mindustry.ui;
|
||||
import arc.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -20,6 +22,22 @@ public class Minimap extends Table{
|
||||
add(new Element(){
|
||||
{
|
||||
setSize(Scl.scl(140f));
|
||||
|
||||
addListener(new ClickListener(KeyCode.mouseRight){
|
||||
@Override
|
||||
public void clicked(InputEvent event, float cx, float cy){
|
||||
var region = renderer.minimap.getRegion();
|
||||
if(region == null) return;
|
||||
|
||||
float
|
||||
sx = (cx - x) / width,
|
||||
sy = (cy - y) / height,
|
||||
scaledX = Mathf.lerp(region.u, region.u2, sx) * world.width() * tilesize,
|
||||
scaledY = Mathf.lerp(1f - region.v2, 1f - region.v, sy) * world.height() * tilesize;
|
||||
|
||||
control.input.panCamera(Tmp.v1.set(scaledX, scaledY));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,6 +55,7 @@ public class Minimap extends Table{
|
||||
Draw.rect(renderer.minimap.getRegion(), x + width / 2f, y + height / 2f, width, height);
|
||||
|
||||
if(renderer.minimap.getTexture() != null){
|
||||
Draw.alpha(parentAlpha);
|
||||
renderer.minimap.drawEntities(x, y, width, height, 0.75f, false);
|
||||
}
|
||||
|
||||
@@ -92,7 +111,7 @@ public class Minimap extends Table{
|
||||
|
||||
update(() -> {
|
||||
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
if(e != null && e.isDescendantOf(this)){
|
||||
requestScroll();
|
||||
}else if(hasScroll()){
|
||||
|
||||
@@ -5,6 +5,7 @@ import arc.graphics.g2d.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
public class ReqImage extends Stack{
|
||||
@@ -30,7 +31,7 @@ public class ReqImage extends Stack{
|
||||
}
|
||||
|
||||
public ReqImage(TextureRegion region, Boolp valid){
|
||||
this(new Image(region), valid);
|
||||
this(new Image(region).setScaling(Scaling.fit), valid);
|
||||
}
|
||||
|
||||
public boolean valid(){
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package mindustry.ui;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class SearchBar{
|
||||
|
||||
public static <T> Table add(Table parent, Seq<T> list, Func<String, String> queryf,
|
||||
Func<T, String> namef, Cons2<Table, T> itemc, boolean show){
|
||||
Table[] pane = {null};
|
||||
|
||||
Cons<String> rebuild = str -> {
|
||||
String query = queryf.get(str);
|
||||
|
||||
pane[0].clear();
|
||||
boolean any = false;
|
||||
for(T item : list){
|
||||
if(query.isEmpty() || matches(query, namef.get(item))){
|
||||
any = true;
|
||||
itemc.get(pane[0], item);
|
||||
}
|
||||
}
|
||||
|
||||
if(!any){
|
||||
pane[0].add("@none.found").color(Color.lightGray).pad(4);
|
||||
}
|
||||
};
|
||||
|
||||
if(show){
|
||||
parent.table(search -> {
|
||||
search.image(Icon.zoom).padRight(8f);
|
||||
search.field("", rebuild).growX();
|
||||
}).fillX().padBottom(4);
|
||||
}
|
||||
|
||||
parent.row();
|
||||
parent.pane(table -> {
|
||||
pane[0] = table;
|
||||
rebuild.get("");
|
||||
});
|
||||
return pane[0];
|
||||
}
|
||||
|
||||
public static <T> Table add(Table parent, Seq<T> list, Func<String, String> queryf, Func<T, String> namef, Cons2<Table, T> itemc){
|
||||
return add(parent, list, queryf, namef, itemc, true);
|
||||
}
|
||||
|
||||
public static <T> Table add(Table parent, Seq<T> list, Func<T, String> namef, Cons2<Table, T> itemc, boolean show){
|
||||
return add(parent, list, String::toLowerCase, namef, itemc, show);
|
||||
}
|
||||
|
||||
/** Match a list item with the search query, case insensitive */
|
||||
public static boolean matches(String query, String name){
|
||||
if(name == null || name.isEmpty()){
|
||||
return false;
|
||||
}
|
||||
return name.toLowerCase().contains(query);
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,12 @@ 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 arc.scene.ui.TreeElement.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
@@ -24,19 +24,83 @@ import static mindustry.gen.Tex.*;
|
||||
@StyleDefaults
|
||||
public class Styles{
|
||||
//TODO all these names are inconsistent and not descriptive
|
||||
public static Drawable black, black9, black8, black6, black3, black5, none, flatDown, flatOver;
|
||||
public static ButtonStyle defaultb, waveb, modsb;
|
||||
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, nonet, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
|
||||
public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, logici, geni, colori, accenti, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali;
|
||||
public static ScrollPaneStyle defaultPane, horizontalPane, smallPane;
|
||||
public static KeybindDialogStyle defaultKeybindDialog;
|
||||
public static SliderStyle defaultSlider, vSlider;
|
||||
public static Drawable black, black9, black8, black6, black3, black5, grayPanel, none, flatDown, flatOver, accentDrawable;
|
||||
|
||||
public static ButtonStyle defaultb, underlineb;
|
||||
|
||||
/** Default text button style - gray corners at 45 degrees. */
|
||||
public static TextButtonStyle defaultt,
|
||||
/** Flat, square, opaque. */
|
||||
flatt,
|
||||
/** Flat, square, opaque, gray. */
|
||||
grayt,
|
||||
/** Flat, square, toggleable. */
|
||||
flatTogglet,
|
||||
/** Flat, square, gray border.*/
|
||||
flatBordert,
|
||||
/** No background whatsoever, only text. */
|
||||
nonet,
|
||||
/** Similar to flatToggle, but slightly tweaked for logic. */
|
||||
logicTogglet,
|
||||
/** Similar to flatToggle, but with a transparent base background. */
|
||||
flatToggleMenut,
|
||||
/** Toggle variant of default style. */
|
||||
togglet,
|
||||
/** Partially transparent square button. */
|
||||
cleart,
|
||||
/** Clear, square, orange border, toggleable. */
|
||||
clearTogglet,
|
||||
/** Similar to flatToggle, but without a darker border. */
|
||||
fullTogglet,
|
||||
/** Toggle-able version of flatBorder. */
|
||||
squareTogglet,
|
||||
/** Special square button for logic dialogs. */
|
||||
logict;
|
||||
|
||||
/** Default image button style - gray corners at 45 degrees. */
|
||||
public static ImageButtonStyle defaulti,
|
||||
/** Used for research nodes in the tech tree. */
|
||||
nodei,
|
||||
/** No background, tints the image itself when hovered. */
|
||||
emptyi,
|
||||
/** Toggleable variant of emptyi */
|
||||
emptyTogglei,
|
||||
/** Displays border around image when selected; used in placement fragment. */
|
||||
selecti,
|
||||
/** Pure black version of emptyi, used for logic toolbar. */
|
||||
logici,
|
||||
/** Used for toolbar in map generation filters. */
|
||||
geni,
|
||||
/** Gray, toggleable, no background. */
|
||||
grayi,
|
||||
/** Gray square background, standard behavior. Equivalent to grayt. */
|
||||
graySquarei,
|
||||
/** Flat, square, black background. */
|
||||
flati,
|
||||
/** Square border. */
|
||||
squarei,
|
||||
/** Square border, toggleable. */
|
||||
squareTogglei,
|
||||
/** No background unless focused, no border. */
|
||||
clearNonei,
|
||||
/** Partially transparent black background. */
|
||||
cleari,
|
||||
/** Toggleable variant of cleari. */
|
||||
clearTogglei,
|
||||
/** clearNone, but toggleable. */
|
||||
clearNoneTogglei;
|
||||
|
||||
public static ScrollPaneStyle defaultPane, horizontalPane, smallPane, noBarPane;
|
||||
public static SliderStyle defaultSlider;
|
||||
public static LabelStyle defaultLabel, outlineLabel, techLabel;
|
||||
public static TextFieldStyle defaultField, nodeField, areaField, nodeArea;
|
||||
public static CheckBoxStyle defaultCheck;
|
||||
public static DialogStyle defaultDialog, fullDialog;
|
||||
public static TreeStyle defaultTree;
|
||||
|
||||
public static void load(){
|
||||
var whiteui = (TextureRegionDrawable)Tex.whiteui;
|
||||
|
||||
black = whiteui.tint(0f, 0f, 0f, 1f);
|
||||
black9 = whiteui.tint(0f, 0f, 0f, 0.9f);
|
||||
black8 = whiteui.tint(0f, 0f, 0f, 0.8f);
|
||||
@@ -44,8 +108,10 @@ public class Styles{
|
||||
black5 = whiteui.tint(0f, 0f, 0f, 0.5f);
|
||||
black3 = whiteui.tint(0f, 0f, 0f, 0.3f);
|
||||
none = whiteui.tint(0f, 0f, 0f, 0f);
|
||||
grayPanel = whiteui.tint(Pal.darkestGray);
|
||||
flatDown = createFlatDown();
|
||||
flatOver = whiteui.tint(Color.valueOf("454545"));
|
||||
accentDrawable = whiteui.tint(Pal.accent);
|
||||
|
||||
defaultb = new ButtonStyle(){{
|
||||
down = buttonDown;
|
||||
@@ -54,16 +120,11 @@ public class Styles{
|
||||
disabled = buttonDisabled;
|
||||
}};
|
||||
|
||||
modsb = new ButtonStyle(){{
|
||||
underlineb = new ButtonStyle(){{
|
||||
down = flatOver;
|
||||
up = underline;
|
||||
over = underlineWhite;
|
||||
}};
|
||||
|
||||
waveb = new ButtonStyle(){{
|
||||
up = wavepane;
|
||||
over = wavepane; //TODO wrong
|
||||
disabled = wavepane;
|
||||
up = sideline;
|
||||
over = sidelineOver;
|
||||
checked = flatOver;
|
||||
}};
|
||||
|
||||
defaultt = new TextButtonStyle(){{
|
||||
@@ -75,23 +136,6 @@ public class Styles{
|
||||
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;
|
||||
}};
|
||||
nonet = new TextButtonStyle(){{
|
||||
font = Fonts.outline;
|
||||
fontColor = Color.lightGray;
|
||||
@@ -99,7 +143,7 @@ public class Styles{
|
||||
disabledFontColor = Color.gray;
|
||||
up = none;
|
||||
}};
|
||||
cleart = new TextButtonStyle(){{
|
||||
flatt = new TextButtonStyle(){{
|
||||
over = flatOver;
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
@@ -107,6 +151,14 @@ public class Styles{
|
||||
down = flatOver;
|
||||
up = black;
|
||||
}};
|
||||
grayt = new TextButtonStyle(){{
|
||||
over = flatOver;
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.lightGray;
|
||||
down = flatOver;
|
||||
up = grayPanel;
|
||||
}};
|
||||
logict = new TextButtonStyle(){{
|
||||
over = flatOver;
|
||||
font = Fonts.def;
|
||||
@@ -115,17 +167,7 @@ public class Styles{
|
||||
down = flatOver;
|
||||
up = underlineWhite;
|
||||
}};
|
||||
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(){{
|
||||
flatBordert = new TextButtonStyle(){{
|
||||
down = flatOver;
|
||||
up = pane;
|
||||
over = flatDownBase;
|
||||
@@ -133,7 +175,7 @@ public class Styles{
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.gray;
|
||||
}};
|
||||
transt = new TextButtonStyle(){{
|
||||
cleart = new TextButtonStyle(){{
|
||||
down = flatDown;
|
||||
up = none;
|
||||
over = flatOver;
|
||||
@@ -141,7 +183,7 @@ public class Styles{
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.gray;
|
||||
}};
|
||||
clearTogglet = new TextButtonStyle(){{
|
||||
flatTogglet = new TextButtonStyle(){{
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
checked = flatDown;
|
||||
@@ -151,7 +193,17 @@ public class Styles{
|
||||
disabled = black;
|
||||
disabledFontColor = Color.gray;
|
||||
}};
|
||||
clearToggleMenut = new TextButtonStyle(){{
|
||||
logicTogglet = new TextButtonStyle(){{
|
||||
font = Fonts.outline;
|
||||
fontColor = Color.white;
|
||||
checked = accentDrawable;
|
||||
down = accentDrawable;
|
||||
up = black;
|
||||
over = flatOver;
|
||||
disabled = black;
|
||||
disabledFontColor = Color.gray;
|
||||
}};
|
||||
flatToggleMenut = new TextButtonStyle(){{
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
checked = flatDown;
|
||||
@@ -171,6 +223,16 @@ public class Styles{
|
||||
disabled = buttonDisabled;
|
||||
disabledFontColor = Color.gray;
|
||||
}};
|
||||
clearTogglet = new TextButtonStyle(){{
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
down = flatDown;
|
||||
checked = flatDown;
|
||||
up = black6;
|
||||
over = flatOver;
|
||||
disabled = black;
|
||||
disabledFontColor = Color.gray;
|
||||
}};
|
||||
fullTogglet = new TextButtonStyle(){{
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
@@ -181,6 +243,16 @@ public class Styles{
|
||||
disabled = black;
|
||||
disabledFontColor = Color.gray;
|
||||
}};
|
||||
squareTogglet = new TextButtonStyle(){{
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
checked = flatOver;
|
||||
down = flatOver;
|
||||
up = pane;
|
||||
over = flatOver;
|
||||
disabled = black;
|
||||
disabledFontColor = Color.gray;
|
||||
}};
|
||||
defaulti = new ImageButtonStyle(){{
|
||||
down = buttonDown;
|
||||
up = button;
|
||||
@@ -193,19 +265,12 @@ public class Styles{
|
||||
up = buttonOver;
|
||||
over = buttonDown;
|
||||
}};
|
||||
righti = new ImageButtonStyle(){{
|
||||
over = buttonRightOver;
|
||||
down = buttonRightDown;
|
||||
up = buttonRight;
|
||||
disabled = buttonRightDisabled;
|
||||
imageDisabledColor = Color.clear;
|
||||
imageUpColor = Color.white;
|
||||
}};
|
||||
emptyi = new ImageButtonStyle(){{
|
||||
imageDownColor = Pal.accent;
|
||||
imageOverColor = Color.lightGray;
|
||||
imageUpColor = Color.white;
|
||||
}};
|
||||
emptytogglei = new ImageButtonStyle(){{
|
||||
emptyTogglei = new ImageButtonStyle(){{
|
||||
imageCheckedColor = Color.white;
|
||||
imageDownColor = Color.white;
|
||||
imageUpColor = Color.gray;
|
||||
@@ -215,33 +280,35 @@ public class Styles{
|
||||
up = none;
|
||||
}};
|
||||
logici = new ImageButtonStyle(){{
|
||||
//imageDownColor = Pal.accent;
|
||||
imageUpColor = Color.black;
|
||||
}};
|
||||
geni = new ImageButtonStyle(){{
|
||||
imageDownColor = Pal.accent;
|
||||
imageUpColor = Color.black;
|
||||
}};
|
||||
colori = new ImageButtonStyle(){{
|
||||
//imageDownColor = Pal.accent;
|
||||
imageUpColor = Color.white;
|
||||
}};
|
||||
accenti = new ImageButtonStyle(){{
|
||||
//imageDownColor = Pal.accent;
|
||||
grayi = new ImageButtonStyle(){{
|
||||
imageUpColor = Color.lightGray;
|
||||
imageDownColor = Color.white;
|
||||
}};
|
||||
cleari = new ImageButtonStyle(){{
|
||||
graySquarei = new ImageButtonStyle(){{
|
||||
imageUpColor = Color.white;
|
||||
imageDownColor = Color.lightGray;
|
||||
|
||||
over = flatOver;
|
||||
down = flatOver;
|
||||
up = grayPanel;
|
||||
}};
|
||||
flati = new ImageButtonStyle(){{
|
||||
down = flatOver;
|
||||
up = black;
|
||||
over = flatOver;
|
||||
}};
|
||||
clearFulli = new ImageButtonStyle(){{
|
||||
squarei = new ImageButtonStyle(){{
|
||||
down = whiteui;
|
||||
up = pane;
|
||||
over = flatDown;
|
||||
}};
|
||||
clearPartiali = new ImageButtonStyle(){{
|
||||
clearNonei = new ImageButtonStyle(){{
|
||||
down = flatDown;
|
||||
up = none;
|
||||
over = flatOver;
|
||||
@@ -249,18 +316,13 @@ public class Styles{
|
||||
imageDisabledColor = Color.gray;
|
||||
imageUpColor = Color.white;
|
||||
}};
|
||||
clearPartial2i = new ImageButtonStyle(){{
|
||||
down = whiteui;
|
||||
up = pane;
|
||||
over = flatDown;
|
||||
}};
|
||||
clearTogglei = new ImageButtonStyle(){{
|
||||
squareTogglei = new ImageButtonStyle(){{
|
||||
down = flatDown;
|
||||
checked = flatDown;
|
||||
up = black;
|
||||
over = flatOver;
|
||||
}};
|
||||
clearTransi = new ImageButtonStyle(){{
|
||||
cleari = new ImageButtonStyle(){{
|
||||
down = flatDown;
|
||||
up = black6;
|
||||
over = flatOver;
|
||||
@@ -268,13 +330,13 @@ public class Styles{
|
||||
imageDisabledColor = Color.lightGray;
|
||||
imageUpColor = Color.white;
|
||||
}};
|
||||
clearToggleTransi = new ImageButtonStyle(){{
|
||||
clearTogglei = new ImageButtonStyle(){{
|
||||
down = flatDown;
|
||||
checked = flatDown;
|
||||
up = black6;
|
||||
over = flatOver;
|
||||
}};
|
||||
clearTogglePartiali = new ImageButtonStyle(){{
|
||||
clearNoneTogglei = new ImageButtonStyle(){{
|
||||
down = flatDown;
|
||||
checked = flatDown;
|
||||
up = none;
|
||||
@@ -295,21 +357,10 @@ public class Styles{
|
||||
vScroll = clear;
|
||||
vScrollKnob = scrollKnobVerticalThin;
|
||||
}};
|
||||
|
||||
defaultKeybindDialog = new KeybindDialogStyle(){{
|
||||
keyColor = Pal.accent;
|
||||
keyNameColor = Color.white;
|
||||
controllerColor = Color.lightGray;
|
||||
}};
|
||||
noBarPane = new ScrollPaneStyle();
|
||||
|
||||
defaultSlider = new SliderStyle(){{
|
||||
background = slider;
|
||||
knob = sliderKnob;
|
||||
knobOver = sliderKnobOver;
|
||||
knobDown = sliderKnobDown;
|
||||
}};
|
||||
vSlider = new SliderStyle(){{
|
||||
background = sliderVertical;
|
||||
background = sliderBack;
|
||||
knob = sliderKnob;
|
||||
knobOver = sliderKnobOver;
|
||||
knobDown = sliderKnobDown;
|
||||
@@ -329,7 +380,7 @@ public class Styles{
|
||||
}};
|
||||
|
||||
defaultField = new TextFieldStyle(){{
|
||||
font = Fonts.chat;
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.gray;
|
||||
disabledBackground = underlineDisabled;
|
||||
@@ -342,7 +393,7 @@ public class Styles{
|
||||
}};
|
||||
|
||||
nodeField = new TextFieldStyle(){{
|
||||
font = Fonts.chat;
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.gray;
|
||||
disabledBackground = underlineDisabled;
|
||||
@@ -355,7 +406,7 @@ public class Styles{
|
||||
}};
|
||||
|
||||
areaField = new TextFieldStyle(){{
|
||||
font = Fonts.chat;
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.gray;
|
||||
selection = Tex.selection;
|
||||
@@ -366,7 +417,7 @@ public class Styles{
|
||||
}};
|
||||
|
||||
nodeArea = new TextFieldStyle(){{
|
||||
font = Fonts.chat;
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.gray;
|
||||
selection = Tex.selection;
|
||||
@@ -400,6 +451,13 @@ public class Styles{
|
||||
background = windowEmpty;
|
||||
titleFontColor = Pal.accent;
|
||||
}};
|
||||
|
||||
defaultTree = new TreeStyle(){{
|
||||
plus = Icon.downOpen;
|
||||
minus = Icon.upOpen;
|
||||
background = black5;
|
||||
over = flatOver;
|
||||
}};
|
||||
}
|
||||
|
||||
private static Drawable createFlatDown(){
|
||||
|
||||
@@ -2,6 +2,7 @@ package mindustry.ui;
|
||||
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
public class WarningBar extends Element{
|
||||
@@ -27,7 +28,7 @@ public class WarningBar extends Element{
|
||||
rx + barWidth, y
|
||||
);
|
||||
}
|
||||
Lines.stroke(3f);
|
||||
Lines.stroke(Scl.scl(3f));
|
||||
Lines.line(x, y, x + width, y);
|
||||
Lines.line(x, y + height, x + width, y + height);
|
||||
|
||||
|
||||
@@ -35,36 +35,36 @@ public class AboutDialog extends BaseDialog{
|
||||
buttons.clear();
|
||||
|
||||
float h = Core.graphics.isPortrait() ? 90f : 80f;
|
||||
float w = Core.graphics.isPortrait() ? 330f : 600f;
|
||||
float w = Core.graphics.isPortrait() ? 400f : 600f;
|
||||
|
||||
Table in = new Table();
|
||||
ScrollPane pane = new ScrollPane(in);
|
||||
|
||||
for(LinkEntry link : Links.getLinks()){
|
||||
if((ios || OS.isMac || steam) && bannedItems.contains(link.name)){
|
||||
if((ios || steam) && bannedItems.contains(link.name)){
|
||||
continue;
|
||||
}
|
||||
|
||||
Table table = new Table(Tex.underline);
|
||||
Table table = new Table(Styles.grayPanel);
|
||||
table.margin(0);
|
||||
table.table(img -> {
|
||||
img.image().height(h - 5).width(40f).color(link.color);
|
||||
img.row();
|
||||
img.image().height(5).width(40f).color(link.color.cpy().mul(0.8f, 0.8f, 0.8f, 1f));
|
||||
img.image().height(5).width(40f).color(link.color.cpy().mul(0.6f, 0.6f, 0.8f, 1f));
|
||||
}).expandY();
|
||||
|
||||
table.table(i -> {
|
||||
i.background(Tex.buttonEdge3);
|
||||
i.background(Styles.grayPanel);
|
||||
i.image(link.icon);
|
||||
}).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();
|
||||
inset.labelWrap(link.description).width(w - 100f - h).color(Color.lightGray).growX();
|
||||
}).padLeft(8);
|
||||
|
||||
table.button(Icon.link, () -> {
|
||||
table.button(Icon.link, Styles.clearNonei, () -> {
|
||||
if(link.name.equals("wiki")) Events.fire(Trigger.openWiki);
|
||||
|
||||
if(!Core.app.openURI(link.link)){
|
||||
@@ -84,12 +84,6 @@ public class AboutDialog extends BaseDialog{
|
||||
|
||||
buttons.button("@credits", this::showCredits).size(200f, 64f);
|
||||
|
||||
if(Core.graphics.isPortrait()){
|
||||
for(Cell<?> cell : buttons.getCells()){
|
||||
cell.width(140f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void showCredits(){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -39,7 +40,7 @@ public class AdminsDialog extends BaseDialog{
|
||||
res.labelWrap("[lightgray]" + info.lastName).width(w - h - 24f);
|
||||
res.add().growX();
|
||||
res.button(Icon.cancel, () -> {
|
||||
ui.showConfirm("@confirm", "@confirmunadmin", () -> {
|
||||
ui.showConfirm("@confirm", Core.bundle.format("@confirmunadmin", info.lastName), () -> {
|
||||
netServer.admins.unAdminPlayer(info.id);
|
||||
Groups.player.each(player -> {
|
||||
if(player != null && !player.isLocal() && player.uuid().equals(info.id)){
|
||||
|
||||
@@ -11,7 +11,8 @@ import mindustry.graphics.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BaseDialog extends Dialog{
|
||||
private boolean wasPaused;
|
||||
protected boolean wasPaused;
|
||||
/** If true, this dialog will pause the game while open. */
|
||||
protected boolean shouldPause;
|
||||
|
||||
public BaseDialog(String title, DialogStyle style){
|
||||
@@ -19,20 +20,17 @@ public class BaseDialog extends Dialog{
|
||||
setFillParent(true);
|
||||
this.title.setAlignment(Align.center);
|
||||
titleTable.row();
|
||||
titleTable.image(Tex.whiteui, Pal.accent)
|
||||
.growX().height(3f).pad(4f);
|
||||
titleTable.image(Tex.whiteui, Pal.accent).growX().height(3f).pad(4f);
|
||||
|
||||
hidden(() -> {
|
||||
if(shouldPause && state.isGame()){
|
||||
if(!wasPaused || net.active()){
|
||||
state.set(State.playing);
|
||||
}
|
||||
if(shouldPause && state.isGame() && !net.active() && !wasPaused){
|
||||
state.set(State.playing);
|
||||
}
|
||||
Sounds.back.play();
|
||||
});
|
||||
|
||||
shown(() -> {
|
||||
if(shouldPause && state.isGame()){
|
||||
if(shouldPause && state.isGame() && !net.active()){
|
||||
wasPaused = state.is(State.paused);
|
||||
state.set(State.paused);
|
||||
}
|
||||
@@ -43,6 +41,14 @@ public class BaseDialog extends Dialog{
|
||||
this(title, Core.scene.getStyle(DialogStyle.class));
|
||||
}
|
||||
|
||||
/** Places the buttons as an overlay on top of the content. Used when the content can be scrolled through.*/
|
||||
protected void makeButtonOverlay(){
|
||||
clearChildren();
|
||||
add(titleTable).growX().row();
|
||||
stack(cont, buttons).grow();
|
||||
buttons.bottom();
|
||||
}
|
||||
|
||||
protected void onResize(Runnable run){
|
||||
Events.on(ResizeEvent.class, event -> {
|
||||
if(isShown() && Core.scene.getDialog() == this){
|
||||
@@ -56,11 +62,15 @@ public class BaseDialog extends Dialog{
|
||||
closeOnBack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCloseButton(){
|
||||
buttons.defaults().size(210f, 64f);
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
|
||||
public void addCloseButton(float width){
|
||||
buttons.defaults().size(width, 64f);
|
||||
buttons.button("@back", Icon.left, this::hide).size(width, 64f);
|
||||
|
||||
addCloseListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCloseButton(){
|
||||
addCloseButton(210f);
|
||||
}
|
||||
}
|
||||
|
||||
45
core/src/mindustry/ui/dialogs/CampaignCompleteDialog.java
Normal file
45
core/src/mindustry/ui/dialogs/CampaignCompleteDialog.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.actions.*;
|
||||
import mindustry.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
import static arc.scene.actions.Actions.*;
|
||||
|
||||
public class CampaignCompleteDialog extends BaseDialog{
|
||||
|
||||
public CampaignCompleteDialog(){
|
||||
super("");
|
||||
|
||||
addCloseListener();
|
||||
shouldPause = true;
|
||||
|
||||
buttons.defaults().size(210f, 64f);
|
||||
buttons.button("@menu", Icon.left, () -> {
|
||||
hide();
|
||||
Vars.ui.paused.runExitSave();
|
||||
});
|
||||
|
||||
buttons.button("@continue", Icon.ok, this::hide);
|
||||
}
|
||||
|
||||
public void show(Planet planet){
|
||||
cont.clear();
|
||||
|
||||
cont.add(Core.bundle.format("campaign.complete", "[#" + planet.iconColor + "]" + planet.localizedName + "[]")).row();
|
||||
|
||||
float playtime = planet.sectors.sumf(s -> s.hasSave() ? s.save.meta.timePlayed : 0) / 1000f;
|
||||
|
||||
//TODO needs more info?
|
||||
cont.add(Core.bundle.format("campaign.playtime", UI.formatTime(playtime))).left().row();
|
||||
|
||||
setTranslation(0f, -Core.graphics.getHeight());
|
||||
color.a = 0f;
|
||||
|
||||
show(Core.scene, Actions.sequence(parallel(fadeIn(1.1f, Interp.fade), translateBy(0f, Core.graphics.getHeight(), 6f, Interp.pow5Out))));
|
||||
}
|
||||
}
|
||||
100
core/src/mindustry/ui/dialogs/CampaignRulesDialog.java
Normal file
100
core/src/mindustry/ui/dialogs/CampaignRulesDialog.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
public class CampaignRulesDialog extends BaseDialog{
|
||||
Planet planet;
|
||||
Table current;
|
||||
|
||||
public CampaignRulesDialog(){
|
||||
super("@campaign.difficulty");
|
||||
|
||||
addCloseButton();
|
||||
|
||||
hidden(() -> {
|
||||
if(planet != null){
|
||||
planet.saveRules();
|
||||
|
||||
if(Vars.state.isGame() && Vars.state.isCampaign() && Vars.state.getPlanet() == planet){
|
||||
planet.campaignRules.apply(planet, Vars.state.rules);
|
||||
Call.setRules(Vars.state.rules);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onResize(() -> {
|
||||
rebuild();
|
||||
});
|
||||
}
|
||||
|
||||
void rebuild(){
|
||||
CampaignRules rules = planet.campaignRules;
|
||||
cont.clear();
|
||||
|
||||
cont.top().pane(inner -> {
|
||||
inner.top().left().defaults().fillX().left().pad(5);
|
||||
current = inner;
|
||||
|
||||
current.table(Tex.button, t -> {
|
||||
t.margin(10f);
|
||||
var group = new ButtonGroup<>();
|
||||
var style = Styles.flatTogglet;
|
||||
|
||||
t.defaults().size(140f, 50f);
|
||||
|
||||
for(Difficulty diff : Difficulty.all){
|
||||
t.button(diff.localized(), style, () -> {
|
||||
rules.difficulty = diff;
|
||||
}).group(group).checked(b -> rules.difficulty == diff);
|
||||
|
||||
if(Core.graphics.isPortrait() && diff.ordinal() % 2 == 1){
|
||||
t.row();
|
||||
}
|
||||
}
|
||||
}).left().fill(false).expand(false, false).row();
|
||||
|
||||
if(planet.allowSectorInvasion){
|
||||
check("@rules.invasions", b -> rules.sectorInvasion = b, () -> rules.sectorInvasion);
|
||||
}
|
||||
|
||||
check("@rules.fog", b -> rules.fog = b, () -> rules.fog);
|
||||
check("@rules.showspawns", b -> rules.showSpawns = b, () -> rules.showSpawns);
|
||||
check("@rules.randomwaveai", b -> rules.randomWaveAI = b, () -> rules.randomWaveAI);
|
||||
|
||||
//TODO: this is intentionally hidden until the new mechanics have been well-tested. I don't want people immediately switching to the old mechanics
|
||||
if(planet.allowLegacyLaunchPads){
|
||||
// check("@rules.legacylaunchpads", b -> rules.legacyLaunchPads = b, () -> rules.legacyLaunchPads);
|
||||
}
|
||||
}).growY();
|
||||
}
|
||||
|
||||
public void show(Planet planet){
|
||||
this.planet = planet;
|
||||
|
||||
rebuild();
|
||||
show();
|
||||
}
|
||||
|
||||
void check(String text, Boolc cons, Boolp prov){
|
||||
check(text, cons, prov, () -> true);
|
||||
}
|
||||
|
||||
void check(String text, Boolc cons, Boolp prov, Boolp condition){
|
||||
String infoText = text.substring(1) + ".info";
|
||||
var cell = current.check(text, cons).checked(prov.get()).update(a -> a.setDisabled(!condition.get()));
|
||||
if(Core.bundle.has(infoText)){
|
||||
cell.tooltip(text + ".info");
|
||||
}
|
||||
cell.get().left();
|
||||
current.row();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,13 +2,21 @@ package mindustry.ui.dialogs;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
public class ColorPicker extends BaseDialog{
|
||||
private static Texture hueTex;
|
||||
|
||||
private Cons<Color> cons = c -> {};
|
||||
Color current = new Color();
|
||||
float h, s, v, a;
|
||||
TextField hexField;
|
||||
Slider hSlider, sSlider, vSlider, aSlider;
|
||||
|
||||
public ColorPicker(){
|
||||
super("@pickcolor");
|
||||
@@ -23,6 +31,17 @@ public class ColorPicker extends BaseDialog{
|
||||
this.cons = consumer;
|
||||
show();
|
||||
|
||||
if(hueTex == null){
|
||||
hueTex = Pixmaps.hueTexture(128, 1);
|
||||
hueTex.setFilter(TextureFilter.linear);
|
||||
}
|
||||
|
||||
float[] values = color.toHsv(new float[3]);
|
||||
h = values[0];
|
||||
s = values[1];
|
||||
v = values[2];
|
||||
a = color.a;
|
||||
|
||||
cont.clear();
|
||||
cont.pane(t -> {
|
||||
t.table(Tex.pane, i -> {
|
||||
@@ -32,26 +51,114 @@ public class ColorPicker extends BaseDialog{
|
||||
}}).size(200f);
|
||||
}).colspan(2).padBottom(5);
|
||||
|
||||
float w = 150f;
|
||||
|
||||
t.row();
|
||||
|
||||
t.defaults().padBottom(4);
|
||||
t.add("R").color(Pal.remove);
|
||||
t.slider(0f, 1f, 0.01f, current.r, current::r).width(w);
|
||||
t.row();
|
||||
t.add("G").color(Color.lime);
|
||||
t.slider(0f, 1f, 0.01f, current.g, current::g).width(w);
|
||||
t.row();
|
||||
t.add("B").color(Color.royal);
|
||||
t.slider(0f, 1f, 0.01f, current.b, current::b).width(w);
|
||||
t.row();
|
||||
t.defaults().padBottom(6).width(370f).height(44f);
|
||||
|
||||
t.stack(new Image(new TextureRegion(hueTex)), hSlider = new Slider(0f, 360f, 0.3f, false){{
|
||||
setValue(h);
|
||||
moved(value -> {
|
||||
h = value;
|
||||
updateColor();
|
||||
});
|
||||
}}).row();
|
||||
|
||||
t.stack(new Element(){
|
||||
@Override
|
||||
public void draw(){
|
||||
float first = Tmp.c1.set(current).saturation(0f).a(parentAlpha).toFloatBits();
|
||||
float second = Tmp.c1.set(current).saturation(1f).a(parentAlpha).toFloatBits();
|
||||
|
||||
Fill.quad(
|
||||
x, y, first,
|
||||
x + width, y, second,
|
||||
x + width, y + height, second,
|
||||
x, y + height, first
|
||||
);
|
||||
}
|
||||
}, sSlider = new Slider(0f, 1f, 0.001f, false){{
|
||||
setValue(s);
|
||||
moved(value -> {
|
||||
s = value;
|
||||
updateColor();
|
||||
});
|
||||
}}).row();
|
||||
|
||||
t.stack(new Element(){
|
||||
@Override
|
||||
public void draw(){
|
||||
float first = Tmp.c1.set(current).value(0f).a(parentAlpha).toFloatBits();
|
||||
float second = Tmp.c1.fromHsv(h, s, 1f).a(parentAlpha).toFloatBits();
|
||||
|
||||
Fill.quad(
|
||||
x, y, first,
|
||||
x + width, y, second,
|
||||
x + width, y + height, second,
|
||||
x, y + height, first
|
||||
);
|
||||
}
|
||||
}, vSlider = new Slider(0f, 1f, 0.001f, false){{
|
||||
setValue(v);
|
||||
|
||||
moved(value -> {
|
||||
v = value;
|
||||
updateColor();
|
||||
});
|
||||
}}).row();
|
||||
|
||||
if(alpha){
|
||||
t.add("A");
|
||||
t.slider(0f, 1f, 0.01f, current.a, current::a).width(w);
|
||||
t.row();
|
||||
t.stack(new Image(Tex.alphaBgLine), new Element(){
|
||||
@Override
|
||||
public void draw(){
|
||||
float first = Tmp.c1.set(current).a(0f).toFloatBits();
|
||||
float second = Tmp.c1.set(current).a(parentAlpha).toFloatBits();
|
||||
|
||||
Fill.quad(
|
||||
x, y, first,
|
||||
x + width, y, second,
|
||||
x + width, y + height, second,
|
||||
x, y + height, first
|
||||
);
|
||||
}
|
||||
}, aSlider = new Slider(0f, 1f, 0.001f, false){{
|
||||
setValue(a);
|
||||
|
||||
moved(value -> {
|
||||
a = value;
|
||||
updateColor();
|
||||
});
|
||||
}}).row();
|
||||
}
|
||||
});
|
||||
|
||||
hexField = t.field(current.toString(), value -> {
|
||||
try{
|
||||
Color.valueOf(current, value);
|
||||
current.toHsv(values);
|
||||
h = values[0];
|
||||
s = values[1];
|
||||
v = values[2];
|
||||
a = current.a;
|
||||
|
||||
hSlider.setValue(h);
|
||||
sSlider.setValue(s);
|
||||
vSlider.setValue(v);
|
||||
if(aSlider != null){
|
||||
aSlider.setValue(a);
|
||||
}
|
||||
|
||||
updateColor(false);
|
||||
}catch(Exception ignored){
|
||||
}
|
||||
}).size(130f, 40f).valid(text -> {
|
||||
//garbage performance but who cares this runs only every key type anyway
|
||||
try{
|
||||
Color.valueOf(text);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
return false;
|
||||
}
|
||||
}).get();
|
||||
}).grow();
|
||||
|
||||
buttons.clear();
|
||||
addCloseButton();
|
||||
@@ -60,4 +167,21 @@ public class ColorPicker extends BaseDialog{
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
void updateColor(){
|
||||
updateColor(true);
|
||||
}
|
||||
|
||||
void updateColor(boolean updateField){
|
||||
current.fromHsv(h, s, v);
|
||||
current.a = a;
|
||||
|
||||
if(hexField != null && updateField){
|
||||
String val = current.toString();
|
||||
if(current.a >= 0.9999f){
|
||||
val = val.substring(0, 6);
|
||||
}
|
||||
hexField.setText(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.scene.actions.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class ContentInfoDialog extends BaseDialog{
|
||||
|
||||
public ContentInfoDialog(){
|
||||
super("@info.title");
|
||||
|
||||
addCloseButton();
|
||||
|
||||
keyDown(key -> {
|
||||
if(key == keybinds.get(Binding.block_info).key){
|
||||
Core.app.post(this::hide);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void show(UnlockableContent content){
|
||||
@@ -27,10 +39,8 @@ public class ContentInfoDialog extends BaseDialog{
|
||||
content.checkStats();
|
||||
|
||||
table.table(title1 -> {
|
||||
int size = 8 * 6;
|
||||
|
||||
title1.image(content.icon(Cicon.xlarge)).size(size).scaling(Scaling.fit);
|
||||
title1.add("[accent]" + content.localizedName).padLeft(5);
|
||||
title1.image(content.uiIcon).size(iconXLarge).scaling(Scaling.fit);
|
||||
title1.add("[accent]" + content.localizedName + (settings.getBool("console") ? "\n[gray]" + content.name : "")).padLeft(5);
|
||||
});
|
||||
|
||||
table.row();
|
||||
@@ -59,36 +69,43 @@ public class ContentInfoDialog extends BaseDialog{
|
||||
|
||||
if(map.size == 0) continue;
|
||||
|
||||
//TODO check
|
||||
if(stats.useCategories){
|
||||
table.add("@category." + cat.name()).color(Pal.accent).fillX();
|
||||
table.add("@category." + cat.name).color(Pal.accent).fillX();
|
||||
table.row();
|
||||
}
|
||||
|
||||
for(Stat stat : map.keys()){
|
||||
table.table(inset -> {
|
||||
inset.left();
|
||||
inset.add("[lightgray]" + stat.localized() + ":[] ").left();
|
||||
inset.add("[lightgray]" + stat.localized() + ":[] ").left().top();
|
||||
Seq<StatValue> arr = map.get(stat);
|
||||
for(StatValue value : arr){
|
||||
value.display(inset);
|
||||
inset.add().size(10f);
|
||||
}
|
||||
|
||||
}).fillX().padLeft(10);
|
||||
table.row();
|
||||
}
|
||||
}
|
||||
|
||||
if(content.details != null){
|
||||
table.add("[gray]" + content.details).pad(6).padTop(20).width(400f).wrap().fillX();
|
||||
table.add("[gray]" + (content.unlocked() || !content.hideDetails ? content.details : Iconc.lock + " " + Core.bundle.get("unlock.incampaign"))).pad(6).padTop(20).width(400f).wrap().fillX();
|
||||
table.row();
|
||||
}
|
||||
|
||||
content.displayExtra(table);
|
||||
|
||||
ScrollPane pane = new ScrollPane(table);
|
||||
table.marginRight(30f);
|
||||
//TODO: some things (e.g. reconstructor requirements) are too long and screw up the layout
|
||||
//pane.setScrollingDisabled(true, false);
|
||||
cont.add(pane);
|
||||
|
||||
show();
|
||||
if(isShown()){
|
||||
show(scene, Actions.fadeIn(0f));
|
||||
}else{
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
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.button("@back", Icon.left, this::hide).size(230f, 64f);
|
||||
|
||||
keyDown(key -> {
|
||||
if(key == KeyCode.escape || key == KeyCode.back) hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,92 +1,16 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.style.*;
|
||||
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 BaseDialog{
|
||||
public class CustomGameDialog extends MapListDialog{
|
||||
private MapPlayDialog dialog = new MapPlayDialog();
|
||||
|
||||
public CustomGameDialog(){
|
||||
super("@customgame");
|
||||
addCloseButton();
|
||||
shown(this::setup);
|
||||
onResize(this::setup);
|
||||
super("@customgame", false);
|
||||
}
|
||||
|
||||
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 = Math.max((int)(Core.graphics.getWidth() / Scl.scl(210)), 1);
|
||||
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){
|
||||
TextureRegionDrawable icon = Vars.ui.getIcon("mode" + Strings.capitalize(mode.name()) + "Small");
|
||||
if(mode.valid(map) && Core.atlas.isFound(icon.getRegion())){
|
||||
t.image(icon).size(16f).pad(4f);
|
||||
}
|
||||
}
|
||||
}).left();
|
||||
image.row();
|
||||
image.add(map.name()).pad(1f).growX().wrap().left().get().setEllipsis(true);
|
||||
image.row();
|
||||
image.image(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();
|
||||
@Override
|
||||
void showMap(Map map){
|
||||
dialog.show(map);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,20 @@ package mindustry.ui.dialogs;
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.ImageButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.editor.BannedContentDialog;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Rules.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.type.Weather.*;
|
||||
import mindustry.ui.*;
|
||||
@@ -28,87 +30,84 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
private Table main;
|
||||
private Prov<Rules> resetter;
|
||||
private LoadoutDialog loadoutDialog;
|
||||
private BaseDialog banDialog;
|
||||
private BannedContentDialog<Block> bannedBlocks = new BannedContentDialog<>("@bannedblocks", ContentType.block, Block::canBeBuilt);
|
||||
private BannedContentDialog<UnitType> bannedUnits = new BannedContentDialog<>("@bannedunits", ContentType.unit, u -> !u.isHidden());
|
||||
public boolean showRuleEditRule;
|
||||
public Seq<Table> categories;
|
||||
public Table current;
|
||||
public Seq<String> categoryNames;
|
||||
public String currentName = "";
|
||||
public String ruleSearch = "";
|
||||
public Seq<Runnable> additionalSetup; // for modding to easily add new rules
|
||||
|
||||
public CustomRulesDialog(){
|
||||
this(false);
|
||||
}
|
||||
|
||||
public CustomRulesDialog(boolean showRuleEditRule){
|
||||
super("@mode.custom");
|
||||
|
||||
this.showRuleEditRule = showRuleEditRule;
|
||||
|
||||
loadoutDialog = new LoadoutDialog();
|
||||
banDialog = new BaseDialog("@bannedblocks");
|
||||
banDialog.addCloseButton();
|
||||
|
||||
banDialog.shown(this::rebuildBanned);
|
||||
banDialog.buttons.button("@addall", Icon.add, () -> {
|
||||
rules.bannedBlocks.addAll(content.blocks().select(Block::canBeBuilt));
|
||||
rebuildBanned();
|
||||
}).size(180, 64f);
|
||||
|
||||
banDialog.buttons.button("@clear", Icon.trash, () -> {
|
||||
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);
|
||||
additionalSetup = new Seq<>();
|
||||
categories = new Seq<>();
|
||||
categoryNames = new Seq<>();
|
||||
|
||||
if(rules.bannedBlocks.isEmpty()){
|
||||
t.add("@empty");
|
||||
}
|
||||
buttons.button("@edit", Icon.pencil, () -> {
|
||||
BaseDialog dialog = new BaseDialog("@waves.edit");
|
||||
dialog.addCloseButton();
|
||||
dialog.setFillParent(false);
|
||||
|
||||
Seq<Block> array = Seq.with(rules.bannedBlocks);
|
||||
array.sort();
|
||||
dialog.cont.table(Tex.button, t -> {
|
||||
var style = Styles.cleart;
|
||||
t.defaults().size(280f, 64f).pad(2f);
|
||||
|
||||
int cols = mobile && Core.graphics.isPortrait() ? 1 : mobile ? 2 : 3;
|
||||
int i = 0;
|
||||
t.button("@waves.copy", Icon.copy, style, () -> {
|
||||
ui.showInfoFade("@copied");
|
||||
|
||||
for(Block block : array){
|
||||
t.table(Tex.underline, b -> {
|
||||
b.left().margin(4f);
|
||||
b.image(block.icon(Cicon.medium)).size(Cicon.medium.size).padRight(3);
|
||||
b.add(block.localizedName).color(Color.lightGray).padLeft(3).growX().left().wrap();
|
||||
//hack: don't write the spawns, they just waste space
|
||||
var spawns = rules.spawns;
|
||||
rules.spawns = new Seq<>();
|
||||
Core.app.setClipboardText(JsonIO.write(rules));
|
||||
rules.spawns = spawns;
|
||||
dialog.hide();
|
||||
}).marginLeft(12f).row();
|
||||
|
||||
b.button(Icon.cancel, 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.button("@add", Icon.add, () -> {
|
||||
BaseDialog dialog = new BaseDialog("@add");
|
||||
dialog.cont.pane(t -> {
|
||||
t.left().margin(14f);
|
||||
int[] i = {0};
|
||||
content.blocks().each(b -> !rules.bannedBlocks.contains(b) && b.canBeBuilt(), b -> {
|
||||
int cols = mobile && Core.graphics.isPortrait() ? 4 : 12;
|
||||
t.button(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();
|
||||
t.button("@waves.load", Icon.download, style, () -> {
|
||||
try{
|
||||
Rules newRules = JsonIO.read(Rules.class, Core.app.getClipboardText());
|
||||
//objectives and spawns are considered to be map-specific; don't use them
|
||||
newRules.spawns = rules.spawns;
|
||||
newRules.objectives = rules.objectives;
|
||||
rules = newRules;
|
||||
refresh();
|
||||
}catch(Throwable e){
|
||||
Log.err(e);
|
||||
ui.showErrorMessage("@rules.invaliddata");
|
||||
}
|
||||
});
|
||||
dialog.hide();
|
||||
}).disabled(Core.app.getClipboardText() == null || !Core.app.getClipboardText().startsWith("{")).marginLeft(12f).row();
|
||||
|
||||
t.button("@settings.reset", Icon.refresh, style, () -> {
|
||||
rules = resetter.get();
|
||||
refresh();
|
||||
}).marginLeft(12f);
|
||||
});
|
||||
|
||||
dialog.addCloseButton();
|
||||
dialog.show();
|
||||
}).size(300f, 64f);
|
||||
});
|
||||
}
|
||||
|
||||
void refresh(){
|
||||
setup();
|
||||
requestKeyboard();
|
||||
requestScroll();
|
||||
}
|
||||
|
||||
public void show(Rules rules, Prov<Rules> resetter){
|
||||
@@ -118,125 +117,330 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
void setup(){
|
||||
categories.clear();
|
||||
cont.clear();
|
||||
cont.pane(m -> main = m).get().setScrollingDisabled(true, false);
|
||||
cont.table(t -> {
|
||||
t.add("@search").padRight(10);
|
||||
var field = t.field(ruleSearch, text -> {
|
||||
ruleSearch = text.trim().replaceAll(" +", " ").toLowerCase();
|
||||
setup();
|
||||
}).grow().pad(8).get();
|
||||
field.setCursorPosition(ruleSearch.length());
|
||||
Core.scene.setKeyboardFocus(field);
|
||||
t.button(Icon.cancel, Styles.emptyi, () -> {
|
||||
ruleSearch = "";
|
||||
setup();
|
||||
}).padLeft(10f).size(35f);
|
||||
t.button(Icon.zoom, Styles.emptyi, this::setup).size(54f);
|
||||
}).row();
|
||||
cont.pane(m -> main = m).scrollX(false);
|
||||
main.margin(10f);
|
||||
main.button("@settings.reset", () -> {
|
||||
rules = resetter.get();
|
||||
setup();
|
||||
requestKeyboard();
|
||||
requestScroll();
|
||||
}).size(300f, 50f);
|
||||
main.left().defaults().fillX().left().pad(5);
|
||||
main.left().defaults().fillX().left();
|
||||
main.row();
|
||||
|
||||
title("@rules.title.waves");
|
||||
category("waves");
|
||||
check("@rules.waves", b -> rules.waves = b, () -> rules.waves);
|
||||
check("@rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer);
|
||||
check("@rules.waitForWaveToEnd", b -> rules.waitEnemies = b, () -> rules.waitEnemies);
|
||||
number("@rules.wavespacing", false, f -> rules.waveSpacing = f * 60f, () -> rules.waveSpacing / 60f, () -> true, 1, Float.MAX_VALUE);
|
||||
number("@rules.dropzoneradius", false, f -> rules.dropZoneRadius = f * tilesize, () -> rules.dropZoneRadius / tilesize, () -> true);
|
||||
check("@rules.wavesending", b -> rules.waveSending = b, () -> rules.waveSending, () -> rules.waves);
|
||||
check("@rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer, () -> rules.waves);
|
||||
check("@rules.waitForWaveToEnd", b -> rules.waitEnemies = b, () -> rules.waitEnemies, () -> rules.waves && rules.waveTimer);
|
||||
check("@rules.randomwaveai", b -> rules.randomWaveAI = b, () -> rules.randomWaveAI, () -> rules.waves);
|
||||
check("@rules.airUseSpawns", b -> rules.airUseSpawns = b, () -> rules.airUseSpawns, () -> rules.waves);
|
||||
numberi("@rules.wavelimit", f -> rules.winWave = f, () -> rules.winWave, () -> rules.waves, 0, Integer.MAX_VALUE);
|
||||
number("@rules.wavespacing", false, f -> rules.waveSpacing = f * 60f, () -> rules.waveSpacing / 60f, () -> rules.waves && rules.waveTimer, 1, Float.MAX_VALUE);
|
||||
number("@rules.initialwavespacing", false, f -> rules.initialWaveSpacing = f * 60f, () -> rules.initialWaveSpacing / 60f, () -> rules.waves && rules.waveTimer, 0, Float.MAX_VALUE);
|
||||
number("@rules.dropzoneradius", false, f -> rules.dropZoneRadius = f * tilesize, () -> rules.dropZoneRadius / tilesize, () -> rules.waves);
|
||||
|
||||
title("@rules.title.resourcesbuilding");
|
||||
category("resourcesbuilding");
|
||||
check("@rules.alloweditworldprocessors", b -> rules.allowEditWorldProcessors = b, () -> rules.allowEditWorldProcessors);
|
||||
check("@rules.infiniteresources", b -> rules.infiniteResources = b, () -> rules.infiniteResources);
|
||||
check("@rules.onlydepositcore", b -> rules.onlyDepositCore = b, () -> rules.onlyDepositCore);
|
||||
check("@rules.derelictrepair", b -> rules.derelictRepair = b, () -> rules.derelictRepair);
|
||||
check("@rules.reactorexplosions", b -> rules.reactorExplosions = b, () -> rules.reactorExplosions);
|
||||
check("@rules.schematic", b -> rules.schematicsAllowed = b, () -> rules.schematicsAllowed);
|
||||
check("@rules.coreincinerates", b -> rules.coreIncinerates = b, () -> rules.coreIncinerates);
|
||||
check("@rules.cleanupdeadteams", b -> rules.cleanupDeadTeams = b, () -> rules.cleanupDeadTeams, () -> rules.pvp);
|
||||
check("@rules.disableworldprocessors", b -> rules.disableWorldProcessors = b, () -> rules.disableWorldProcessors);
|
||||
number("@rules.buildcostmultiplier", false, f -> rules.buildCostMultiplier = f, () -> rules.buildCostMultiplier, () -> !rules.infiniteResources);
|
||||
number("@rules.buildspeedmultiplier", f -> rules.buildSpeedMultiplier = f, () -> rules.buildSpeedMultiplier, 0.00001f, 10000f);
|
||||
number("@rules.buildspeedmultiplier", f -> rules.buildSpeedMultiplier = f, () -> rules.buildSpeedMultiplier, 0.001f, 50f);
|
||||
number("@rules.deconstructrefundmultiplier", false, f -> rules.deconstructRefundMultiplier = f, () -> rules.deconstructRefundMultiplier, () -> !rules.infiniteResources);
|
||||
number("@rules.blockhealthmultiplier", f -> rules.blockHealthMultiplier = f, () -> rules.blockHealthMultiplier);
|
||||
number("@rules.blockdamagemultiplier", f -> rules.blockDamageMultiplier = f, () -> rules.blockDamageMultiplier);
|
||||
|
||||
main.button("@configure",
|
||||
() -> loadoutDialog.show(Blocks.coreShard.itemCapacity, rules.loadout,
|
||||
i -> true,
|
||||
() -> rules.loadout.clear().add(new ItemStack(Items.copper, 100)),
|
||||
() -> {}, () -> {}
|
||||
)).left().width(300f);
|
||||
main.row();
|
||||
if(Core.bundle.get("configure").toLowerCase().contains(ruleSearch)){
|
||||
current.button("@configure",
|
||||
() -> loadoutDialog.show(999999, rules.loadout,
|
||||
i -> true,
|
||||
() -> rules.loadout.clear().add(new ItemStack(Items.copper, 100)),
|
||||
() -> {}, () -> {}
|
||||
)).left().width(300f).row();
|
||||
}
|
||||
|
||||
main.button("@bannedblocks", banDialog::show).left().width(300f);
|
||||
main.row();
|
||||
if(Core.bundle.get("bannedblocks").toLowerCase().contains(ruleSearch)){
|
||||
current.button("@bannedblocks", () -> bannedBlocks.show(rules.bannedBlocks)).left().width(300f).row();
|
||||
}
|
||||
check("@rules.hidebannedblocks", b -> rules.hideBannedBlocks = b, () -> rules.hideBannedBlocks);
|
||||
check("@bannedblocks.whitelist", b -> rules.blockWhitelist = b, () -> rules.blockWhitelist);
|
||||
|
||||
title("@rules.title.unit");
|
||||
check("@rules.unitammo", b -> rules.unitAmmo = b, () -> rules.unitAmmo);
|
||||
number("@rules.unithealthmultiplier", f -> rules.unitHealthMultiplier = f, () -> rules.unitHealthMultiplier);
|
||||
|
||||
category("unit");
|
||||
check("@rules.unitcapvariable", b -> rules.unitCapVariable = b, () -> rules.unitCapVariable);
|
||||
check("@rules.unitpayloadsexplode", b -> rules.unitPayloadsExplode = b, () -> rules.unitPayloadsExplode);
|
||||
numberi("@rules.unitcap", f -> rules.unitCap = f, () -> rules.unitCap, -999, 999);
|
||||
number("@rules.unitdamagemultiplier", f -> rules.unitDamageMultiplier = f, () -> rules.unitDamageMultiplier);
|
||||
number("@rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier, 0.00001f, 100f);
|
||||
number("@rules.unitcrashdamagemultiplier", f -> rules.unitCrashDamageMultiplier = f, () -> rules.unitCrashDamageMultiplier);
|
||||
number("@rules.unitminespeedmultiplier", f -> rules.unitMineSpeedMultiplier = f, () -> rules.unitMineSpeedMultiplier);
|
||||
number("@rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier, 0f, 50f);
|
||||
number("@rules.unitcostmultiplier", f -> rules.unitCostMultiplier = f, () -> rules.unitCostMultiplier);
|
||||
number("@rules.unithealthmultiplier", f -> rules.unitHealthMultiplier = f, () -> rules.unitHealthMultiplier);
|
||||
|
||||
title("@rules.title.enemy");
|
||||
if(Core.bundle.get("bannedunits").toLowerCase().contains(ruleSearch)){
|
||||
current.button("@bannedunits", () -> bannedUnits.show(rules.bannedUnits)).left().width(300f).row();
|
||||
}
|
||||
check("@bannedunits.whitelist", b -> rules.unitWhitelist = b, () -> rules.unitWhitelist);
|
||||
|
||||
|
||||
category("enemy");
|
||||
check("@rules.attack", b -> rules.attackMode = b, () -> rules.attackMode);
|
||||
check("@rules.buildai", b -> rules.teams.get(rules.waveTeam).ai = rules.teams.get(rules.waveTeam).infiniteResources = b, () -> rules.teams.get(rules.waveTeam).ai);
|
||||
number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200));
|
||||
check("@rules.corecapture", b -> rules.coreCapture = b, () -> rules.coreCapture);
|
||||
check("@rules.placerangecheck", b -> rules.placeRangeCheck = b, () -> rules.placeRangeCheck);
|
||||
check("@rules.polygoncoreprotection", b -> rules.polygonCoreProtection = b, () -> rules.polygonCoreProtection);
|
||||
number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200), () -> !rules.polygonCoreProtection);
|
||||
|
||||
title("@rules.title.environment");
|
||||
|
||||
category("environment");
|
||||
check("@rules.explosions", b -> rules.damageExplosions = b, () -> rules.damageExplosions);
|
||||
check("@rules.fire", b -> rules.fire = b, () -> rules.fire);
|
||||
check("@rules.fog", b -> rules.fog = b, () -> rules.fog);
|
||||
check("@rules.lighting", b -> rules.lighting = b, () -> rules.lighting);
|
||||
check("@rules.enemyLights", b -> rules.enemyLights = b, () -> rules.enemyLights);
|
||||
|
||||
main.button(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).row();
|
||||
check("@rules.limitarea", b -> rules.limitMapArea = b, () -> rules.limitMapArea);
|
||||
numberi("x", x -> rules.limitX = x, () -> rules.limitX, () -> rules.limitMapArea, 0, 10000);
|
||||
numberi("y", y -> rules.limitY = y, () -> rules.limitY, () -> rules.limitMapArea, 0, 10000);
|
||||
numberi("w", w -> rules.limitWidth = w, () -> rules.limitWidth, () -> rules.limitMapArea, 0, 10000);
|
||||
numberi("h", h -> rules.limitHeight = h, () -> rules.limitHeight, () -> rules.limitMapArea, 0, 10000);
|
||||
|
||||
main.button("@rules.weather", this::weatherDialog).width(250f).left().row();
|
||||
number("@rules.solarmultiplier", f -> rules.solarMultiplier = f, () -> rules.solarMultiplier);
|
||||
|
||||
if(Core.bundle.get("rules.ambientlight").toLowerCase().contains(ruleSearch)){
|
||||
current.button(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).row();
|
||||
}
|
||||
|
||||
if(Core.bundle.get("rules.weather").toLowerCase().contains(ruleSearch)){
|
||||
current.button("@rules.weather", this::weatherDialog).width(250f).left().row();
|
||||
}
|
||||
|
||||
|
||||
category("planet");
|
||||
if(Core.bundle.get("rules.title.planet").toLowerCase().contains(ruleSearch)){
|
||||
current.table(Tex.button, t -> {
|
||||
t.margin(10f);
|
||||
var group = new ButtonGroup<>();
|
||||
var style = Styles.flatTogglet;
|
||||
|
||||
t.defaults().size(140f, 50f);
|
||||
|
||||
for(Planet planet : content.planets().select(p -> p.accessible && p.visible && p.isLandable())){
|
||||
t.button(planet.localizedName, style, () -> {
|
||||
planet.applyRules(rules, true);
|
||||
}).group(group).checked(b -> rules.planet == planet);
|
||||
|
||||
if(t.getChildren().size % 3 == 0){
|
||||
t.row();
|
||||
}
|
||||
}
|
||||
|
||||
t.button("@rules.anyenv", style, () -> {
|
||||
rules.env = Vars.defaultEnv;
|
||||
rules.planet = Planets.sun;
|
||||
}).group(group).checked(b -> rules.planet == Planets.sun);
|
||||
}).left().fill(false).expand(false, false).row();
|
||||
}
|
||||
|
||||
|
||||
category("teams");
|
||||
//not sure where else to put this
|
||||
if(showRuleEditRule){
|
||||
check("@rules.allowedit", b -> rules.allowEditRules = b, () -> rules.allowEditRules);
|
||||
}
|
||||
team("@rules.playerteam", t -> rules.defaultTeam = t, () -> rules.defaultTeam);
|
||||
team("@rules.enemyteam", t -> rules.waveTeam = t, () -> rules.waveTeam);
|
||||
|
||||
for(Team team : Team.baseTeams){
|
||||
boolean[] shown = {false};
|
||||
Table wasCurrent = current;
|
||||
|
||||
Table teamRules = new Table(); // just button and collapser in one table
|
||||
teamRules.button(team.coloredName(), Icon.downOpen, Styles.togglet, () -> {
|
||||
shown[0] = !shown[0];
|
||||
}).marginLeft(14f).width(260f).height(55f).update(t -> {
|
||||
((Image)t.getChildren().get(1)).setDrawable(shown[0] ? Icon.upOpen : Icon.downOpen);
|
||||
t.setChecked(shown[0]);
|
||||
}).left().padBottom(2f).row();
|
||||
|
||||
teamRules.collapser(c -> {
|
||||
c.left().defaults().fillX().left().pad(5);
|
||||
current = c;
|
||||
TeamRule teams = rules.teams.get(team);
|
||||
|
||||
number("@rules.blockhealthmultiplier", f -> teams.blockHealthMultiplier = f, () -> teams.blockHealthMultiplier);
|
||||
number("@rules.blockdamagemultiplier", f -> teams.blockDamageMultiplier = f, () -> teams.blockDamageMultiplier);
|
||||
|
||||
check("@rules.rtsai", b -> teams.rtsAi = b, () -> teams.rtsAi, () -> team != rules.defaultTeam);
|
||||
numberi("@rules.rtsminsquadsize", f -> teams.rtsMinSquad = f, () -> teams.rtsMinSquad, () -> teams.rtsAi, 0, 100);
|
||||
numberi("@rules.rtsmaxsquadsize", f -> teams.rtsMaxSquad = f, () -> teams.rtsMaxSquad, () -> teams.rtsAi, 1, 1000);
|
||||
number("@rules.rtsminattackweight", f -> teams.rtsMinWeight = f, () -> teams.rtsMinWeight, () -> teams.rtsAi);
|
||||
|
||||
//disallow on Erekir (this is broken for mods I'm sure, but whatever)
|
||||
check("@rules.buildai", b -> teams.buildAi = b, () -> teams.buildAi, () -> team != rules.defaultTeam && rules.env != Planets.erekir.defaultEnv && !rules.pvp);
|
||||
number("@rules.buildaitier", false, f -> teams.buildAiTier = f, () -> teams.buildAiTier, () -> teams.buildAi && rules.env != Planets.erekir.defaultEnv && !rules.pvp, 0, 1);
|
||||
|
||||
number("@rules.extracorebuildradius", f -> teams.extraCoreBuildRadius = f * tilesize, () -> Math.min(teams.extraCoreBuildRadius / tilesize, 200), () -> !rules.polygonCoreProtection);
|
||||
|
||||
check("@rules.infiniteresources", b -> teams.infiniteResources = b, () -> teams.infiniteResources);
|
||||
number("@rules.buildspeedmultiplier", f -> teams.buildSpeedMultiplier = f, () -> teams.buildSpeedMultiplier, 0.001f, 50f);
|
||||
|
||||
number("@rules.unitdamagemultiplier", f -> teams.unitDamageMultiplier = f, () -> teams.unitDamageMultiplier);
|
||||
number("@rules.unitcrashdamagemultiplier", f -> teams.unitCrashDamageMultiplier = f, () -> teams.unitCrashDamageMultiplier);
|
||||
number("@rules.unitminespeedmultiplier", f -> teams.unitMineSpeedMultiplier = f, () -> teams.unitMineSpeedMultiplier);
|
||||
number("@rules.unitbuildspeedmultiplier", f -> teams.unitBuildSpeedMultiplier = f, () -> teams.unitBuildSpeedMultiplier, 0.001f, 50f);
|
||||
number("@rules.unitcostmultiplier", f -> teams.unitCostMultiplier = f, () -> teams.unitCostMultiplier);
|
||||
number("@rules.unithealthmultiplier", f -> teams.unitHealthMultiplier = f, () -> teams.unitHealthMultiplier);
|
||||
|
||||
if(!current.hasChildren()){
|
||||
teamRules.clear();
|
||||
}else{
|
||||
wasCurrent.add(teamRules).row();
|
||||
}
|
||||
|
||||
current = wasCurrent;
|
||||
}, () -> shown[0]).left().growX().row();
|
||||
}
|
||||
|
||||
additionalSetup.each(Runnable::run);
|
||||
|
||||
for(var i = 0; i < categories.size; i++){
|
||||
addToMain(categories.get(i), Core.bundle.get("rules.title." + categoryNames.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
void number(String text, Floatc cons, Floatp prov){
|
||||
public void category(String name){
|
||||
current = new Table();
|
||||
current.left().defaults().fillX().left().pad(5);
|
||||
currentName = name;
|
||||
categories.add(current);
|
||||
categoryNames.add(currentName);
|
||||
}
|
||||
|
||||
void addToMain(Table category, String title){
|
||||
if(category.hasChildren()){
|
||||
main.add(title).color(Pal.accent).padTop(20).padRight(100f).padBottom(-3).fillX().left().pad(5).row();
|
||||
main.image().color(Pal.accent).height(3f).padRight(100f).padBottom(20).fillX().left().pad(5).row();
|
||||
main.add(category).row();
|
||||
}
|
||||
}
|
||||
|
||||
public void team(String text, Cons<Team> cons, Prov<Team> prov){
|
||||
if(!Core.bundle.get(text.substring(1)).toLowerCase().contains(ruleSearch)) return;
|
||||
current.table(t -> {
|
||||
t.left();
|
||||
t.add(text).left().padRight(5);
|
||||
|
||||
for(Team team : Team.baseTeams){
|
||||
t.button(Tex.whiteui, Styles.squareTogglei, 38f, () -> {
|
||||
cons.get(team);
|
||||
}).pad(1f).checked(b -> prov.get() == team).size(60f).tooltip(team.coloredName()).with(i -> i.getStyle().imageUpColor = team.color);
|
||||
}
|
||||
}).padTop(0).row();
|
||||
}
|
||||
|
||||
public void number(String text, Floatc cons, Floatp prov){
|
||||
number(text, false, cons, prov, () -> true, 0, Float.MAX_VALUE);
|
||||
}
|
||||
|
||||
void number(String text, Floatc cons, Floatp prov, float min, float max){
|
||||
public void number(String text, Floatc cons, Floatp prov, float min, float max){
|
||||
number(text, false, cons, prov, () -> true, min, max);
|
||||
}
|
||||
|
||||
void number(String text, boolean integer, Floatc cons, Floatp prov, Boolp condition){
|
||||
public void number(String text, boolean integer, Floatc cons, Floatp prov, Boolp condition){
|
||||
number(text, integer, cons, prov, condition, 0, Float.MAX_VALUE);
|
||||
}
|
||||
|
||||
void number(String text, Floatc cons, Floatp prov, Boolp condition){
|
||||
public void number(String text, Floatc cons, Floatp prov, Boolp condition){
|
||||
number(text, false, cons, prov, condition, 0, Float.MAX_VALUE);
|
||||
}
|
||||
|
||||
void number(String text, boolean integer, Floatc cons, Floatp prov, Boolp condition, float min, float max){
|
||||
main.table(t -> {
|
||||
public void numberi(String text, Intc cons, Intp prov, int min, int max){
|
||||
numberi(text, cons, prov, () -> true, min, max);
|
||||
}
|
||||
|
||||
public void numberi(String text, Intc cons, Intp prov, Boolp condition, int min, int max){
|
||||
if(!Core.bundle.get(text.substring(1)).toLowerCase().contains(ruleSearch)) return;
|
||||
var cell = current.table(t -> {
|
||||
t.left();
|
||||
t.add(text).left().padRight(5)
|
||||
.update(a -> a.setColor(condition.get() ? Color.white : Color.gray));
|
||||
t.field((prov.get()) + "", s -> cons.get(Strings.parseInt(s)))
|
||||
.update(a -> a.setDisabled(!condition.get()))
|
||||
.padRight(100f)
|
||||
.valid(f -> Strings.parseInt(f) >= min && Strings.parseInt(f) <= max).width(120f).left();
|
||||
}).padTop(0);
|
||||
ruleInfo(cell, text);
|
||||
current.row();
|
||||
}
|
||||
|
||||
public void number(String text, boolean integer, Floatc cons, Floatp prov, Boolp condition, float min, float max){
|
||||
if(!Core.bundle.get(text.substring(1)).toLowerCase().contains(ruleSearch)) return;
|
||||
var cell = current.table(t -> {
|
||||
t.left();
|
||||
t.add(text).left().padRight(5)
|
||||
.update(a -> a.setColor(condition.get() ? Color.white : Color.gray));
|
||||
t.field((integer ? (int)prov.get() : prov.get()) + "", s -> cons.get(Strings.parseFloat(s)))
|
||||
.padRight(100f)
|
||||
.update(a -> a.setDisabled(!condition.get()))
|
||||
.valid(f -> Strings.canParsePositiveFloat(f) && Strings.parseFloat(f) >= min && Strings.parseFloat(f) <= max).width(120f).left().addInputDialog();
|
||||
.valid(f -> Strings.canParsePositiveFloat(f) && Strings.parseFloat(f) >= min && Strings.parseFloat(f) <= max).width(120f).left();
|
||||
}).padTop(0);
|
||||
main.row();
|
||||
ruleInfo(cell, text);
|
||||
current.row();
|
||||
}
|
||||
|
||||
void check(String text, Boolc cons, Boolp prov){
|
||||
public void check(String text, Boolc cons, Boolp prov){
|
||||
check(text, cons, prov, () -> true);
|
||||
}
|
||||
|
||||
void check(String text, Boolc cons, Boolp prov, Boolp condition){
|
||||
main.check(text, cons).checked(prov.get()).update(a -> a.setDisabled(!condition.get())).padRight(100f).get().left();
|
||||
main.row();
|
||||
public void check(String text, Boolc cons, Boolp prov, Boolp condition){
|
||||
if(!Core.bundle.get(text.substring(1)).toLowerCase().contains(ruleSearch)) return;
|
||||
var cell = current.check(text, cons).checked(prov.get()).update(a -> a.setDisabled(!condition.get()));
|
||||
cell.get().left();
|
||||
ruleInfo(cell, text);
|
||||
current.row();
|
||||
}
|
||||
|
||||
void title(String text){
|
||||
main.add(text).color(Pal.accent).padTop(20).padRight(100f).padBottom(-3);
|
||||
main.row();
|
||||
main.image().color(Pal.accent).height(3f).padRight(100f).padBottom(20);
|
||||
main.row();
|
||||
public void ruleInfo(Cell<?> cell, String text){
|
||||
if(Core.bundle.has(text.substring(1) + ".info")){
|
||||
if(mobile){
|
||||
Table table = new Table();
|
||||
table.add(cell.get()).left().expandX().fillX();
|
||||
cell.clearElement();
|
||||
table.button(Icon.infoSmall, () -> ui.showInfo(text + ".info")).size(32f).padRight(24f).right();
|
||||
cell.setElement(table).left().expandX().fillX();
|
||||
}else{
|
||||
cell.tooltip(text + ".info");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cell<TextField> field(Table table, float value, Floatc setter){
|
||||
return table.field(Strings.autoFixed(value, 2), v -> setter.get(Strings.parseFloat(v)))
|
||||
.valid(Strings::canParsePositiveFloat)
|
||||
.size(90f, 40f).pad(2f).addInputDialog();
|
||||
.size(90f, 40f).pad(2f);
|
||||
}
|
||||
|
||||
void weatherDialog(){
|
||||
@@ -247,7 +451,7 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
|
||||
rebuild[0] = () -> {
|
||||
base.clearChildren();
|
||||
int cols = Math.max(1, Core.graphics.getWidth() / 460);
|
||||
int cols = Math.max(1, (int)(Core.graphics.getWidth() / Scl.scl(450)));
|
||||
int idx = 0;
|
||||
|
||||
for(WeatherEntry entry : rules.weather){
|
||||
@@ -323,8 +527,9 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
t.background(Tex.button);
|
||||
int i = 0;
|
||||
for(Weather weather : content.<Weather>getBy(ContentType.weather)){
|
||||
if(weather.hidden) continue;
|
||||
|
||||
t.button(weather.localizedName, Styles.cleart, () -> {
|
||||
t.button(weather.localizedName, Styles.flatt, () -> {
|
||||
rules.weather.add(new WeatherEntry(weather));
|
||||
rebuild[0].run();
|
||||
|
||||
@@ -337,19 +542,6 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
add.show();
|
||||
}).width(170f);
|
||||
|
||||
//reset cooldown to random number
|
||||
dialog.hidden(() -> {
|
||||
float sum = 0;
|
||||
Seq<WeatherEntry> sh = rules.weather.copy();
|
||||
sh.shuffle();
|
||||
|
||||
for(WeatherEntry w : sh){
|
||||
//add the previous cooldowns to the sum so weather events are staggered and don't happen all at once.
|
||||
w.cooldown = sum + Mathf.random(w.minFrequency, w.maxFrequency);
|
||||
sum += w.cooldown;
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,64 +5,137 @@ import arc.graphics.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class DatabaseDialog extends BaseDialog{
|
||||
private TextField search;
|
||||
private Table all = new Table();
|
||||
|
||||
private @Nullable Seq<UnlockableContent> allTabs;
|
||||
//sun means "all content"
|
||||
private UnlockableContent tab = Planets.sun;
|
||||
|
||||
public DatabaseDialog(){
|
||||
super("@database");
|
||||
|
||||
shouldPause = true;
|
||||
addCloseButton();
|
||||
shown(this::rebuild);
|
||||
shown(() -> {
|
||||
checkTabList();
|
||||
if(state.isCampaign() && allTabs.contains(state.getPlanet())){
|
||||
tab = state.getPlanet();
|
||||
}else if(state.isGame() && state.rules.planet != null && allTabs.contains(state.rules.planet)){
|
||||
tab = state.rules.planet;
|
||||
}
|
||||
|
||||
rebuild();
|
||||
});
|
||||
onResize(this::rebuild);
|
||||
|
||||
all.margin(20).marginTop(0f).marginRight(30f);
|
||||
|
||||
cont.top();
|
||||
cont.table(s -> {
|
||||
s.image(Icon.zoom).padRight(8);
|
||||
search = s.field(null, text -> rebuild()).growX().get();
|
||||
search.setMessageText("@players.search");
|
||||
}).fillX().padBottom(4).row();
|
||||
|
||||
cont.pane(all).scrollX(false);
|
||||
}
|
||||
|
||||
void checkTabList(){
|
||||
if(allTabs == null){
|
||||
Seq<Content>[] allContent = Vars.content.getContentMap();
|
||||
ObjectSet<UnlockableContent> all = new ObjectSet<>();
|
||||
for(var contents : allContent){
|
||||
for(var content : contents){
|
||||
if(content instanceof UnlockableContent u){
|
||||
all.addAll(u.databaseTabs);
|
||||
}
|
||||
}
|
||||
}
|
||||
allTabs = all.toSeq().sort();
|
||||
allTabs.insert(0, Planets.sun);
|
||||
}
|
||||
}
|
||||
|
||||
void rebuild(){
|
||||
cont.clear();
|
||||
checkTabList();
|
||||
|
||||
Table table = new Table();
|
||||
table.margin(20);
|
||||
ScrollPane pane = new ScrollPane(table);
|
||||
all.clear();
|
||||
var text = search.getText().toLowerCase();
|
||||
|
||||
Seq<Content>[] allContent = Vars.content.getContentMap();
|
||||
|
||||
all.table(t -> {
|
||||
int i = 0;
|
||||
for(var content : allTabs){
|
||||
t.button(content == Planets.sun ? Icon.eyeSmall : content instanceof Planet p ? Icon.icons.get(p.icon, Icon.commandRally) : new TextureRegionDrawable(content.uiIcon), Styles.clearNoneTogglei, iconMed, () -> {
|
||||
tab = content;
|
||||
rebuild();
|
||||
}).size(50f).checked(b -> tab == content).tooltip(content == Planets.sun ? "@all" : content.localizedName).with(but -> {
|
||||
but.getStyle().imageUpColor = content instanceof Planet p ? p.iconColor : Color.white.cpy();
|
||||
});
|
||||
|
||||
if(++i % 10 == 0) t.row();
|
||||
}
|
||||
}).row();;
|
||||
|
||||
for(int j = 0; j < allContent.length; j++){
|
||||
ContentType type = ContentType.all[j];
|
||||
|
||||
Seq<Content> array = allContent[j].select(c -> c instanceof UnlockableContent u && (!u.isHidden() || u.node() != null));
|
||||
Seq<UnlockableContent> array = allContent[j]
|
||||
.select(c -> c instanceof UnlockableContent u && !u.isHidden() && !u.hideDatabase && (tab == Planets.sun || u.allDatabaseTabs || u.databaseTabs.contains(tab)) &&
|
||||
(text.isEmpty() || u.localizedName.toLowerCase().contains(text))).as();
|
||||
|
||||
if(array.size == 0) continue;
|
||||
|
||||
table.add("@content." + type.name() + ".name").growX().left().color(Pal.accent);
|
||||
table.row();
|
||||
table.image().growX().pad(5).padLeft(0).padRight(0).height(3).color(Pal.accent);
|
||||
table.row();
|
||||
table.table(list -> {
|
||||
all.add("@content." + type.name() + ".name").growX().left().color(Pal.accent);
|
||||
all.row();
|
||||
all.image().growX().pad(5).padLeft(0).padRight(0).height(3).color(Pal.accent);
|
||||
all.row();
|
||||
all.table(list -> {
|
||||
list.left();
|
||||
|
||||
int cols = Mathf.clamp((Core.graphics.getWidth() - 30) / (32 + 10), 1, 18);
|
||||
int cols = (int)Mathf.clamp((Core.graphics.getWidth() - Scl.scl(30)) / Scl.scl(32 + 12), 1, 22);
|
||||
int count = 0;
|
||||
|
||||
for(int i = 0; i < array.size; i++){
|
||||
UnlockableContent unlock = (UnlockableContent)array.get(i);
|
||||
UnlockableContent unlock = array.get(i);
|
||||
|
||||
Image image = unlocked(unlock) ? new Image(unlock.uiIcon).setScaling(Scaling.fit) : new Image(Icon.lock, Pal.gray);
|
||||
|
||||
//banned cross
|
||||
if(state.isGame() && (unlock instanceof UnitType u && u.isBanned() || unlock instanceof Block b && state.rules.isBanned(b))){
|
||||
list.stack(image, new Image(Icon.cancel){{
|
||||
setColor(Color.scarlet);
|
||||
touchable = Touchable.disabled;
|
||||
}}).size(8 * 4).pad(3);
|
||||
}else{
|
||||
list.add(image).size(8 * 4).pad(3);
|
||||
}
|
||||
|
||||
Image image = unlocked(unlock) ? new Image(unlock.icon(Cicon.medium)).setScaling(Scaling.fit) : new Image(Icon.lock, Pal.gray);
|
||||
list.add(image).size(8 * 4).pad(3);
|
||||
ClickListener listener = new ClickListener();
|
||||
image.addListener(listener);
|
||||
if(!Vars.mobile && unlocked(unlock)){
|
||||
if(!mobile && unlocked(unlock)){
|
||||
image.addListener(new HandCursorListener());
|
||||
image.update(() -> image.color.lerp(!listener.isOver() ? Color.lightGray : Color.white, 0.4f * Time.delta));
|
||||
image.update(() -> image.color.lerp(!listener.isOver() ? Color.lightGray : Color.white, Mathf.clamp(0.4f * Time.delta)));
|
||||
}
|
||||
|
||||
if(unlocked(unlock)){
|
||||
@@ -71,10 +144,10 @@ public class DatabaseDialog extends BaseDialog{
|
||||
Core.app.setClipboardText((char)Fonts.getUnicode(unlock.name) + "");
|
||||
ui.showInfoFade("@copied");
|
||||
}else{
|
||||
Vars.ui.content.show(unlock);
|
||||
ui.content.show(unlock);
|
||||
}
|
||||
});
|
||||
image.addListener(new Tooltip(t -> t.background(Tex.button).add(unlock.localizedName)));
|
||||
image.addListener(new Tooltip(t -> t.background(Tex.button).add(unlock.localizedName + (settings.getBool("console") ? "\n[gray]" + unlock.name : ""))));
|
||||
}
|
||||
|
||||
if((++count) % cols == 0){
|
||||
@@ -82,10 +155,12 @@ public class DatabaseDialog extends BaseDialog{
|
||||
}
|
||||
}
|
||||
}).growX().left().padBottom(10);
|
||||
table.row();
|
||||
all.row();
|
||||
}
|
||||
|
||||
cont.add(pane);
|
||||
if(all.getChildren().isEmpty()){
|
||||
all.add("@none.found");
|
||||
}
|
||||
}
|
||||
|
||||
boolean unlocked(UnlockableContent content){
|
||||
|
||||
@@ -29,20 +29,20 @@ public class DiscordDialog extends Dialog{
|
||||
}).expandY();
|
||||
|
||||
t.table(i -> {
|
||||
i.background(Tex.button);
|
||||
i.image(Icon.discord);
|
||||
}).size(h).left();
|
||||
|
||||
t.add("@discord").color(Pal.accent).growX().padLeft(10f);
|
||||
}).size(440f, h).pad(10f);
|
||||
}).size(520f, h).pad(10f);
|
||||
|
||||
buttons.defaults().size(150f, 50);
|
||||
buttons.defaults().size(170f, 50);
|
||||
|
||||
buttons.button("@back", this::hide);
|
||||
buttons.button("@copylink", () -> {
|
||||
buttons.button("@back", Icon.left, this::hide);
|
||||
buttons.button("@copylink", Icon.copy, () -> {
|
||||
Core.app.setClipboardText(discordURL);
|
||||
ui.showInfoFade("@copied");
|
||||
});
|
||||
buttons.button("@openlink", () -> {
|
||||
buttons.button("@openlink", Icon.discord, () -> {
|
||||
if(!Core.app.openURI(discordURL)){
|
||||
ui.showErrorMessage("@linkfail");
|
||||
Core.app.setClipboardText(discordURL);
|
||||
|
||||
@@ -8,48 +8,26 @@ 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 BaseDialog{
|
||||
private BaseDialog dialog;
|
||||
public class EditorMapsDialog extends MapListDialog{
|
||||
|
||||
public MapsDialog(){
|
||||
super("@maps");
|
||||
|
||||
buttons.remove();
|
||||
|
||||
addCloseListener();
|
||||
|
||||
shown(this::setup);
|
||||
onResize(() -> {
|
||||
if(dialog != null){
|
||||
dialog.hide();
|
||||
}
|
||||
setup();
|
||||
});
|
||||
public EditorMapsDialog(){
|
||||
super("@maps", true);
|
||||
}
|
||||
|
||||
void setup(){
|
||||
buttons.clearChildren();
|
||||
|
||||
if(Core.graphics.isPortrait()){
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f*2f, 64f).colspan(2);
|
||||
buttons.row();
|
||||
}else{
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
|
||||
}
|
||||
|
||||
@Override
|
||||
void buildButtons(){
|
||||
buttons.button("@editor.newmap", Icon.add, () -> {
|
||||
ui.showTextInput("@editor.newmap", "@editor.mapname", "", text -> {
|
||||
Runnable show = () -> ui.loadAnd(() -> {
|
||||
hide();
|
||||
ui.editor.show();
|
||||
ui.editor.editor.tags.put("name", text);
|
||||
editor.tags.put("name", text);
|
||||
Events.fire(new MapMakeEvent());
|
||||
});
|
||||
|
||||
@@ -72,12 +50,11 @@ public class MapsDialog extends BaseDialog{
|
||||
|
||||
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.get("name", () -> {
|
||||
String result = "unknown";
|
||||
int number = 0;
|
||||
while(maps.byName(result + number++) != null);
|
||||
while(maps.byName(result + number++) != null) ;
|
||||
return result + number;
|
||||
});
|
||||
|
||||
@@ -108,51 +85,11 @@ public class MapsDialog extends BaseDialog{
|
||||
});
|
||||
});
|
||||
}).size(210f, 64f);
|
||||
|
||||
|
||||
cont.clear();
|
||||
|
||||
Table maps = new Table();
|
||||
maps.marginRight(24);
|
||||
|
||||
ScrollPane pane = new ScrollPane(maps);
|
||||
pane.setFadeScrollBars(false);
|
||||
|
||||
int maxwidth = Math.max((int)(Core.graphics.getWidth() / Scl.scl(230)), 1);
|
||||
float mapsize = 200f;
|
||||
|
||||
int i = 0;
|
||||
for(Map map : Vars.maps.all()){
|
||||
|
||||
if(i % maxwidth == 0){
|
||||
maps.row();
|
||||
}
|
||||
|
||||
TextButton button = maps.button("", 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.image().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 BaseDialog("@editor.mapinfo");
|
||||
@Override
|
||||
void showMap(Map map){
|
||||
BaseDialog dialog = activeDialog = new BaseDialog("@editor.mapinfo");
|
||||
dialog.addCloseButton();
|
||||
|
||||
float mapsize = Core.graphics.isPortrait() ? 160f : 300f;
|
||||
@@ -195,7 +132,7 @@ public class MapsDialog extends BaseDialog{
|
||||
dialog.hide();
|
||||
hide();
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
Log.err(e);
|
||||
ui.showErrorMessage("@error.mapnotfound");
|
||||
}
|
||||
}).fillX().height(54f).marginLeft(10);
|
||||
@@ -214,4 +151,5 @@ public class MapsDialog extends BaseDialog{
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
}
|
||||
235
core/src/mindustry/ui/dialogs/EffectsDialog.java
Normal file
235
core/src/mindustry/ui/dialogs/EffectsDialog.java
Normal file
@@ -0,0 +1,235 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.logic.LogicFx.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
public class EffectsDialog extends BaseDialog{
|
||||
static BoundsBatch bounds = new BoundsBatch();
|
||||
|
||||
Iterable<EffectEntry> entries;
|
||||
@Nullable Cons<EffectEntry> listener;
|
||||
|
||||
public EffectsDialog(Iterable<EffectEntry> entries){
|
||||
super("Effects");
|
||||
|
||||
this.entries = entries;
|
||||
|
||||
addCloseButton();
|
||||
makeButtonOverlay();
|
||||
onResize(this::setup);
|
||||
shown(this::setup);
|
||||
|
||||
setup();
|
||||
}
|
||||
|
||||
public EffectsDialog(){
|
||||
this(LogicFx.entries());
|
||||
}
|
||||
|
||||
public static EffectsDialog withAllEffects(){
|
||||
return new EffectsDialog(Seq.select(Fx.class.getFields(), f -> f.getType() == Effect.class).map(f -> new EffectEntry(Reflect.get(f)).name(f.getName())));
|
||||
}
|
||||
|
||||
public Dialog show(Cons<EffectEntry> listener){
|
||||
this.listener = listener;
|
||||
return super.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog show(){
|
||||
this.listener = null;
|
||||
return super.show();
|
||||
}
|
||||
|
||||
void setup(){
|
||||
float size = 280f;
|
||||
int cols = (int)Math.max(1, Core.graphics.getWidth() / Scl.scl(size + 12f));
|
||||
|
||||
cont.clearChildren();
|
||||
cont.pane(t -> {
|
||||
int i = 0;
|
||||
for(var entry : entries){
|
||||
float bounds = calculateSize(entry);
|
||||
|
||||
if(bounds <= 0) continue;
|
||||
|
||||
ClickListener cl = new ClickListener();
|
||||
|
||||
t.stack(
|
||||
new EffectCell(entry, cl),
|
||||
new Table(af -> af.add(entry.name).grow().labelAlign(Align.bottomLeft).style(Styles.outlineLabel).bottom().left())
|
||||
).size(size).with(a -> {
|
||||
a.clicked(() -> {
|
||||
if(listener != null){
|
||||
listener.get(entry);
|
||||
hide();
|
||||
}
|
||||
});
|
||||
a.addListener(cl);
|
||||
a.addListener(new HandCursorListener(() -> listener != null, true));
|
||||
});
|
||||
|
||||
if(++i % cols == 0) t.row();
|
||||
}
|
||||
}).grow().scrollX(false);
|
||||
}
|
||||
|
||||
static Object getData(Class<?> type){
|
||||
if(type == Block.class) return Blocks.router;
|
||||
return null;
|
||||
}
|
||||
|
||||
static float calculateSize(EffectEntry entry){
|
||||
if(entry.bounds >= 0) return entry.bounds;
|
||||
|
||||
|
||||
var effect = entry.effect;
|
||||
try{
|
||||
effect.init();
|
||||
Batch prev = Core.batch;
|
||||
bounds.reset();
|
||||
Core.batch = bounds;
|
||||
Object data = getData(entry.data);
|
||||
|
||||
float lifetime = effect.lifetime;
|
||||
float rot = 1f;
|
||||
int steps = 60;
|
||||
int seeds = 4;
|
||||
for(int s = 0; s < seeds; s++){
|
||||
for(int i = 0; i <= steps; i++){
|
||||
effect.render(1, Color.white, i / (float)steps * lifetime, lifetime, rot, 0f, 0f, data);
|
||||
}
|
||||
}
|
||||
|
||||
Core.batch = prev;
|
||||
|
||||
return entry.bounds = bounds.max * 2f;
|
||||
}catch(Exception e){
|
||||
//might crash with invalid data
|
||||
return -1f;
|
||||
}
|
||||
}
|
||||
|
||||
static class BoundsBatch extends Batch{
|
||||
float max;
|
||||
|
||||
void reset(){
|
||||
max = 0f;
|
||||
}
|
||||
|
||||
void max(float... xs){
|
||||
for(float f : xs){
|
||||
if(Float.isNaN(f)) continue;
|
||||
max = Math.max(max, Math.abs(f));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void draw(Texture texture, float[] spriteVertices, int offset, int count){
|
||||
for(int i = offset; i < count; i += SpriteBatch.VERTEX_SIZE){
|
||||
max(spriteVertices[i], spriteVertices[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float rotation){
|
||||
float worldOriginX = x + originX;
|
||||
float worldOriginY = y + originY;
|
||||
float fx = -originX;
|
||||
float fy = -originY;
|
||||
float fx2 = width - originX;
|
||||
float fy2 = height - originY;
|
||||
float cos = Mathf.cosDeg(rotation);
|
||||
float sin = Mathf.sinDeg(rotation);
|
||||
float x1 = cos * fx - sin * fy + worldOriginX;
|
||||
float y1 = sin * fx + cos * fy + worldOriginY;
|
||||
float x2 = cos * fx - sin * fy2 + worldOriginX;
|
||||
float y2 = sin * fx + cos * fy2 + worldOriginY;
|
||||
float x3 = cos * fx2 - sin * fy2 + worldOriginX;
|
||||
float y3 = sin * fx2 + cos * fy2 + worldOriginY;
|
||||
|
||||
max(x1, y1, x2, y2, x3, y3, x1 + (x3 - x2), y3 - (y2 - y1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void flush(){}
|
||||
}
|
||||
|
||||
class EffectCell extends Element{
|
||||
EffectEntry effect;
|
||||
float size = -1f;
|
||||
|
||||
int id = 1;
|
||||
float time = 0f;
|
||||
float lifetime;
|
||||
float rotation = 1f;
|
||||
Object data;
|
||||
ClickListener cl;
|
||||
|
||||
public EffectCell(EffectEntry effect, ClickListener cl){
|
||||
this.effect = effect;
|
||||
this.lifetime = effect.effect.lifetime;
|
||||
this.cl = cl;
|
||||
|
||||
data = getData(effect.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
if(size < 0){
|
||||
size = calculateSize(effect) + 1f;
|
||||
}
|
||||
|
||||
color.fromHsv((Time.globalTime * 2f) % 360f, 1f, 1f);
|
||||
|
||||
if(clipBegin(x, y, width, height)){
|
||||
Draw.colorl(cl.isOver() && listener != null ? 0.4f : 0.5f);
|
||||
Draw.alpha(parentAlpha);
|
||||
Tex.alphaBg.draw(x, y, width, height);
|
||||
Draw.reset();
|
||||
Draw.flush();
|
||||
|
||||
float scale = width / size;
|
||||
Tmp.m1.set(Draw.trans());
|
||||
Draw.trans().translate(x + width/2f, y + height/2f).scale(scale, scale);
|
||||
Draw.flush();
|
||||
this.lifetime = effect.effect.render(id, color, time, lifetime, rotation, 0f, 0f, data);
|
||||
|
||||
Draw.flush();
|
||||
Draw.trans().set(Tmp.m1);
|
||||
clipEnd();
|
||||
}
|
||||
|
||||
Lines.stroke(Scl.scl(3f), Color.black);
|
||||
Lines.rect(x, y, width, height);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void act(float delta){
|
||||
super.act(delta);
|
||||
|
||||
time += Time.delta;
|
||||
if(time >= lifetime){
|
||||
id ++;
|
||||
}
|
||||
time %= lifetime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@ public class FileChooser extends BaseDialog{
|
||||
private static final Fi homeDirectory = Core.files.absolute(Core.files.getExternalStoragePath());
|
||||
static Fi lastDirectory = Core.files.absolute(Core.settings.getString("lastDirectory", homeDirectory.absolutePath()));
|
||||
|
||||
Fi directory;
|
||||
private Table files;
|
||||
Fi directory = lastDirectory;
|
||||
private ScrollPane pane;
|
||||
private TextField navigation, filefield;
|
||||
private TextButton ok;
|
||||
@@ -37,6 +37,8 @@ public class FileChooser extends BaseDialog{
|
||||
this.filter = filter;
|
||||
this.selectListener = result;
|
||||
|
||||
directory = getLastDirectory();
|
||||
|
||||
onResize(() -> {
|
||||
cont.clear();
|
||||
setupWidgets();
|
||||
@@ -191,7 +193,7 @@ public class FileChooser extends BaseDialog{
|
||||
Fi[] names = getFileNames();
|
||||
|
||||
Image upimage = new Image(Icon.upOpen);
|
||||
TextButton upbutton = new TextButton(".." + directory.toString(), Styles.clearTogglet);
|
||||
TextButton upbutton = new TextButton(".." + directory.toString(), Styles.flatTogglet);
|
||||
upbutton.clicked(() -> {
|
||||
directory = directory.parent();
|
||||
setLastDirectory(directory);
|
||||
@@ -213,7 +215,7 @@ public class FileChooser extends BaseDialog{
|
||||
|
||||
String filename = file.name();
|
||||
|
||||
TextButton button = new TextButton(filename, Styles.clearTogglet);
|
||||
TextButton button = new TextButton(filename.replace("[", "[["), Styles.flatTogglet);
|
||||
button.getLabel().setWrap(false);
|
||||
button.getLabel().setEllipsis(true);
|
||||
group.add(button);
|
||||
@@ -249,18 +251,16 @@ public class FileChooser extends BaseDialog{
|
||||
if(open) filefield.clearText();
|
||||
}
|
||||
|
||||
public static void setLastDirectory(Fi directory){
|
||||
public static synchronized void setLastDirectory(Fi directory){
|
||||
lastDirectory = directory;
|
||||
Core.settings.put("lastDirectory", directory.absolutePath());
|
||||
}
|
||||
|
||||
private String shorten(String string){
|
||||
int max = 30;
|
||||
if(string.length() <= max){
|
||||
return string;
|
||||
}else{
|
||||
return string.substring(0, max - 3).concat("...");
|
||||
public static synchronized Fi getLastDirectory(){
|
||||
if(!lastDirectory.exists()){
|
||||
lastDirectory = homeDirectory;
|
||||
}
|
||||
return lastDirectory;
|
||||
}
|
||||
|
||||
public class FileHistory{
|
||||
@@ -300,19 +300,5 @@ public class FileChooser extends BaseDialog{
|
||||
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() + "--");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
21
core/src/mindustry/ui/dialogs/FullTextDialog.java
Normal file
21
core/src/mindustry/ui/dialogs/FullTextDialog.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.util.*;
|
||||
|
||||
public class FullTextDialog extends BaseDialog{
|
||||
|
||||
public FullTextDialog(){
|
||||
super("");
|
||||
shouldPause = true;
|
||||
addCloseButton();
|
||||
}
|
||||
|
||||
public void show(String titleText, String text){
|
||||
title.setText(titleText);
|
||||
cont.clear();
|
||||
cont.add(text).grow().wrap().labelAlign(Align.center);
|
||||
|
||||
super.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +1,38 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.flabel.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.actions.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class GameOverDialog extends BaseDialog{
|
||||
private Team winner;
|
||||
private boolean hudShown;
|
||||
|
||||
public GameOverDialog(){
|
||||
super("@gameover");
|
||||
setFillParent(true);
|
||||
shown(this::rebuild);
|
||||
|
||||
titleTable.remove();
|
||||
|
||||
shown(() -> {
|
||||
hudShown = ui.hudfrag.shown;
|
||||
ui.hudfrag.shown = false;
|
||||
rebuild();
|
||||
});
|
||||
|
||||
hidden(() -> ui.hudfrag.shown = hudShown);
|
||||
|
||||
Events.on(ResetEvent.class, e -> hide());
|
||||
}
|
||||
@@ -31,73 +48,119 @@ public class GameOverDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
void rebuild(){
|
||||
title.setText(state.isCampaign() ? "@sector.curlost" : "@gameover");
|
||||
buttons.clear();
|
||||
cont.clear();
|
||||
|
||||
buttons.margin(10);
|
||||
|
||||
if(state.rules.pvp && winner != null){
|
||||
cont.add(Core.bundle.format("gameover.pvp", winner.localized())).pad(6);
|
||||
cont.table(t -> {
|
||||
if(state.rules.pvp && winner != null){
|
||||
t.add(Core.bundle.format("gameover.pvp", winner.coloredName())).center().pad(6);
|
||||
}else{
|
||||
t.add(state.isCampaign() ? Core.bundle.format("sector.lost", state.getSector().name()) : "@gameover").center().pad(6);
|
||||
}
|
||||
t.row();
|
||||
|
||||
if(control.isHighScore()){
|
||||
t.add("@highscore").pad(6);
|
||||
t.row();
|
||||
}
|
||||
|
||||
t.pane(p -> {
|
||||
p.margin(13f);
|
||||
p.left().defaults().left();
|
||||
p.setBackground(Styles.black3);
|
||||
|
||||
p.table(stats -> {
|
||||
if(state.rules.waves) addStat(stats, Core.bundle.get("stats.wave"), state.stats.wavesLasted, 0f);
|
||||
addStat(stats, Core.bundle.get("stats.unitsCreated"), state.stats.unitsCreated, 0.05f);
|
||||
addStat(stats, Core.bundle.get("stats.enemiesDestroyed"), state.stats.enemyUnitsDestroyed, 0.1f);
|
||||
addStat(stats, Core.bundle.get("stats.built"), state.stats.buildingsBuilt, 0.15f);
|
||||
addStat(stats, Core.bundle.get("stats.destroyed"), state.stats.buildingsDestroyed, 0.2f);
|
||||
addStat(stats, Core.bundle.get("stats.deconstructed"), state.stats.buildingsDeconstructed, 0.25f);
|
||||
}).top().grow().row();
|
||||
|
||||
if(control.saves.getCurrent() != null){
|
||||
p.table(pt -> {
|
||||
pt.add(new FLabel(Core.bundle.get("stats.playtime"))).left().pad(5).growX();
|
||||
pt.add(new FLabel("[accent]" + control.saves.getCurrent().getPlayTime())).right().pad(5);
|
||||
}).growX();
|
||||
}
|
||||
}).grow().pad(12).top();
|
||||
}).center().minWidth(370).maxSize(600, 550).grow();
|
||||
|
||||
if(state.isCampaign() && net.client()){
|
||||
cont.row();
|
||||
cont.add("@gameover.waiting").padTop(20f).row();
|
||||
}
|
||||
|
||||
if(state.isCampaign()){
|
||||
if(net.client()){
|
||||
buttons.button("@gameover.disconnect", () -> {
|
||||
logic.reset();
|
||||
net.reset();
|
||||
hide();
|
||||
state.set(State.menu);
|
||||
}).size(170f, 60f);
|
||||
}else{
|
||||
buttons.button("@continue", () -> {
|
||||
hide();
|
||||
ui.planet.show();
|
||||
}).size(170f, 60f);
|
||||
}
|
||||
}else{
|
||||
buttons.button("@menu", () -> {
|
||||
hide();
|
||||
logic.reset();
|
||||
}).size(130f, 60f);
|
||||
}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)).row();
|
||||
t.add(Core.bundle.format("stat.enemiesDestroyed", state.stats.enemyUnitsDestroyed)).row();
|
||||
t.add(Core.bundle.format("stat.built", state.stats.buildingsBuilt)).row();
|
||||
t.add(Core.bundle.format("stat.destroyed", state.stats.buildingsDestroyed)).row();
|
||||
t.add(Core.bundle.format("stat.deconstructed", state.stats.buildingsDeconstructed)).row();
|
||||
if(control.saves.getCurrent() != null){
|
||||
t.add(Core.bundle.format("stat.playtime", control.saves.getCurrent().getPlayTime())).row();
|
||||
}
|
||||
if(state.isCampaign() && !state.stats.itemsDelivered.isEmpty()){
|
||||
t.add("@stat.delivered").row();
|
||||
for(Item item : content.items()){
|
||||
if(state.stats.itemsDelivered.get(item, 0) > 0){
|
||||
t.table(items -> {
|
||||
items.add(" [lightgray]" + state.stats.itemsDelivered.get(item, 0));
|
||||
items.image(item.icon(Cicon.small)).size(8 * 3).pad(4);
|
||||
}).left().row();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(state.isCampaign() && net.client()){
|
||||
t.add("@gameover.waiting").padTop(20f).row();
|
||||
}
|
||||
|
||||
}).pad(12);
|
||||
|
||||
if(state.isCampaign()){
|
||||
if(net.client()){
|
||||
buttons.button("@gameover.disconnect", () -> {
|
||||
logic.reset();
|
||||
net.reset();
|
||||
hide();
|
||||
state.set(State.menu);
|
||||
}).size(170f, 60f);
|
||||
}else{
|
||||
buttons.button("@continue", () -> {
|
||||
hide();
|
||||
ui.planet.show();
|
||||
}).size(170f, 60f);
|
||||
}
|
||||
}else{
|
||||
buttons.button("@menu", () -> {
|
||||
hide();
|
||||
if(!ui.paused.checkPlaytest()){
|
||||
logic.reset();
|
||||
}).size(140f, 60f);
|
||||
}
|
||||
}
|
||||
}).size(140f, 60f);
|
||||
}
|
||||
}
|
||||
|
||||
private void addStat(Table parent, String stat, int value, float delay){
|
||||
parent.add(new StatLabel(stat, value, delay)).top().pad(5).growX().height(50).row();
|
||||
}
|
||||
|
||||
private static class StatLabel extends Table {
|
||||
private float progress = 0;
|
||||
|
||||
public StatLabel(String stat, int value, float delay){
|
||||
setTransform(true);
|
||||
setClip(true);
|
||||
setBackground(Tex.whiteui);
|
||||
setColor(Pal.accent);
|
||||
margin(2f);
|
||||
|
||||
FLabel statLabel = new FLabel(stat);
|
||||
statLabel.setStyle(Styles.outlineLabel);
|
||||
statLabel.setWrap(true);
|
||||
statLabel.pause();
|
||||
|
||||
Label valueLabel = new Label("", Styles.outlineLabel);
|
||||
valueLabel.setAlignment(Align.right);
|
||||
|
||||
add(statLabel).left().growX().padLeft(5);
|
||||
add(valueLabel).right().growX().padRight(5);
|
||||
|
||||
actions(
|
||||
Actions.scaleTo(0, 1),
|
||||
Actions.delay(delay),
|
||||
Actions.parallel(
|
||||
Actions.scaleTo(1, 1, 0.3f, Interp.pow3Out),
|
||||
Actions.color(Pal.darkestGray, 0.3f, Interp.pow3Out),
|
||||
Actions.sequence(
|
||||
Actions.delay(0.3f),
|
||||
Actions.run(() -> {
|
||||
valueLabel.update(() -> {
|
||||
progress = Math.min(1, progress + (Time.delta / 60));
|
||||
valueLabel.setText("" + (int)Mathf.lerp(0, value, value < 10 ? progress : Interp.slowFast.apply(progress)));
|
||||
});
|
||||
statLabel.resume();
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import arc.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.util.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -28,15 +29,24 @@ public class HostDialog extends BaseDialog{
|
||||
ui.listfrag.rebuild();
|
||||
}).grow().pad(8).get().setMaxLength(40);
|
||||
|
||||
ImageButton button = t.button(Tex.whiteui, Styles.clearFulli, 40, () -> {
|
||||
ImageButton button = t.button(Tex.whiteui, Styles.squarei, 40, () -> {
|
||||
new PaletteDialog().show(color -> {
|
||||
player.color().set(color);
|
||||
player.color.set(color);
|
||||
Core.settings.put("color-0", color.rgba());
|
||||
});
|
||||
}).size(54f).get();
|
||||
button.update(() -> button.getStyle().imageUpColor = player.color());
|
||||
}).width(w).height(70f).pad(4).colspan(3);
|
||||
|
||||
if(steam){
|
||||
cont.row();
|
||||
|
||||
cont.add().width(65f);
|
||||
|
||||
cont.check("@steam.friendsonly", !Core.settings.getBool("steampublichost"), val -> Core.settings.put("steampublichost", !val)).colspan(2).left()
|
||||
.with(c -> ui.addDescTooltip(c, "@steam.friendsonly.tooltip")).padBottom(15f).row();
|
||||
}
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.table(t -> {
|
||||
@@ -68,7 +78,11 @@ public class HostDialog extends BaseDialog{
|
||||
runHost();
|
||||
}).width(w).height(70f).disabled(b -> !portField.isValid());
|
||||
|
||||
cont.button("?", () -> ui.showInfo("@host.info")).size(65f, 70f).padLeft(6f);
|
||||
if(!steam){
|
||||
cont.button("?", () -> ui.showInfo("@host.info")).size(65f, 70f).padLeft(6f);
|
||||
}else{
|
||||
cont.add().size(65f, 70f).padLeft(6f);
|
||||
}
|
||||
|
||||
shown(() -> {
|
||||
if(!steam){
|
||||
@@ -82,34 +96,20 @@ public class HostDialog extends BaseDialog{
|
||||
Time.runTask(5f, () -> {
|
||||
try{
|
||||
net.host(Core.settings.getInt("port", port));
|
||||
player.admin(true);
|
||||
|
||||
if(steam){
|
||||
Core.app.post(() -> Core.settings.getBoolOnce("steampublic3", () -> {
|
||||
ui.showCustomConfirm("@setting.publichost.name", "@public.confirm", "@yes", "@no", () -> {
|
||||
ui.showCustomConfirm("@setting.publichost.name", "@public.confirm.really", "@no", "@yes", () -> {
|
||||
Core.settings.put("publichost", true);
|
||||
platform.updateLobby();
|
||||
}, () -> {
|
||||
Core.settings.put("publichost", false);
|
||||
platform.updateLobby();
|
||||
});
|
||||
}, () -> {
|
||||
Core.settings.put("publichost", false);
|
||||
platform.updateLobby();
|
||||
});
|
||||
}));
|
||||
player.admin = true;
|
||||
Events.fire(new HostEvent());
|
||||
|
||||
if(steam && Core.settings.getBool("steampublichost")){
|
||||
if(Version.modifier.contains("beta") || Version.modifier.contains("alpha")){
|
||||
Core.settings.put("publichost", false);
|
||||
Core.settings.put("steampublichost", false);
|
||||
platform.updateLobby();
|
||||
Core.settings.getBoolOnce("betapublic", () -> ui.showInfo("@public.beta"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}catch(IOException e){
|
||||
ui.showException("@server.error", e);
|
||||
}catch(Exception e){
|
||||
ui.showException(e.getMessage() != null && e.getMessage().toLowerCase(Locale.ROOT).contains("address already in use") ? "@server.error.addressinuse" : "@server.error", e);
|
||||
}
|
||||
ui.loadfrag.hide();
|
||||
hide();
|
||||
|
||||
79
core/src/mindustry/ui/dialogs/IconSelectDialog.java
Normal file
79
core/src/mindustry/ui/dialogs/IconSelectDialog.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class IconSelectDialog extends Dialog{
|
||||
private Intc consumer = i -> Log.info("you have mere seconds");
|
||||
private boolean allowLocked;
|
||||
|
||||
public IconSelectDialog(){
|
||||
this(true);
|
||||
}
|
||||
|
||||
public IconSelectDialog(boolean allowLocked){
|
||||
closeOnBack();
|
||||
setFillParent(true);
|
||||
|
||||
cont.pane(t -> {
|
||||
resized(true, () -> {
|
||||
t.clearChildren();
|
||||
t.marginRight(19f);
|
||||
t.defaults().size(48f);
|
||||
|
||||
t.button(Icon.none, Styles.flati, () -> {
|
||||
hide();
|
||||
consumer.get(0);
|
||||
});
|
||||
|
||||
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
|
||||
|
||||
int i = 1;
|
||||
for(var key : accessibleIcons){
|
||||
var value = Icon.icons.get(key);
|
||||
|
||||
t.button(value, Styles.flati, () -> {
|
||||
hide();
|
||||
consumer.get(Iconc.codes.get(key));
|
||||
});
|
||||
|
||||
if(++i % cols == 0) t.row();
|
||||
}
|
||||
|
||||
for(ContentType ctype : defaultContentIcons){
|
||||
t.row();
|
||||
t.image().colspan(cols).growX().width(Float.NEGATIVE_INFINITY).height(3f).color(Pal.accent);
|
||||
t.row();
|
||||
|
||||
i = 0;
|
||||
for(UnlockableContent u : content.getBy(ctype).<UnlockableContent>as()){
|
||||
if(!u.isHidden() && (allowLocked || u.unlocked())){
|
||||
t.button(new TextureRegionDrawable(u.uiIcon), Styles.flati, iconMed, () -> {
|
||||
hide();
|
||||
consumer.get(u.emojiChar());
|
||||
});
|
||||
|
||||
if(++i % cols == 0) t.row();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
|
||||
}
|
||||
|
||||
public void show(Intc listener){
|
||||
consumer = listener;
|
||||
super.show();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.Net.*;
|
||||
import arc.freetype.FreeTypeFontGenerator.*;
|
||||
import arc.graphics.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.TextButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
@@ -16,7 +17,7 @@ import mindustry.core.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.legacy.*;
|
||||
import mindustry.io.versions.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.net.Packets.*;
|
||||
import mindustry.ui.*;
|
||||
@@ -34,32 +35,53 @@ public class JoinDialog extends BaseDialog{
|
||||
int totalHosts;
|
||||
int refreshes;
|
||||
boolean showHidden;
|
||||
TextButtonStyle style;
|
||||
Task fontIgnoreDirtyTask;
|
||||
|
||||
String lastIp;
|
||||
int lastPort;
|
||||
int lastPort, lastColumns = -1;
|
||||
Task ping;
|
||||
|
||||
String serverSearch = "";
|
||||
|
||||
public JoinDialog(){
|
||||
super("@joingame");
|
||||
|
||||
makeButtonOverlay();
|
||||
|
||||
style = new TextButtonStyle(){{
|
||||
over = Styles.flatOver;
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.gray;
|
||||
down = Styles.flatOver;
|
||||
up = Styles.black5;
|
||||
}};
|
||||
|
||||
loadServers();
|
||||
|
||||
if(!steam) buttons.add().width(60f);
|
||||
//mobile players don't get information >:(
|
||||
boolean infoButton = !steam && !mobile;
|
||||
|
||||
if(infoButton) buttons.add().width(60f);
|
||||
buttons.add().growX().width(-1);
|
||||
|
||||
addCloseButton();
|
||||
addCloseButton(mobile ? 190f : 210f);
|
||||
|
||||
buttons.button("@server.add", Icon.add, () -> {
|
||||
renaming = null;
|
||||
add.show();
|
||||
});
|
||||
|
||||
buttons.add().growX().width(-1);
|
||||
if(!steam){
|
||||
buttons.button("?", () -> ui.showInfo("@join.info")).size(60f, 64f).width(-1);
|
||||
}
|
||||
if(infoButton) buttons.button("?", () -> ui.showInfo("@join.info")).size(60f, 64f);
|
||||
|
||||
add = new BaseDialog("@joingame.title");
|
||||
add.cont.add("@joingame.ip").padRight(5f).left();
|
||||
|
||||
TextField field = add.cont.field(Core.settings.getString("ip"), text -> {
|
||||
Core.settings.put("ip", text);
|
||||
}).size(320f, 54f).maxTextLength(100).addInputDialog().get();
|
||||
}).size(320f, 54f).maxTextLength(100).get();
|
||||
|
||||
add.cont.row();
|
||||
add.buttons.defaults().size(140f, 60f).pad(4f);
|
||||
@@ -97,8 +119,17 @@ public class JoinDialog extends BaseDialog{
|
||||
});
|
||||
|
||||
onResize(() -> {
|
||||
setup();
|
||||
refreshAll();
|
||||
|
||||
|
||||
//only refresh on resize when the minimum dimension is smaller than the maximum preferred width
|
||||
//this means that refreshes on resize will only happen for small phones that need the list to fit in portrait mode
|
||||
//also resize if number of cols changes
|
||||
if(Math.min(Core.graphics.getWidth(), Core.graphics.getHeight()) / Scl.scl() * 0.9f < 500f || lastColumns != columns()){
|
||||
setup();
|
||||
refreshAll();
|
||||
}
|
||||
|
||||
lastColumns = columns();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,7 +138,9 @@ public class JoinDialog extends BaseDialog{
|
||||
|
||||
refreshLocal();
|
||||
refreshRemote();
|
||||
refreshGlobal();
|
||||
if(Core.settings.getBool("communityservers", true)){
|
||||
refreshCommunity();
|
||||
}
|
||||
}
|
||||
|
||||
void setupRemote(){
|
||||
@@ -115,26 +148,30 @@ public class JoinDialog extends BaseDialog{
|
||||
|
||||
for(Server server : servers){
|
||||
//why are java lambdas this bad
|
||||
TextButton[] buttons = {null};
|
||||
Button[] buttons = {null};
|
||||
|
||||
TextButton button = buttons[0] = remote.button("[accent]" + server.displayIP(), Styles.cleart, () -> {
|
||||
Button button = buttons[0] = remote.button(b -> {}, style, () -> {
|
||||
if(!buttons[0].childrenPressed()){
|
||||
if(server.lastHost != null){
|
||||
Events.fire(new ClientPreConnectEvent(server.lastHost));
|
||||
safeConnect(server.ip, server.port, server.lastHost.version);
|
||||
safeConnect(server.lastHost.address, server.lastHost.port, server.lastHost.version);
|
||||
}else{
|
||||
connect(server.ip, server.port);
|
||||
}
|
||||
}
|
||||
}).width(targetWidth()).pad(4f).get();
|
||||
}).width(targetWidth()).growY().top().left().pad(4f).get();
|
||||
|
||||
button.getLabel().setWrap(true);
|
||||
if(remote.getChildren().size % columns() == 0){
|
||||
remote.row();
|
||||
}
|
||||
|
||||
Table inner = new Table(Tex.whiteui);
|
||||
inner.setColor(Pal.gray);
|
||||
|
||||
Table inner = new Table();
|
||||
button.clearChildren();
|
||||
button.add(inner).growX();
|
||||
|
||||
inner.add(button.getLabel()).growX();
|
||||
inner.add("[accent]" + server.displayIP()).left().padLeft(10f).wrap().style(Styles.outlineLabel).growX();
|
||||
|
||||
inner.button(Icon.upOpen, Styles.emptyi, () -> {
|
||||
moveRemote(server, -1);
|
||||
@@ -167,8 +204,6 @@ public class JoinDialog extends BaseDialog{
|
||||
button.row();
|
||||
|
||||
server.content = button.table(t -> {}).grow().get();
|
||||
|
||||
remote.row();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,55 +235,76 @@ public class JoinDialog extends BaseDialog{
|
||||
|
||||
void refreshServer(Server server){
|
||||
server.content.clear();
|
||||
server.content.label(() -> Core.bundle.get("server.refreshing") + Strings.animated(Time.time, 4, 11, "."));
|
||||
|
||||
server.content.background(Tex.whitePane).setColor(Pal.gray);
|
||||
|
||||
server.content.label(() -> Core.bundle.get("server.refreshing") + Strings.animated(Time.time, 4, 11, ".")).grow().center().labelAlign(Align.center).padBottom(4);
|
||||
|
||||
net.pingHost(server.ip, server.port, host -> setupServer(server, host), e -> {
|
||||
server.content.clear();
|
||||
server.content.add("@host.invalid").padBottom(4);
|
||||
|
||||
server.content.background(Tex.whitePane).setColor(Pal.gray);
|
||||
server.content.add("@host.invalid").grow().center().labelAlign(Align.center);
|
||||
});
|
||||
}
|
||||
|
||||
void setupServer(Server server, Host host){
|
||||
server.lastHost = host;
|
||||
server.content.clear();
|
||||
buildServer(host, server.content);
|
||||
buildServer(host, server.content, false, true);
|
||||
}
|
||||
|
||||
void buildServer(Host host, Table content){
|
||||
String versionString;
|
||||
void buildServer(Host host, Table content, boolean local, boolean addName){
|
||||
content.top().left();
|
||||
boolean isBanned = local && Vars.steam && host.description != null && host.description.equals("[banned]");
|
||||
String versionString = getVersionString(host) + (isBanned ? "[red] [banned]" : "");
|
||||
|
||||
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);
|
||||
float twidth = targetWidth() - 40f;
|
||||
|
||||
content.background(null);
|
||||
|
||||
Color color = Pal.gray;
|
||||
|
||||
if(addName){
|
||||
content.table(Tex.whiteui, t -> {
|
||||
t.left();
|
||||
t.setColor(color);
|
||||
|
||||
t.add(host.name + " " + versionString).style(Styles.outlineLabel).padLeft(10f).width(twidth).left().ellipsis(true);
|
||||
}).growX().height(36f).row();
|
||||
}
|
||||
|
||||
content.table(t -> {
|
||||
t.add("[lightgray]" + host.name + " " + versionString).width(targetWidth() - 10f).left().get().setEllipsis(true);
|
||||
t.row();
|
||||
if(!host.description.isEmpty()){
|
||||
t.add("[gray]" + host.description).width(targetWidth() - 10f).left().wrap();
|
||||
content.table(Tex.whitePane, t -> {
|
||||
t.top().left();
|
||||
t.setColor(color);
|
||||
t.left();
|
||||
|
||||
if(!host.description.isEmpty() && !isBanned){
|
||||
//limit newlines.
|
||||
int count = 0;
|
||||
StringBuilder result = new StringBuilder(host.description.length());
|
||||
for(int i = 0; i < host.description.length(); i++){
|
||||
char c = host.description.charAt(i);
|
||||
if(c == '\n'){
|
||||
count ++;
|
||||
if(count < 3) result.append(c);
|
||||
}else{
|
||||
result.append(c);
|
||||
}
|
||||
}
|
||||
t.add("[gray]" + result).width(twidth).left().wrap();
|
||||
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.modeName == null ? host.mode.toString() : host.modeName)).width(targetWidth() - 10f).left().get().setEllipsis(true);
|
||||
|
||||
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().row();
|
||||
|
||||
t.add("[lightgray]" + Core.bundle.format("save.map", host.mapname) + "[lightgray] / " + (host.modeName == null ? host.mode.toString() : host.modeName)).width(twidth).left().ellipsis(true).row();
|
||||
|
||||
if(host.ping > 0){
|
||||
t.row();
|
||||
t.add(Iconc.chartBar + " " + host.ping + "ms").color(Color.gray).left();
|
||||
t.add(Iconc.chartBar + " " + host.ping + "ms").style(Styles.outlineLabel).color(Pal.gray).left();
|
||||
}
|
||||
}).expand().left().bottom().padLeft(12f).padBottom(8);
|
||||
}).growY().growX().left().bottom();
|
||||
}
|
||||
|
||||
void setup(){
|
||||
@@ -258,10 +314,14 @@ public class JoinDialog extends BaseDialog{
|
||||
float w = targetWidth();
|
||||
|
||||
hosts.clear();
|
||||
//since the buttons are an overlay, make room for that
|
||||
hosts.marginBottom(70f);
|
||||
|
||||
section("@servers.local", local, false);
|
||||
section(steam ? "@servers.local.steam" : "@servers.local", local, false);
|
||||
section("@servers.remote", remote, false);
|
||||
section("@servers.global", global, true);
|
||||
if(Core.settings.getBool("communityservers", true)){
|
||||
section("@servers.global", global, true);
|
||||
}
|
||||
|
||||
ScrollPane pane = new ScrollPane(hosts);
|
||||
pane.setFadeScrollBars(false);
|
||||
@@ -275,9 +335,9 @@ public class JoinDialog extends BaseDialog{
|
||||
t.field(Core.settings.getString("name"), text -> {
|
||||
player.name(text);
|
||||
Core.settings.put("name", text);
|
||||
}).grow().pad(8).addInputDialog(maxNameLength);
|
||||
}).grow().pad(8).maxTextLength(maxNameLength);
|
||||
|
||||
ImageButton button = t.button(Tex.whiteui, Styles.clearFulli, 40, () -> {
|
||||
ImageButton button = t.button(Tex.whiteui, Styles.squarei, 40, () -> {
|
||||
new PaletteDialog().show(color -> {
|
||||
player.color().set(color);
|
||||
Core.settings.put("color-0", color.rgba8888());
|
||||
@@ -286,27 +346,8 @@ public class JoinDialog extends BaseDialog{
|
||||
button.update(() -> button.getStyle().imageUpColor = player.color());
|
||||
}).width(w).height(70f).pad(4);
|
||||
cont.row();
|
||||
cont.add(pane).width(w + 38).pad(0);
|
||||
cont.add(pane).width((w + 5) * columns() + 33).pad(0);
|
||||
cont.row();
|
||||
cont.buttonCenter("@server.add", Icon.add, () -> {
|
||||
renaming = null;
|
||||
add.show();
|
||||
}).marginLeft(10).width(w).height(80f).update(button -> {
|
||||
float pw = w;
|
||||
float pad = 0f;
|
||||
if(pane.getChildren().first().getPrefHeight() > pane.getHeight()){
|
||||
pw = w + 30;
|
||||
pad = 6;
|
||||
}
|
||||
|
||||
var cell = ((Table)pane.parent).getCell(button);
|
||||
|
||||
if(!Mathf.equal(cell.minWidth(), pw)){
|
||||
cell.width(pw);
|
||||
cell.padLeft(pad);
|
||||
pane.parent.invalidateHierarchy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void section(String label, Table servers, boolean eye){
|
||||
@@ -319,7 +360,7 @@ public class JoinDialog extends BaseDialog{
|
||||
if(eye){
|
||||
name.button(Icon.eyeSmall, Styles.emptyi, () -> {
|
||||
showHidden = !showHidden;
|
||||
refreshGlobal();
|
||||
refreshCommunity();
|
||||
}).update(i -> i.getStyle().imageUp = (showHidden ? Icon.eyeSmall : Icon.eyeOffSmall))
|
||||
.size(40f).right().padRight(3).tooltip("@servers.showhidden");
|
||||
}
|
||||
@@ -332,7 +373,7 @@ public class JoinDialog extends BaseDialog{
|
||||
hosts.row();
|
||||
hosts.image().growX().pad(5).padLeft(10).padRight(10).height(3).color(Pal.accent);
|
||||
hosts.row();
|
||||
hosts.add(coll).width(targetWidth());
|
||||
hosts.add(coll).width((targetWidth() + 5f) * columns());
|
||||
hosts.row();
|
||||
}
|
||||
|
||||
@@ -345,51 +386,61 @@ public class JoinDialog extends BaseDialog{
|
||||
net.discoverServers(this::addLocalHost, this::finishLocalHosts);
|
||||
}
|
||||
|
||||
void refreshGlobal(){
|
||||
void refreshCommunity(){
|
||||
int cur = refreshes;
|
||||
|
||||
global.clear();
|
||||
global.background(null);
|
||||
for(ServerGroup group : defaultServers){
|
||||
|
||||
global.table(t -> {
|
||||
t.add("@search").padRight(10);
|
||||
t.field(serverSearch, text ->
|
||||
serverSearch = text.trim().replaceAll(" +", " ").toLowerCase()
|
||||
).grow().pad(8).get().keyDown(KeyCode.enter, this::refreshCommunity);
|
||||
t.button(Icon.zoom, Styles.emptyi, this::refreshCommunity).size(54f);
|
||||
}).width((targetWidth() + 5f) * columns()).height(70f).pad(4).row();
|
||||
|
||||
for(int i = 0; i < defaultServers.size; i ++){
|
||||
ServerGroup group = defaultServers.get((i + defaultServers.size/2) % defaultServers.size);
|
||||
boolean hidden = group.hidden();
|
||||
if(hidden && !showHidden){
|
||||
continue;
|
||||
}
|
||||
|
||||
Table[] groupTable = {null};
|
||||
Table[] groupTable = {null, null};
|
||||
|
||||
if(group.prioritized){
|
||||
addHeader(groupTable, group, hidden, false);
|
||||
}
|
||||
//table containing all groups
|
||||
for(String address : group.addresses){
|
||||
String resaddress = address.contains(":") ? address.split(":")[0] : address;
|
||||
int resport = address.contains(":") ? Strings.parseInt(address.split(":")[1]) : port;
|
||||
net.pingHost(resaddress, resport, res -> {
|
||||
if(refreshes != cur) return;
|
||||
res.port = resport;
|
||||
|
||||
//add header
|
||||
if(groupTable[0] == null){
|
||||
global.table(t -> groupTable[0] = t).row();
|
||||
|
||||
groupTable[0].table(head -> {
|
||||
if(!group.name.isEmpty()){
|
||||
head.add(group.name).color(Color.lightGray).padRight(4);
|
||||
}
|
||||
head.image().height(3f).growX().color(Color.lightGray);
|
||||
|
||||
//button for showing/hiding servers
|
||||
ImageButton[] image = {null};
|
||||
image[0] = head.button(hidden ? Icon.eyeOffSmall : Icon.eyeSmall, Styles.accenti, () -> {
|
||||
group.setHidden(!group.hidden());
|
||||
image[0].getStyle().imageUp = group.hidden() ? Icon.eyeOffSmall : Icon.eyeSmall;
|
||||
if(group.hidden() && !showHidden){
|
||||
groupTable[0].remove();
|
||||
}
|
||||
}).size(40f).get();
|
||||
image[0].addListener(new Tooltip(t -> t.background(Styles.black6).margin(4).label(() -> !group.hidden() ? "@server.shown" : "@server.hidden")));
|
||||
}).width(targetWidth()).padBottom(-2).row();
|
||||
//don't recache the texture for a while
|
||||
if(fontIgnoreDirtyTask == null){
|
||||
FreeTypeFontData.ignoreDirty = true;
|
||||
fontIgnoreDirtyTask = Time.runTask(0.6f * 60f, () -> {
|
||||
FreeTypeFontData.ignoreDirty = false;
|
||||
fontIgnoreDirtyTask = null;
|
||||
});
|
||||
}
|
||||
|
||||
addGlobalHost(res, groupTable[0]);
|
||||
if(!serverSearch.isEmpty() && !(group.name.toLowerCase().contains(serverSearch)
|
||||
|| res.name.toLowerCase().contains(serverSearch)
|
||||
|| res.description.toLowerCase().contains(serverSearch)
|
||||
|| res.mapname.toLowerCase().contains(serverSearch)
|
||||
|| (res.modeName != null && res.modeName.toLowerCase().contains(serverSearch)))) return;
|
||||
|
||||
if(groupTable[0] == null){
|
||||
addHeader(groupTable, group, hidden, true);
|
||||
}else if(!groupTable[0].visible){
|
||||
addHeader(groupTable, group, hidden, true);
|
||||
}
|
||||
|
||||
addCommunityHost(res, groupTable[1]);
|
||||
|
||||
groupTable[0].margin(5f);
|
||||
groupTable[0].pack();
|
||||
@@ -398,11 +449,53 @@ public class JoinDialog extends BaseDialog{
|
||||
}
|
||||
}
|
||||
|
||||
void addGlobalHost(Host host, Table container){
|
||||
void addHeader(Table[] groupTable, ServerGroup group, boolean hidden, boolean doInit){ // outlined separately
|
||||
if(groupTable[0] == null){
|
||||
global.table(t -> groupTable[0] = t).fillX().left().row();
|
||||
}
|
||||
groupTable[0].visible(() -> doInit);
|
||||
if(!doInit){
|
||||
return;
|
||||
}
|
||||
|
||||
groupTable[0].table(head -> {
|
||||
Color col = group.prioritized ? Pal.accent : Color.lightGray;
|
||||
if(!group.name.isEmpty()){
|
||||
head.add(group.name).color(col).padRight(4);
|
||||
}
|
||||
head.image().height(3f).growX().color(col);
|
||||
|
||||
//button for showing/hiding servers
|
||||
ImageButton[] image = {null};
|
||||
image[0] = head.button(hidden ? Icon.eyeOffSmall : Icon.eyeSmall, Styles.grayi, () -> {
|
||||
group.setHidden(!group.hidden());
|
||||
image[0].getStyle().imageUp = group.hidden() ? Icon.eyeOffSmall : Icon.eyeSmall;
|
||||
if(group.hidden() && !showHidden){
|
||||
groupTable[0].remove();
|
||||
}
|
||||
}).size(40f).get();
|
||||
image[0].addListener(new Tooltip(t -> t.background(Styles.black6).margin(4).label(() -> !group.hidden() ? "@server.shown" : "@server.hidden")));
|
||||
}).width(targetWidth() * columns()).padBottom(-2).row();
|
||||
|
||||
groupTable[1] = groupTable[0].row().table().top().left().grow().get();
|
||||
}
|
||||
|
||||
int columns(){
|
||||
return Mathf.clamp((int)((Core.graphics.getWidth() / Scl.scl() * 0.9f) / targetWidth()), 1, 4);
|
||||
}
|
||||
|
||||
void addCommunityHost(Host host, Table container){
|
||||
global.background(null);
|
||||
String versionString = getVersionString(host);
|
||||
float w = targetWidth();
|
||||
|
||||
container.button(b -> buildServer(host, b), Styles.cleart, () -> {
|
||||
container.left().top();
|
||||
|
||||
Button[] button = {null};
|
||||
|
||||
button[0] = container.button(b -> {}, style, () -> {
|
||||
if(button[0].childrenPressed()) return;
|
||||
|
||||
Events.fire(new ClientPreConnectEvent(host));
|
||||
if(!Core.settings.getBool("server-disclaimer", false)){
|
||||
ui.showCustomConfirm("@warning", "@servers.disclaimer", "@ok", "@back", () -> {
|
||||
@@ -414,7 +507,32 @@ public class JoinDialog extends BaseDialog{
|
||||
}else{
|
||||
safeConnect(host.address, host.port, host.version);
|
||||
}
|
||||
}).width(w).row();
|
||||
}).width(w).padBottom(7).padRight(4f).top().left().growY().uniformY().get();
|
||||
|
||||
Table inner = new Table(Tex.whiteui);
|
||||
inner.setColor(Pal.gray);
|
||||
|
||||
button[0].clearChildren();
|
||||
button[0].add(inner).growX();
|
||||
|
||||
inner.add(host.name + " " + versionString).left().padLeft(10f).wrap().style(Styles.outlineLabel).growX();
|
||||
|
||||
inner.button(Icon.add, Styles.emptyi, () -> {
|
||||
Server server = new Server();
|
||||
server.setIP(host.address + ":" + host.port);
|
||||
servers.add(server);
|
||||
saveServers();
|
||||
setupRemote();
|
||||
refreshRemote();
|
||||
}).margin(3f).pad(8f).padRight(4f).top().right();
|
||||
|
||||
button[0].row();
|
||||
|
||||
buildServer(host, button[0].table(t -> {}).grow().get(), false, false);
|
||||
|
||||
if((container.getChildren().size) % columns() == 0){
|
||||
container.row();
|
||||
}
|
||||
}
|
||||
|
||||
void finishLocalHosts(){
|
||||
@@ -437,12 +555,14 @@ public class JoinDialog extends BaseDialog{
|
||||
totalHosts++;
|
||||
float w = targetWidth();
|
||||
|
||||
local.row();
|
||||
if((local.getChildren().size) % columns() == 0){
|
||||
local.row();
|
||||
}
|
||||
|
||||
local.button(b -> buildServer(host, b), Styles.cleart, () -> {
|
||||
local.button(b -> buildServer(host, b, true, true), style, () -> {
|
||||
Events.fire(new ClientPreConnectEvent(host));
|
||||
safeConnect(host.address, host.port, host.version);
|
||||
}).width(w);
|
||||
}).width(w).top().left().growY();
|
||||
}
|
||||
|
||||
public void connect(String ip, int port){
|
||||
@@ -463,8 +583,10 @@ public class JoinDialog extends BaseDialog{
|
||||
net.reset();
|
||||
Vars.netClient.beginConnecting();
|
||||
net.connect(lastIp = ip, lastPort = port, () -> {
|
||||
hide();
|
||||
add.hide();
|
||||
if(net.client()){
|
||||
hide();
|
||||
add.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -481,7 +603,7 @@ public class JoinDialog extends BaseDialog{
|
||||
connect(lastIp, lastPort);
|
||||
}, exception -> {});
|
||||
}, 1, 1);
|
||||
|
||||
|
||||
ui.loadfrag.setButton(() -> {
|
||||
ui.loadfrag.hide();
|
||||
if(ping == null) return;
|
||||
@@ -492,7 +614,7 @@ public class JoinDialog extends BaseDialog{
|
||||
|
||||
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[]" +
|
||||
ui.showInfo("[scarlet]" + (version > Version.build ? KickReason.clientOutdated : KickReason.serverOutdated) + "\n[]" +
|
||||
Core.bundle.format("server.versions", Version.build, version));
|
||||
}else{
|
||||
connect(ip, port);
|
||||
@@ -500,7 +622,7 @@ public class JoinDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
float targetWidth(){
|
||||
return Math.min(Core.graphics.getWidth() / Scl.scl() * 0.9f, 500f);
|
||||
return Math.min(Core.graphics.getWidth() / Scl.scl() * 0.9f, 550f);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -513,48 +635,76 @@ public class JoinDialog extends BaseDialog{
|
||||
Core.settings.remove("server-list");
|
||||
}
|
||||
|
||||
var url = becontrol.active() ? serverJsonBeURL : serverJsonV6URL;
|
||||
Log.info("Fetching community servers at @", url);
|
||||
fetchServers();
|
||||
}
|
||||
|
||||
public static void fetchServers(){
|
||||
var urls = Version.type.equals("bleeding-edge") || Vars.forceBeServers ? serverJsonBeURLs : serverJsonURLs;
|
||||
|
||||
if(Core.settings.getBool("communityservers", true)){
|
||||
fetchServers(urls, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void fetchServers(String[] urls, int index){
|
||||
if(index >= urls.length) return;
|
||||
|
||||
//get servers
|
||||
Core.net.httpGet(url, result -> {
|
||||
try{
|
||||
if(result.getStatus() != HttpStatus.OK){
|
||||
Log.warn("Failed to fetch community servers: @", result.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
Jval val = Jval.read(result.getResultAsString());
|
||||
Core.app.post(() -> {
|
||||
try{
|
||||
defaultServers.clear();
|
||||
val.asArray().each(child -> {
|
||||
String name = child.getString("name", "");
|
||||
String[] addresses;
|
||||
if(child.has("addresses") || (child.has("address") && child.get("address").isArray())){
|
||||
addresses = (child.has("addresses") ? child.get("addresses") : child.get("address")).asArray().map(Jval::asString).toArray(String.class);
|
||||
}else{
|
||||
addresses = new String[]{child.getString("address", "<invalid>")};
|
||||
}
|
||||
defaultServers.add(new ServerGroup(name, addresses));
|
||||
});
|
||||
Log.info("Fetched @ community servers.", defaultServers.size);
|
||||
}catch(Throwable e){
|
||||
Log.err("Failed to parse community servers.");
|
||||
Log.err(e);
|
||||
}
|
||||
});
|
||||
}catch(Throwable e){
|
||||
Log.err("Failed to fetch community servers.");
|
||||
Log.err(e);
|
||||
Http.get(urls[index])
|
||||
.error(t -> {
|
||||
if(index < urls.length - 1){
|
||||
//attempt fetching from the next URL upon failure
|
||||
fetchServers(urls, index + 1);
|
||||
}else{
|
||||
Log.err("Failed to fetch community servers", t);
|
||||
}
|
||||
}, Log::err);
|
||||
})
|
||||
.submit(result -> {
|
||||
Jval val = Jval.read(result.getResultAsString());
|
||||
Seq<ServerGroup> servers = new Seq<>();
|
||||
val.asArray().each(child -> {
|
||||
String name = child.getString("name", "");
|
||||
boolean prioritized = child.getBool("prioritized", false);
|
||||
String[] addresses;
|
||||
if(child.has("addresses") || (child.has("address") && child.get("address").isArray())){
|
||||
addresses = (child.has("addresses") ? child.get("addresses") : child.get("address")).asArray().map(Jval::asString).toArray(String.class);
|
||||
}else{
|
||||
addresses = new String[]{child.getString("address", "<invalid>")};
|
||||
}
|
||||
servers.add(new ServerGroup(name, addresses, prioritized));
|
||||
});
|
||||
//modify default servers on main thread
|
||||
Core.app.post(() -> {
|
||||
servers.sort(s -> s.name == null ? Integer.MAX_VALUE : s.name.hashCode());
|
||||
defaultServers.addAll(servers);
|
||||
Log.info("Fetched @ community servers.", defaultServers.sum(s -> s.addresses.length));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void saveServers(){
|
||||
Core.settings.putJson("servers", Server.class, servers);
|
||||
}
|
||||
|
||||
private String getVersionString(Host host){
|
||||
if(host.version == -1){
|
||||
return Core.bundle.format("server.version", Core.bundle.get("server.custombuild"), "");
|
||||
}else if(host.version == 0){
|
||||
return Core.bundle.get("server.outdated");
|
||||
}else if(host.version < Version.build && Version.build != -1){
|
||||
return Core.bundle.get("server.outdated") + "\n" +
|
||||
Core.bundle.format("server.version", host.version, "");
|
||||
}else if(host.version > Version.build && Version.build != -1){
|
||||
return 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
|
||||
return "";
|
||||
}else{
|
||||
return Core.bundle.format("server.version", host.version, host.versionType);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Server{
|
||||
public String ip;
|
||||
public int port;
|
||||
@@ -568,7 +718,7 @@ public class JoinDialog extends BaseDialog{
|
||||
if(isIpv6 && ip.lastIndexOf("]:") != -1 && ip.lastIndexOf("]:") != ip.length() - 1){
|
||||
int idx = ip.indexOf("]:");
|
||||
this.ip = ip.substring(1, idx);
|
||||
this.port = Integer.parseInt(ip.substring(idx + 2, ip.length()));
|
||||
this.port = Integer.parseInt(ip.substring(idx + 2));
|
||||
}else if(!isIpv6 && ip.lastIndexOf(':') != -1 && ip.lastIndexOf(':') != ip.length() - 1){
|
||||
int idx = ip.lastIndexOf(':');
|
||||
this.ip = ip.substring(0, idx);
|
||||
|
||||
233
core/src/mindustry/ui/dialogs/KeybindDialog.java
Normal file
233
core/src/mindustry/ui/dialogs/KeybindDialog.java
Normal file
@@ -0,0 +1,233 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.KeyBinds.*;
|
||||
import arc.graphics.*;
|
||||
import arc.input.*;
|
||||
import arc.input.InputDevice.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
|
||||
public class KeybindDialog extends Dialog{
|
||||
protected Section section;
|
||||
protected KeyBind rebindKey = null;
|
||||
protected boolean rebindAxis = false;
|
||||
protected boolean rebindMin = true;
|
||||
protected KeyCode minKey = null;
|
||||
protected Dialog rebindDialog;
|
||||
protected float scroll;
|
||||
protected ObjectIntMap<Section> sectionControls = new ObjectIntMap<>();
|
||||
|
||||
public KeybindDialog(){
|
||||
super(bundle.get("keybind.title"));
|
||||
setup();
|
||||
addCloseButton();
|
||||
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.button("@back", Icon.left, this::hide).size(210f, 64f);
|
||||
|
||||
keyDown(key -> {
|
||||
if(key == KeyCode.escape || key == KeyCode.back) hide();
|
||||
});
|
||||
}
|
||||
|
||||
private void setup(){
|
||||
cont.clear();
|
||||
|
||||
Section[] sections = Core.keybinds.getSections();
|
||||
|
||||
Stack stack = new Stack();
|
||||
ButtonGroup<TextButton> group = new ButtonGroup<>();
|
||||
ScrollPane pane = new ScrollPane(stack);
|
||||
pane.setFadeScrollBars(false);
|
||||
pane.update(() -> scroll = pane.getScrollY());
|
||||
this.section = sections[0];
|
||||
|
||||
for(Section section : sections){
|
||||
if(!sectionControls.containsKey(section))
|
||||
sectionControls.put(section, input.getDevices().indexOf(section.device, true));
|
||||
|
||||
if(sectionControls.get(section, 0) >= input.getDevices().size){
|
||||
sectionControls.put(section, 0);
|
||||
section.device = input.getDevices().get(0);
|
||||
}
|
||||
|
||||
if(sections.length != 1){
|
||||
TextButton button = new TextButton(bundle.get("section." + section.name + ".name", Strings.capitalize(section.name)));
|
||||
if(section.equals(this.section))
|
||||
button.toggle();
|
||||
|
||||
button.clicked(() -> this.section = section);
|
||||
|
||||
group.add(button);
|
||||
cont.add(button).fill();
|
||||
}
|
||||
|
||||
Table table = new Table();
|
||||
|
||||
Label device = new Label("Keyboard");
|
||||
//device.setColor(style.controllerColor);
|
||||
device.setAlignment(Align.center);
|
||||
|
||||
Seq<InputDevice> devices = input.getDevices();
|
||||
|
||||
Table stable = new Table();
|
||||
|
||||
stable.button("<", () -> {
|
||||
int i = sectionControls.get(section, 0);
|
||||
if(i - 1 >= 0){
|
||||
sectionControls.put(section, i - 1);
|
||||
section.device = devices.get(i - 1);
|
||||
setup();
|
||||
}
|
||||
}).disabled(sectionControls.get(section, 0) - 1 < 0).size(40);
|
||||
|
||||
stable.add(device).minWidth(device.getMinWidth() + 60);
|
||||
|
||||
device.setText(input.getDevices().get(sectionControls.get(section, 0)).name());
|
||||
|
||||
stable.button(">", () -> {
|
||||
int i = sectionControls.get(section, 0);
|
||||
|
||||
if(i + 1 < devices.size){
|
||||
sectionControls.put(section, i + 1);
|
||||
section.device = devices.get(i + 1);
|
||||
setup();
|
||||
}
|
||||
}).disabled(sectionControls.get(section, 0) + 1 >= devices.size).size(40);
|
||||
|
||||
//no alternate devices until further notice
|
||||
//table.add(stable).colspan(4).row();
|
||||
|
||||
table.add().height(10);
|
||||
table.row();
|
||||
if(section.device.type() == DeviceType.controller){
|
||||
table.table(info -> info.add("Controller Type: [lightGray]" +
|
||||
Strings.capitalize(section.device.name())).left());
|
||||
}
|
||||
table.row();
|
||||
|
||||
String lastCategory = null;
|
||||
var tstyle = Styles.defaultt;
|
||||
|
||||
for(KeyBind keybind : keybinds.getKeybinds()){
|
||||
if(lastCategory != keybind.category() && keybind.category() != null){
|
||||
table.add(bundle.get("category." + keybind.category() + ".name", Strings.capitalize(keybind.category()))).color(Color.gray).colspan(4).pad(10).padBottom(4).row();
|
||||
table.image().color(Color.gray).fillX().height(3).pad(6).colspan(4).padTop(0).padBottom(10).row();
|
||||
lastCategory = keybind.category();
|
||||
}
|
||||
|
||||
if(keybind.defaultValue(section.device.type()) instanceof Axis){
|
||||
table.add(bundle.get("keybind." + keybind.name() + ".name", Strings.capitalize(keybind.name())), Color.white).left().padRight(40).padLeft(8);
|
||||
|
||||
table.labelWrap(() -> {
|
||||
Axis axis = keybinds.get(section, keybind);
|
||||
return axis.key != null ? axis.key.toString() : axis.min + " [red]/[] " + axis.max;
|
||||
}).color(Pal.accent).left().minWidth(90).fillX().padRight(20);
|
||||
|
||||
table.button("@settings.rebind", tstyle, () -> {
|
||||
rebindAxis = true;
|
||||
rebindMin = true;
|
||||
openDialog(section, keybind);
|
||||
}).width(130f);
|
||||
}else{
|
||||
table.add(bundle.get("keybind." + keybind.name() + ".name", Strings.capitalize(keybind.name())), Color.white).left().padRight(40).padLeft(8);
|
||||
table.label(() -> keybinds.get(section, keybind).key.toString()).color(Pal.accent).left().minWidth(90).padRight(20);
|
||||
|
||||
table.button("@settings.rebind", tstyle, () -> {
|
||||
rebindAxis = false;
|
||||
rebindMin = false;
|
||||
openDialog(section, keybind);
|
||||
}).width(130f);
|
||||
}
|
||||
table.button("@settings.resetKey", tstyle, () -> keybinds.resetToDefault(section, keybind)).width(130f).pad(2f).padLeft(4f);
|
||||
table.row();
|
||||
}
|
||||
|
||||
table.visible(() -> this.section.equals(section));
|
||||
|
||||
table.button("@settings.reset", () -> keybinds.resetToDefaults()).colspan(4).padTop(4).fill();
|
||||
|
||||
stack.add(table);
|
||||
}
|
||||
|
||||
cont.row();
|
||||
cont.add(pane).growX().colspan(sections.length);
|
||||
|
||||
}
|
||||
|
||||
void rebind(Section section, KeyBind bind, KeyCode newKey){
|
||||
if(rebindKey == null) return;
|
||||
rebindDialog.hide();
|
||||
boolean isAxis = bind.defaultValue(section.device.type()) instanceof Axis;
|
||||
|
||||
if(isAxis){
|
||||
if(newKey.axis || !rebindMin){
|
||||
section.binds.get(section.device.type(), OrderedMap::new).put(rebindKey, newKey.axis ? new Axis(newKey) : new Axis(minKey, newKey));
|
||||
}
|
||||
}else{
|
||||
section.binds.get(section.device.type(), OrderedMap::new).put(rebindKey, new Axis(newKey));
|
||||
}
|
||||
|
||||
if(rebindAxis && isAxis && rebindMin && !newKey.axis){
|
||||
rebindMin = false;
|
||||
minKey = newKey;
|
||||
openDialog(section, rebindKey);
|
||||
}else{
|
||||
rebindKey = null;
|
||||
rebindAxis = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void openDialog(Section section, KeyBind name){
|
||||
rebindDialog = new Dialog(rebindAxis ? bundle.get("keybind.press.axis") : bundle.get("keybind.press"));
|
||||
|
||||
rebindKey = name;
|
||||
|
||||
rebindDialog.titleTable.getCells().first().pad(4);
|
||||
|
||||
if(section.device.type() == DeviceType.keyboard){
|
||||
|
||||
rebindDialog.addListener(new InputListener(){
|
||||
@Override
|
||||
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
if(Core.app.isAndroid()) return false;
|
||||
rebind(section, name, button);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyDown(InputEvent event, KeyCode keycode){
|
||||
rebindDialog.hide();
|
||||
rebind(section, name, keycode);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scrolled(InputEvent event, float x, float y, float amountX, float amountY){
|
||||
if(!rebindAxis) return false;
|
||||
rebindDialog.hide();
|
||||
rebind(section, name, KeyCode.scroll);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rebindDialog.show();
|
||||
Time.runTask(1f, () -> getScene().setScrollFocus(rebindDialog));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import java.util.*;
|
||||
@@ -12,18 +13,58 @@ import java.util.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class LanguageDialog extends BaseDialog{
|
||||
private Locale lastLocale;
|
||||
private ObjectMap<Locale, String> displayNames = ObjectMap.of(
|
||||
Locale.TRADITIONAL_CHINESE, "正體中文",
|
||||
Locale.SIMPLIFIED_CHINESE, "简体中文"
|
||||
public static final ObjectMap<String, String> displayNames = ObjectMap.of(
|
||||
"ca", "Català",
|
||||
"id_ID", "Bahasa Indonesia",
|
||||
"da", "Dansk",
|
||||
"de", "Deutsch",
|
||||
"et", "Eesti",
|
||||
"en", "English",
|
||||
"es", "Español",
|
||||
"eu", "Euskara",
|
||||
"fil", "Filipino",
|
||||
"fr", "Français",
|
||||
"it", "Italiano",
|
||||
"lt", "Lietuvių",
|
||||
"hu", "Magyar",
|
||||
"nl", "Nederlands",
|
||||
"nl_BE", "Nederlands (België)",
|
||||
"pl", "Polski",
|
||||
"pt_BR", "Português (Brasil)",
|
||||
"pt_PT", "Português (Portugal)",
|
||||
"ro", "Română",
|
||||
"fi", "Suomi",
|
||||
"sv", "Svenska",
|
||||
"vi", "Tiếng Việt",
|
||||
"tk", "Türkmen dili",
|
||||
"tr", "Türkçe",
|
||||
"cs", "Čeština",
|
||||
"be", "Беларуская",
|
||||
"bg", "Български",
|
||||
"ru", "Русский",
|
||||
"sr", "Српски",
|
||||
"uk_UA", "Українська",
|
||||
"th", "ไทย",
|
||||
"zh_CN", "简体中文",
|
||||
"zh_TW", "正體中文",
|
||||
"ja", "日本語",
|
||||
"ko", "한국어",
|
||||
"router", "router"
|
||||
);
|
||||
|
||||
private Locale lastLocale;
|
||||
|
||||
public LanguageDialog(){
|
||||
super("@settings.language");
|
||||
addCloseButton();
|
||||
setup();
|
||||
}
|
||||
|
||||
public static String getDisplayName(Locale locale){
|
||||
String str = locale.toString().replace("in_ID", "id_ID");
|
||||
return displayNames.get(str, str);
|
||||
}
|
||||
|
||||
private void setup(){
|
||||
Table langs = new Table();
|
||||
langs.marginRight(24f).marginLeft(24f);
|
||||
@@ -33,11 +74,12 @@ public class LanguageDialog extends BaseDialog{
|
||||
ButtonGroup<TextButton> group = new ButtonGroup<>();
|
||||
|
||||
for(Locale loc : locales){
|
||||
TextButton button = new TextButton(Strings.capitalize(displayNames.get(loc, loc.getDisplayName(loc))), Styles.clearTogglet);
|
||||
TextButton button = new TextButton(getDisplayName(loc), Styles.flatTogglet);
|
||||
button.clicked(() -> {
|
||||
if(getLocale().equals(loc)) return;
|
||||
Core.settings.put("locale", loc.toString());
|
||||
Log.info("Setting locale: @", loc.toString());
|
||||
player.locale = loc.toString();
|
||||
ui.showInfo("@language.restart");
|
||||
});
|
||||
langs.add(button).group(group).update(t -> t.setChecked(loc.equals(getLocale()))).size(400f, 50f).row();
|
||||
|
||||
@@ -2,10 +2,12 @@ package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.ctype.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
@@ -24,12 +26,14 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
||||
Schematic selected;
|
||||
//validity of loadout items
|
||||
boolean valid;
|
||||
//last calculated capacity
|
||||
int lastCapacity;
|
||||
|
||||
public LaunchLoadoutDialog(){
|
||||
super("@configure");
|
||||
}
|
||||
|
||||
public void show(CoreBlock core, Sector sector, Runnable confirm){
|
||||
public void show(CoreBlock core, Sector sector, Sector destination, Runnable confirm){
|
||||
cont.clear();
|
||||
buttons.clear();
|
||||
|
||||
@@ -40,19 +44,52 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
||||
|
||||
ItemSeq sitems = sector.items();
|
||||
|
||||
//hide nonsensical items
|
||||
ItemSeq launch = universe.getLaunchResources();
|
||||
if(sector.planet.allowLaunchLoadout){
|
||||
for(var item : content.items()){
|
||||
if(!item.isOnPlanet(sector.planet)){
|
||||
launch.set(item, 0);
|
||||
}
|
||||
}
|
||||
universe.updateLaunchResources(launch);
|
||||
}
|
||||
|
||||
//updates sum requirements
|
||||
Runnable update = () -> {
|
||||
int cap = selected.findCore().itemCapacity;
|
||||
int cap = lastCapacity = (int)(sector.planet.launchCapacityMultiplier * selected.findCore().itemCapacity);
|
||||
|
||||
//cap resources based on core type
|
||||
ItemSeq schems = selected.requirements();
|
||||
ItemSeq resources = universe.getLaunchResources();
|
||||
resources.min(cap);
|
||||
|
||||
int capacity = lastCapacity;
|
||||
|
||||
if(!destination.allowLaunchLoadout()){
|
||||
resources.clear();
|
||||
//TODO this should be set to a proper loadout based on sector.
|
||||
if(destination.preset != null){
|
||||
var rules = destination.preset.generator.map.rules();
|
||||
for(var stack : rules.loadout){
|
||||
if(stack.item.isOnPlanet(sector.planet)){
|
||||
resources.add(stack.item, stack.amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else if(getMax()){
|
||||
for(Item item : content.items()){
|
||||
resources.set(item, Mathf.clamp(sitems.get(item) - schems.get(item), 0, capacity));
|
||||
}
|
||||
}
|
||||
|
||||
universe.updateLaunchResources(resources);
|
||||
|
||||
total.clear();
|
||||
selected.requirements().each(total::add);
|
||||
universe.getLaunchResources().each(total::add);
|
||||
valid = sitems.has(total);
|
||||
valid = sitems.has(total) || PlanetDialog.debugSelect;
|
||||
};
|
||||
|
||||
Cons<Table> rebuild = table -> {
|
||||
@@ -63,10 +100,13 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
||||
ItemSeq launches = universe.getLaunchResources();
|
||||
|
||||
for(ItemStack s : total){
|
||||
table.image(s.item.icon(Cicon.small)).left().size(Cicon.small.size);
|
||||
int as = schems.get(s.item), al = launches.get(s.item);
|
||||
|
||||
String amountStr = (al + as) + "[gray] (" + (al + " + " + as + ")");
|
||||
if(as + al == 0) continue;
|
||||
|
||||
table.image(s.item.uiIcon).left().size(iconSmall);
|
||||
|
||||
String amountStr = (al + as) + (destination.allowLaunchLoadout() ? "[gray] (" + (al + " + " + as + ")") : "");
|
||||
|
||||
table.add(
|
||||
sitems.has(s.item, s.amount) ? amountStr :
|
||||
@@ -82,53 +122,95 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
||||
|
||||
Runnable rebuildItems = () -> rebuild.get(items);
|
||||
|
||||
buttons.button("@resources", Icon.terrain, () -> {
|
||||
ItemSeq stacks = universe.getLaunchResources();
|
||||
Seq<ItemStack> out = stacks.toSeq();
|
||||
|
||||
loadout.show(selected.findCore().itemCapacity, out, UnlockableContent::unlocked, out::clear, () -> {}, () -> {
|
||||
universe.updateLaunchResources(new ItemSeq(out));
|
||||
if(destination.allowLaunchLoadout()){
|
||||
buttons.button("@resources.max", Icon.add, Styles.togglet, () -> {
|
||||
setMax(!getMax());
|
||||
update.run();
|
||||
rebuildItems.run();
|
||||
});
|
||||
}).width(204);
|
||||
}).checked(b -> getMax());
|
||||
|
||||
buttons.button("@launch.text", Icon.ok, () -> {
|
||||
buttons.button("@resources", Icon.edit, () -> {
|
||||
ItemSeq stacks = universe.getLaunchResources();
|
||||
Seq<ItemStack> out = stacks.toSeq();
|
||||
|
||||
ItemSeq realItems = sitems.copy();
|
||||
selected.requirements().each(realItems::remove);
|
||||
|
||||
loadout.show(lastCapacity, realItems, out, i -> i.unlocked() && i.isOnPlanet(sector.planet), out::clear, () -> {}, () -> {
|
||||
universe.updateLaunchResources(new ItemSeq(out));
|
||||
update.run();
|
||||
rebuildItems.run();
|
||||
});
|
||||
}).disabled(b -> getMax());
|
||||
}
|
||||
|
||||
boolean rows = Core.graphics.isPortrait() && mobile;
|
||||
|
||||
if(rows) buttons.row();
|
||||
|
||||
var cell = buttons.button("@launch.text", Icon.ok, () -> {
|
||||
universe.updateLoadout(core, selected);
|
||||
confirm.run();
|
||||
hide();
|
||||
}).disabled(b -> !valid);
|
||||
|
||||
if(rows){
|
||||
cell.colspan(2).size(160f + 160f + 4f, 64f);
|
||||
}
|
||||
|
||||
int cols = Math.max((int)(Core.graphics.getWidth() / Scl.scl(230)), 1);
|
||||
ButtonGroup<Button> group = new ButtonGroup<>();
|
||||
selected = universe.getLoadout(core);
|
||||
if(selected == null) selected = schematics.getLoadouts().get((CoreBlock)Blocks.coreShard).first();
|
||||
|
||||
cont.add(Core.bundle.format("launch.from", sector.name())).row();
|
||||
|
||||
cont.pane(t -> {
|
||||
int i = 0;
|
||||
if(destination.allowLaunchSchematics()){
|
||||
cont.pane(t -> {
|
||||
int[] i = {0};
|
||||
|
||||
for(var entry : schematics.getLoadouts()){
|
||||
if(entry.key.size <= core.size){
|
||||
for(Schematic s : entry.value){
|
||||
Cons<Schematic> handler = s -> {
|
||||
if(s.tiles.contains(tile -> !tile.block.supportsEnv(sector.planet.defaultEnv) ||
|
||||
//make sure block can be built here.
|
||||
!tile.block.isOnPlanet(sector.planet))){
|
||||
return;
|
||||
}
|
||||
|
||||
t.button(b -> b.add(new SchematicImage(s)), Styles.togglet, () -> {
|
||||
selected = s;
|
||||
update.run();
|
||||
rebuildItems.run();
|
||||
}).group(group).pad(4).checked(s == selected).size(200f);
|
||||
t.button(b -> b.add(new SchematicImage(s)), Styles.togglet, () -> {
|
||||
selected = s;
|
||||
update.run();
|
||||
rebuildItems.run();
|
||||
}).group(group).pad(4).checked(s == selected).size(200f);
|
||||
|
||||
if(++i % cols == 0){
|
||||
t.row();
|
||||
if(++i[0] % cols == 0){
|
||||
t.row();
|
||||
}
|
||||
};
|
||||
|
||||
if(destination.allowLaunchSchematics() || schematics.getDefaultLoadout(core) == null){
|
||||
for(var entry : schematics.getLoadouts()){
|
||||
if(entry.key.size <= core.size){
|
||||
for(Schematic s : entry.value){
|
||||
handler.get(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//only allow launching with the standard loadout schematic
|
||||
handler.get(schematics.getDefaultLoadout(core));
|
||||
}
|
||||
}
|
||||
}).growX().scrollX(false);
|
||||
|
||||
cont.row();
|
||||
|
||||
}).growX().get().setScrollingDisabled(true, false);
|
||||
cont.label(() -> Core.bundle.format("launch.capacity", lastCapacity)).row();
|
||||
cont.row();
|
||||
}else if(destination.preset != null && destination.preset.description != null){
|
||||
cont.pane(p -> {
|
||||
p.add(destination.preset.description).grow().wrap().labelAlign(Align.center);
|
||||
}).pad(10f).grow().row();
|
||||
}
|
||||
|
||||
cont.row();
|
||||
cont.pane(items);
|
||||
cont.row();
|
||||
cont.add("@sector.missingresources").visible(() -> !valid);
|
||||
@@ -138,4 +220,12 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
void setMax(boolean max){
|
||||
Core.settings.put("maxresources", max);
|
||||
}
|
||||
|
||||
boolean getMax(){
|
||||
return Core.settings.getBool("maxresources", true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Saves.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.io.*;
|
||||
@@ -20,8 +22,11 @@ import java.io.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class LoadDialog extends BaseDialog{
|
||||
ScrollPane pane;
|
||||
Table slots;
|
||||
String searchString;
|
||||
Seq<Gamemode> hidden;
|
||||
TextField searchField;
|
||||
ScrollPane pane;
|
||||
|
||||
public LoadDialog(){
|
||||
this("@loadgame");
|
||||
@@ -31,7 +36,10 @@ public class LoadDialog extends BaseDialog{
|
||||
super(title);
|
||||
setup();
|
||||
|
||||
shown(this::setup);
|
||||
shown(() -> {
|
||||
searchString = "";
|
||||
setup();
|
||||
});
|
||||
onResize(this::setup);
|
||||
|
||||
addCloseButton();
|
||||
@@ -42,10 +50,40 @@ public class LoadDialog extends BaseDialog{
|
||||
cont.clear();
|
||||
|
||||
slots = new Table();
|
||||
hidden = new Seq<>();
|
||||
pane = new ScrollPane(slots);
|
||||
|
||||
rebuild();
|
||||
|
||||
Table search = new Table();
|
||||
search.image(Icon.zoom);
|
||||
searchField = search.field("", t -> {
|
||||
searchString = t.length() > 0 ? t.toLowerCase() : null;
|
||||
rebuild();
|
||||
}).maxTextLength(50).growX().get();
|
||||
searchField.setMessageText("@save.search");
|
||||
for(Gamemode mode : Gamemode.all){
|
||||
TextureRegionDrawable icon = Vars.ui.getIcon("mode" + Strings.capitalize(mode.name()));
|
||||
boolean sandbox = mode == Gamemode.sandbox;
|
||||
if(Core.atlas.isFound(icon.getRegion()) || sandbox){
|
||||
search.button(sandbox ? Icon.terrain : icon, Styles.emptyTogglei, () -> {
|
||||
if(!hidden.addUnique(mode)) hidden.remove(mode);
|
||||
rebuild();
|
||||
}).size(60f).padLeft(-12f).checked(b -> !hidden.contains(mode)).tooltip("@mode." + mode.name() + ".name");
|
||||
}
|
||||
}
|
||||
|
||||
pane.setFadeScrollBars(false);
|
||||
pane.setScrollingDisabled(true, false);
|
||||
|
||||
cont.add(search).growX();
|
||||
cont.row();
|
||||
cont.add(pane).growY();
|
||||
}
|
||||
|
||||
public void rebuild(){
|
||||
|
||||
slots.clear();
|
||||
slots.marginRight(24).marginLeft(20f);
|
||||
|
||||
Time.runTask(2f, () -> Core.scene.setScrollFocus(pane));
|
||||
@@ -58,11 +96,15 @@ public class LoadDialog extends BaseDialog{
|
||||
boolean any = false;
|
||||
|
||||
for(SaveSlot slot : array){
|
||||
if(slot.isHidden()) continue;
|
||||
if(slot.isHidden()
|
||||
|| (searchString != null && !Strings.stripColors(slot.getName()).toLowerCase().contains(searchString))
|
||||
|| (!hidden.isEmpty() && hidden.contains(slot.mode()))){
|
||||
continue;
|
||||
}
|
||||
|
||||
any = true;
|
||||
|
||||
TextButton button = new TextButton("", Styles.cleart);
|
||||
TextButton button = new TextButton("", Styles.grayt);
|
||||
button.getLabel().remove();
|
||||
button.clearChildren();
|
||||
|
||||
@@ -75,21 +117,21 @@ public class LoadDialog extends BaseDialog{
|
||||
t.right();
|
||||
t.defaults().size(40f);
|
||||
|
||||
t.button(Icon.save, Styles.emptytogglei, () -> {
|
||||
t.button(Icon.save, Styles.emptyTogglei, () -> {
|
||||
slot.setAutosave(!slot.isAutosave());
|
||||
}).checked(slot.isAutosave()).right();
|
||||
|
||||
t.button(Icon.trash, Styles.emptyi, () -> {
|
||||
ui.showConfirm("@confirm", "@save.delete.confirm", () -> {
|
||||
slot.delete();
|
||||
setup();
|
||||
rebuild();
|
||||
});
|
||||
}).right();
|
||||
|
||||
t.button(Icon.pencil, Styles.emptyi, () -> {
|
||||
ui.showTextInput("@save.rename", "@save.rename.text", slot.getName(), text -> {
|
||||
slot.setName(text);
|
||||
setup();
|
||||
rebuild();
|
||||
});
|
||||
}).right();
|
||||
|
||||
@@ -141,10 +183,8 @@ public class LoadDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
if(!any){
|
||||
slots.button("@save.none", () -> {}).disabled(true).fillX().margin(20f).minWidth(340f).height(80f).pad(4f);
|
||||
slots.add("@save.none");
|
||||
}
|
||||
|
||||
cont.add(pane);
|
||||
}
|
||||
|
||||
public void addSetup(){
|
||||
@@ -152,12 +192,18 @@ public class LoadDialog extends BaseDialog{
|
||||
buttons.button("@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);
|
||||
var meta = SaveIO.getMeta(file);
|
||||
|
||||
if(meta.rules.sector != null){
|
||||
ui.showErrorMessage("@save.nocampaign");
|
||||
}else{
|
||||
try{
|
||||
control.saves.importSave(file);
|
||||
rebuild();
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
ui.showException("@save.import.fail", e);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
ui.showErrorMessage("@save.import.invalid");
|
||||
@@ -193,4 +239,15 @@ public class LoadDialog extends BaseDialog{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog show(){
|
||||
super.show();
|
||||
|
||||
if(Core.app.isDesktop() && searchField != null){
|
||||
Core.scene.setKeyboardFocus(searchField);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public class LoadoutDialog extends BaseDialog{
|
||||
private Boolf<Item> validator = i -> true;
|
||||
private Table items;
|
||||
private int capacity;
|
||||
private @Nullable ItemSeq total;
|
||||
|
||||
public LoadoutDialog(){
|
||||
super("@configure");
|
||||
@@ -46,6 +47,8 @@ public class LoadoutDialog extends BaseDialog{
|
||||
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
|
||||
|
||||
buttons.button("@max", Icon.export, this::maxItems).size(210f, 64f);
|
||||
|
||||
buttons.button("@settings.reset", Icon.refresh, () -> {
|
||||
resetter.run();
|
||||
reseed();
|
||||
@@ -54,12 +57,23 @@ public class LoadoutDialog extends BaseDialog{
|
||||
}).size(210f, 64f);
|
||||
}
|
||||
|
||||
public void maxItems(){
|
||||
for(ItemStack stack : stacks){
|
||||
stack.amount = total == null ? capacity : Math.max(Math.min(capacity, total.get(stack.item)), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void show(int capacity, Seq<ItemStack> stacks, Boolf<Item> validator, Runnable reseter, Runnable updater, Runnable hider){
|
||||
show(capacity, null, stacks, validator, reseter, updater, hider);
|
||||
}
|
||||
|
||||
public void show(int capacity, ItemSeq total, Seq<ItemStack> stacks, Boolf<Item> validator, Runnable reseter, Runnable updater, Runnable hider){
|
||||
this.originalStacks = stacks;
|
||||
this.validator = validator;
|
||||
this.resetter = reseter;
|
||||
this.updater = updater;
|
||||
this.capacity = capacity;
|
||||
this.total = total;
|
||||
this.hider = hider;
|
||||
reseed();
|
||||
show();
|
||||
@@ -75,17 +89,17 @@ public class LoadoutDialog extends BaseDialog{
|
||||
for(ItemStack stack : stacks){
|
||||
items.table(Tex.pane, t -> {
|
||||
t.margin(4).marginRight(8).left();
|
||||
t.button("-", Styles.cleart, () -> {
|
||||
t.button("-", Styles.flatt, () -> {
|
||||
stack.amount = Math.max(stack.amount - step(stack.amount), 0);
|
||||
updater.run();
|
||||
}).size(bsize);
|
||||
|
||||
t.button("+", Styles.cleart, () -> {
|
||||
t.button("+", Styles.flatt, () -> {
|
||||
stack.amount = Math.min(stack.amount + step(stack.amount), capacity);
|
||||
updater.run();
|
||||
}).size(bsize);
|
||||
|
||||
t.button(Icon.pencil, Styles.cleari, () -> ui.showTextInput("@configure", stack.item.localizedName, 10, stack.amount + "", true, str -> {
|
||||
t.button(Icon.pencil, Styles.flati, () -> ui.showTextInput("@configure", stack.item.localizedName, 10, stack.amount + "", true, str -> {
|
||||
if(Strings.canParsePositiveInt(str)){
|
||||
int amount = Strings.parseInt(str);
|
||||
if(amount >= 0 && amount <= capacity){
|
||||
@@ -97,7 +111,7 @@ public class LoadoutDialog extends BaseDialog{
|
||||
ui.showInfo(Core.bundle.format("configure.invalid", capacity));
|
||||
})).size(bsize);
|
||||
|
||||
t.image(stack.item.icon(Cicon.small)).size(8 * 3).padRight(4).padLeft(4);
|
||||
t.image(stack.item.uiIcon).size(8 * 3).padRight(4).padLeft(4);
|
||||
t.label(() -> stack.amount + "").left().width(90f);
|
||||
}).pad(2).left().fillX();
|
||||
|
||||
@@ -110,7 +124,7 @@ public class LoadoutDialog extends BaseDialog{
|
||||
|
||||
private void reseed(){
|
||||
this.stacks = originalStacks.map(ItemStack::copy);
|
||||
this.stacks.addAll(content.items().select(i -> validator.get(i) && !stacks.contains(stack -> stack.item == i)).map(i -> new ItemStack(i, 0)));
|
||||
this.stacks.addAll(content.items().select(i -> validator.get(i) && !i.isHidden() && !stacks.contains(stack -> stack.item == i)).map(i -> new ItemStack(i, 0)));
|
||||
this.stacks.sort(Structs.comparingInt(s -> s.item.id));
|
||||
}
|
||||
|
||||
|
||||
233
core/src/mindustry/ui/dialogs/MapListDialog.java
Normal file
233
core/src/mindustry/ui/dialogs/MapListDialog.java
Normal file
@@ -0,0 +1,233 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public abstract class MapListDialog extends BaseDialog{
|
||||
BaseDialog activeDialog;
|
||||
|
||||
private String searchString;
|
||||
private Seq<Gamemode> modes = new Seq<>();
|
||||
private Table mapTable = new Table();
|
||||
private TextField searchField;
|
||||
|
||||
private boolean
|
||||
showBuiltIn = Core.settings.getBool("editorshowbuiltinmaps", true),
|
||||
showCustom = Core.settings.getBool("editorshowcustommaps", true),
|
||||
searchAuthor = Core.settings.getBool("editorsearchauthor", false),
|
||||
searchDescription = Core.settings.getBool("editorsearchdescription", false),
|
||||
displayType;
|
||||
|
||||
public MapListDialog(String title, boolean displayType){
|
||||
super(title);
|
||||
|
||||
this.displayType = displayType;
|
||||
|
||||
buttons.remove();
|
||||
|
||||
addCloseListener();
|
||||
|
||||
shown(this::setup);
|
||||
onResize(() -> {
|
||||
if(activeDialog != null){
|
||||
activeDialog.hide();
|
||||
}
|
||||
setup();
|
||||
});
|
||||
}
|
||||
|
||||
void buildButtons(){}
|
||||
|
||||
abstract void showMap(Map map);
|
||||
|
||||
void setup(){
|
||||
makeButtonOverlay();
|
||||
|
||||
buttons.clearChildren();
|
||||
|
||||
searchString = null;
|
||||
|
||||
if(Core.graphics.isPortrait() && displayType){
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f * 2f, 64f).colspan(2);
|
||||
buttons.row();
|
||||
}else{
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
|
||||
}
|
||||
|
||||
buildButtons();
|
||||
|
||||
cont.clear();
|
||||
|
||||
rebuildMaps();
|
||||
|
||||
ScrollPane pane = new ScrollPane(mapTable);
|
||||
pane.setFadeScrollBars(false);
|
||||
pane.setScrollingDisabledX(true);
|
||||
|
||||
Table search = new Table();
|
||||
search.image(Icon.zoom);
|
||||
searchField = search.field("", t -> {
|
||||
searchString = t.length() > 0 ? t.toLowerCase() : null;
|
||||
rebuildMaps();
|
||||
}).maxTextLength(50).growX().get();
|
||||
searchField.setMessageText("@editor.search");
|
||||
search.button(Icon.filter, Styles.emptyi, this::showMapFilters).tooltip("@editor.filters");
|
||||
|
||||
cont.add(search).growX();
|
||||
cont.row();
|
||||
cont.add(pane).padLeft(28f).uniformX().growY();
|
||||
}
|
||||
|
||||
void rebuildMaps(){
|
||||
mapTable.clear();
|
||||
|
||||
mapTable.marginRight(12f);
|
||||
|
||||
int maxwidth = Math.max((int)(Core.graphics.getWidth() / Scl.scl(230)), 1);
|
||||
float mapsize = 200f;
|
||||
boolean noMapsShown = true;
|
||||
|
||||
int i = 0;
|
||||
|
||||
Seq<Map> mapList = showCustom ?
|
||||
showBuiltIn ? maps.all() : maps.customMaps() :
|
||||
showBuiltIn ? maps.defaultMaps() : null;
|
||||
|
||||
if(mapList != null){
|
||||
for(Map map : mapList){
|
||||
|
||||
boolean invalid = false;
|
||||
for(Gamemode mode : modes){
|
||||
invalid |= !mode.valid(map);
|
||||
}
|
||||
if(invalid || (searchString != null
|
||||
&& !map.plainName().toLowerCase().contains(searchString)
|
||||
&& (!searchAuthor || !map.plainAuthor().toLowerCase().contains(searchString))
|
||||
&& (!searchDescription || !map.plainDescription().toLowerCase().contains(searchString)))){
|
||||
continue;
|
||||
}
|
||||
|
||||
noMapsShown = false;
|
||||
|
||||
if(i % maxwidth == 0){
|
||||
mapTable.row();
|
||||
}
|
||||
|
||||
TextButton button = mapTable.button("", Styles.grayt, () -> showMap(map)).width(mapsize).bottom().pad(8).get();
|
||||
button.clearChildren();
|
||||
button.margin(9);
|
||||
button.bottom();
|
||||
|
||||
//TODO hide in editor?
|
||||
button.table(t -> {
|
||||
t.left();
|
||||
for(Gamemode mode : Gamemode.all){
|
||||
TextureRegionDrawable icon = Vars.ui.getIcon("mode" + Strings.capitalize(mode.name()) + "Small");
|
||||
if(mode.valid(map) && Core.atlas.isFound(icon.getRegion())){
|
||||
t.image(icon).size(16f).pad(4f);
|
||||
}
|
||||
}
|
||||
if(t.getChildren().size == 0){
|
||||
t.add().size(16f).pad(4f);
|
||||
}
|
||||
}).left().row();
|
||||
|
||||
button.add(map.name()).width(mapsize - 18f).center().get().setEllipsis(true);
|
||||
button.row();
|
||||
button.image().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);
|
||||
|
||||
if(displayType){
|
||||
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(noMapsShown){
|
||||
mapTable.add("@maps.none");
|
||||
}
|
||||
}
|
||||
|
||||
void showMapFilters(){
|
||||
activeDialog = new BaseDialog("@editor.filters");
|
||||
activeDialog.addCloseButton();
|
||||
activeDialog.cont.table(menu -> {
|
||||
menu.add("@editor.filters.mode").width(150f).left();
|
||||
menu.table(t -> {
|
||||
for(Gamemode mode : Gamemode.all){
|
||||
TextureRegionDrawable icon = Vars.ui.getIcon("mode" + Strings.capitalize(mode.name()));
|
||||
if(Core.atlas.isFound(icon.getRegion())){
|
||||
t.button(icon, Styles.emptyTogglei, () -> {
|
||||
if(modes.contains(mode)){
|
||||
modes.remove(mode);
|
||||
}else{
|
||||
modes.add(mode);
|
||||
}
|
||||
rebuildMaps();
|
||||
}).size(60f).checked(modes.contains(mode)).tooltip("@mode." + mode.name() + ".name");
|
||||
}
|
||||
}
|
||||
}).padBottom(10f);
|
||||
menu.row();
|
||||
|
||||
menu.add("@editor.filters.type").width(150f).left();
|
||||
menu.table(Tex.button, t -> {
|
||||
t.button("@custom", Styles.flatTogglet, () -> {
|
||||
showCustom = !showCustom;
|
||||
Core.settings.put("editorshowcustommaps", showCustom);
|
||||
rebuildMaps();
|
||||
}).size(150f, 60f).checked(showCustom);
|
||||
t.button("@builtin", Styles.flatTogglet, () -> {
|
||||
showBuiltIn = !showBuiltIn;
|
||||
Core.settings.put("editorshowbuiltinmaps", showBuiltIn);
|
||||
rebuildMaps();
|
||||
}).size(150f, 60f).checked(showBuiltIn);
|
||||
}).padBottom(10f);
|
||||
menu.row();
|
||||
|
||||
menu.add("@editor.filters.search").width(150f).left();
|
||||
menu.table(Tex.button, t -> {
|
||||
t.button("@editor.filters.author", Styles.flatTogglet, () -> {
|
||||
searchAuthor = !searchAuthor;
|
||||
Core.settings.put("editorsearchauthor", searchAuthor);
|
||||
rebuildMaps();
|
||||
}).size(150f, 60f).checked(searchAuthor);
|
||||
t.button("@editor.filters.description", Styles.flatTogglet, () -> {
|
||||
searchDescription = !searchDescription;
|
||||
Core.settings.put("editorsearchdescription", searchDescription);
|
||||
rebuildMaps();
|
||||
}).size(150f, 60f).checked(searchDescription);
|
||||
});
|
||||
});
|
||||
|
||||
activeDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog show(){
|
||||
super.show();
|
||||
|
||||
if(Core.app.isDesktop() && searchField != null){
|
||||
Core.scene.setKeyboardFocus(searchField);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@ import mindustry.ui.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MapPlayDialog extends BaseDialog{
|
||||
CustomRulesDialog dialog = new CustomRulesDialog();
|
||||
public @Nullable Runnable playListener;
|
||||
|
||||
CustomRulesDialog dialog = new CustomRulesDialog(true);
|
||||
Rules rules;
|
||||
Gamemode selectedGamemode = Gamemode.survival;
|
||||
Map lastMap;
|
||||
@@ -31,6 +33,10 @@ public class MapPlayDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
public void show(Map map){
|
||||
show(map, false);
|
||||
}
|
||||
|
||||
public void show(Map map, boolean playtesting){
|
||||
this.lastMap = map;
|
||||
title.setText(map.name());
|
||||
cont.clearChildren();
|
||||
@@ -46,27 +52,27 @@ public class MapPlayDialog extends BaseDialog{
|
||||
rules = map.applyRules(selectedGamemode);
|
||||
|
||||
Table selmode = new Table();
|
||||
selmode.add("@level.mode").colspan(4);
|
||||
selmode.add("@level.mode").colspan(2);
|
||||
selmode.row();
|
||||
int i = 0;
|
||||
|
||||
Table modes = new Table();
|
||||
selmode.table(Tex.button, modes -> {
|
||||
int i = 0;
|
||||
for(Gamemode mode : Gamemode.all){
|
||||
if(mode.hidden) continue;
|
||||
|
||||
for(Gamemode mode : Gamemode.values()){
|
||||
if(mode.hidden) continue;
|
||||
modes.button(mode.toString(), Styles.flatToggleMenut, () -> {
|
||||
selectedGamemode = mode;
|
||||
rules = map.applyRules(mode);
|
||||
}).update(b -> b.setChecked(selectedGamemode == mode)).size(140f, mobile ? 44f : 54f).disabled(!mode.valid(map));
|
||||
if(i++ % 2 == 1) modes.row();
|
||||
}
|
||||
});
|
||||
|
||||
modes.button(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.button("?", this::displayGameModeHelp).width(50f).fillY().padLeft(18f);
|
||||
|
||||
cont.add(selmode);
|
||||
cont.row();
|
||||
cont.button("@customize", Icon.settings, () -> dialog.show(rules, () -> rules = map.applyRules(selectedGamemode))).width(230);
|
||||
cont.button("@customize", Icon.settings, () -> dialog.show(rules, () -> rules = map.applyRules(selectedGamemode))).height(50f).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
|
||||
@@ -79,7 +85,8 @@ public class MapPlayDialog extends BaseDialog{
|
||||
addCloseButton();
|
||||
|
||||
buttons.button("@play", Icon.play, () -> {
|
||||
control.playMap(map, rules);
|
||||
if(playListener != null) playListener.run();
|
||||
control.playMap(map, rules, playtesting);
|
||||
hide();
|
||||
ui.custom.hide();
|
||||
}).size(210f, 64f);
|
||||
@@ -95,9 +102,9 @@ public class MapPlayDialog extends BaseDialog{
|
||||
ScrollPane pane = new ScrollPane(table);
|
||||
pane.setFadeScrollBars(false);
|
||||
table.row();
|
||||
for(Gamemode mode : Gamemode.values()){
|
||||
for(Gamemode mode : Gamemode.all){
|
||||
if(mode.hidden) continue;
|
||||
table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f);
|
||||
table.labelWrap("[accent]" + mode + ":[] [lightgray]" + mode.description()).width(400f);
|
||||
table.row();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
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.*;
|
||||
|
||||
public class MinimapDialog extends BaseDialog{
|
||||
|
||||
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.rect((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));
|
||||
}
|
||||
}
|
||||
@@ -1,112 +1,183 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.Net.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.TextButton.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.Http.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.Jval.*;
|
||||
import mindustry.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.mod.*;
|
||||
import mindustry.mod.Mods.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class ModsDialog extends BaseDialog{
|
||||
private ObjectMap<String, TextureRegion> textureCache = new ObjectMap<>();
|
||||
|
||||
private float modImportProgress;
|
||||
private String searchtxt = "";
|
||||
private @Nullable Seq<ModListing> modList;
|
||||
private boolean orderDate = true;
|
||||
private BaseDialog currentContent;
|
||||
|
||||
private BaseDialog browser;
|
||||
private Table browserTable;
|
||||
private float scroll = 0f;
|
||||
|
||||
public ModsDialog(){
|
||||
super("@mods");
|
||||
addCloseButton();
|
||||
|
||||
browser = new BaseDialog("@mods.browser");
|
||||
|
||||
browser.cont.table(table -> {
|
||||
table.left();
|
||||
table.image(Icon.zoom);
|
||||
table.field(searchtxt, res -> {
|
||||
searchtxt = res;
|
||||
rebuildBrowser();
|
||||
}).growX().get();
|
||||
table.button(Icon.list, Styles.emptyi, 32f, () -> {
|
||||
orderDate = !orderDate;
|
||||
rebuildBrowser();
|
||||
}).update(b -> b.getStyle().imageUp = (orderDate ? Icon.list : Icon.star)).size(40f).get()
|
||||
.addListener(new Tooltip(tip -> tip.label(() -> orderDate ? "@mods.browser.sortdate" : "@mods.browser.sortstars").left()));
|
||||
}).fillX().padBottom(4);
|
||||
|
||||
browser.cont.row();
|
||||
browser.cont.pane(tablebrow -> {
|
||||
tablebrow.margin(10f).top();
|
||||
browserTable = tablebrow;
|
||||
}).scrollX(false);
|
||||
browser.addCloseButton();
|
||||
browser.makeButtonOverlay();
|
||||
|
||||
browser.onResize(this::rebuildBrowser);
|
||||
|
||||
buttons.button("@mods.guide", Icon.link, () -> Core.app.openURI(modGuideURL)).size(210, 64f);
|
||||
|
||||
shown(this::setup);
|
||||
if(mobile){
|
||||
onResize(this::setup);
|
||||
if(!mobile){
|
||||
buttons.button("@mods.openfolder", Icon.link, () -> Core.app.openFolder(modDirectory.absolutePath()));
|
||||
}
|
||||
|
||||
shown(this::setup);
|
||||
onResize(this::setup);
|
||||
|
||||
Events.on(ResizeEvent.class, event -> {
|
||||
if(currentContent != null){
|
||||
currentContent.hide();
|
||||
currentContent = null;
|
||||
}
|
||||
});
|
||||
|
||||
hidden(() -> {
|
||||
if(mods.requiresReload()){
|
||||
reload();
|
||||
}
|
||||
});
|
||||
|
||||
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("trust anchor") || t.getMessage().contains("SSL") || t.getMessage().contains("protocol")))){
|
||||
if(error instanceof NoSuchMethodError || Strings.getCauses(error).contains(t -> t.getMessage() != null && (t.getMessage().contains("trust anchor") || t.getMessage().contains("SSL") || t.getMessage().contains("protocol")))){
|
||||
ui.showErrorMessage("@feature.unsupported");
|
||||
}else if(error instanceof HttpStatusException st){
|
||||
ui.showErrorMessage(Core.bundle.format("connectfail", Strings.capitalize(st.status.toString().toLowerCase())));
|
||||
}else if(error.getMessage() != null && error.getMessage().toLowerCase(Locale.ROOT).contains("writable dex")){
|
||||
ui.showException("@error.moddex", error);
|
||||
}else{
|
||||
ui.showException(error);
|
||||
}
|
||||
}
|
||||
|
||||
void getModList(Cons<Seq<ModListing>> listener){
|
||||
if(modList == null){
|
||||
Core.net.httpGet("https://raw.githubusercontent.com/Anuken/MindustryMods/master/mods.json", response -> {
|
||||
String strResult = response.getResultAsString();
|
||||
var status = response.getStatus();
|
||||
void getModList(int index, Cons<Seq<ModListing>> listener){
|
||||
if(index >= modJsonURLs.length) return;
|
||||
|
||||
if(modList != null){
|
||||
listener.get(modList);
|
||||
return;
|
||||
}
|
||||
|
||||
Http.get(modJsonURLs[index], response -> {
|
||||
String strResult = response.getResultAsString();
|
||||
|
||||
Core.app.post(() -> {
|
||||
try{
|
||||
modList = JsonIO.json.fromJson(Seq.class, ModListing.class, strResult);
|
||||
|
||||
var d = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
Func<String, Date> parser = text -> {
|
||||
try{
|
||||
return d.parse(text);
|
||||
}catch(Exception e){
|
||||
return new Date();
|
||||
}
|
||||
};
|
||||
|
||||
modList.sortComparing(m -> parser.get(m.lastUpdated)).reverse();
|
||||
listener.get(modList);
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
ui.showException(e);
|
||||
}
|
||||
});
|
||||
}, error -> {
|
||||
if(index < modJsonURLs.length - 1){
|
||||
getModList(index + 1, listener);
|
||||
}else{
|
||||
Core.app.post(() -> {
|
||||
if(status != HttpStatus.OK){
|
||||
ui.showErrorMessage(Core.bundle.format("connectfail", status));
|
||||
}else{
|
||||
modList = new Json().fromJson(Seq.class, ModListing.class, strResult);
|
||||
|
||||
//potentially sort mods by game version compatibility, or other criteria
|
||||
//modList.sort(Structs.comparingBool(m -> !Version.isAtLeast(m.minGameVersion)));
|
||||
|
||||
listener.get(modList);
|
||||
modError(error);
|
||||
if(browser != null){
|
||||
browser.hide();
|
||||
}
|
||||
});
|
||||
}, error -> Core.app.post(() -> ui.showException(error)));
|
||||
}else{
|
||||
listener.get(modList);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setup(){
|
||||
float h = 110f;
|
||||
float w = mobile ? 440f : 524f;
|
||||
float w = Math.min(Core.graphics.getWidth() / Scl.scl(1.05f), 520f);
|
||||
|
||||
cont.clear();
|
||||
cont.defaults().width(mobile ? 500 : 560f).pad(4);
|
||||
cont.defaults().width(Math.min(Core.graphics.getWidth() / Scl.scl(1.05f), 556f)).pad(4);
|
||||
cont.add("@mod.reloadrequired").visible(mods::requiresReload).center().get().setAlignment(Align.center);
|
||||
cont.row();
|
||||
|
||||
cont.table(buttons -> {
|
||||
buttons.left().defaults().growX().height(60f).uniformX();
|
||||
|
||||
TextButtonStyle style = Styles.clearPartialt;
|
||||
TextButtonStyle style = Styles.flatBordert;
|
||||
float margin = 12f;
|
||||
|
||||
buttons.button("@mod.import", Icon.add, style, () -> {
|
||||
BaseDialog dialog = new BaseDialog("@mod.import");
|
||||
|
||||
TextButtonStyle bstyle = Styles.cleart;
|
||||
TextButtonStyle bstyle = Styles.flatt;
|
||||
|
||||
dialog.cont.table(Tex.button, t -> {
|
||||
t.defaults().size(300f, 70f);
|
||||
@@ -116,21 +187,12 @@ public class ModsDialog extends BaseDialog{
|
||||
dialog.hide();
|
||||
|
||||
platform.showMultiFileChooser(file -> {
|
||||
Runnable go = () -> {
|
||||
try{
|
||||
mods.importMod(file);
|
||||
setup();
|
||||
}catch(IOException e){
|
||||
ui.showException(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
|
||||
//show unsafe jar file warning
|
||||
if(file.extEquals("jar")){
|
||||
ui.showConfirm("@warning", "@mod.jarwarn", go);
|
||||
}else{
|
||||
go.run();
|
||||
try{
|
||||
mods.importMod(file);
|
||||
setup();
|
||||
}catch(Exception e){
|
||||
ui.showException(e.getMessage() != null && e.getMessage().toLowerCase(Locale.ROOT).contains("writable dex") ? "@error.moddex" : "", e);
|
||||
Log.err(e);
|
||||
}
|
||||
}, "zip", "jar");
|
||||
}).margin(12f);
|
||||
@@ -141,81 +203,15 @@ public class ModsDialog extends BaseDialog{
|
||||
dialog.hide();
|
||||
|
||||
ui.showTextInput("@mod.import.github", "", 64, Core.settings.getString("lastmod", ""), text -> {
|
||||
//clean up the text in case somebody inputs a URL or adds random spaces
|
||||
text = text.trim().replace(" ", "");
|
||||
if(text.startsWith("https://github.com/")) text = text.substring("https://github.com/".length());
|
||||
|
||||
Core.settings.put("lastmod", text);
|
||||
|
||||
ui.loadfrag.show();
|
||||
githubImportMod(text);
|
||||
//there's no good way to know if it's a java mod here, so assume it's not
|
||||
githubImportMod(text, false, null);
|
||||
});
|
||||
}).margin(12f);
|
||||
|
||||
t.row();
|
||||
|
||||
t.button("@mod.featured.dialog.title", Icon.star, bstyle, () -> {
|
||||
Runnable[] rebuildBrowser = {null};
|
||||
dialog.hide();
|
||||
BaseDialog browser = new BaseDialog("$mod.featured.dialog.title");
|
||||
browser.cont.table(table -> {
|
||||
table.left();
|
||||
table.image(Icon.zoom);
|
||||
table.field(searchtxt, res -> {
|
||||
searchtxt = res;
|
||||
rebuildBrowser[0].run();
|
||||
}).growX().get();
|
||||
}).fillX().padBottom(4);
|
||||
|
||||
browser.cont.row();
|
||||
|
||||
browser.cont.pane(tablebrow -> {
|
||||
tablebrow.margin(10f).top();
|
||||
rebuildBrowser[0] = () -> {
|
||||
tablebrow.clear();
|
||||
tablebrow.add("@loading");
|
||||
|
||||
getModList(listings -> {
|
||||
tablebrow.clear();
|
||||
|
||||
for(ModListing mod : listings){
|
||||
if(mod.hasJava || !searchtxt.isEmpty() && !mod.repo.contains(searchtxt) || (Vars.ios && mod.hasScripts)) continue;
|
||||
|
||||
tablebrow.button(btn -> {
|
||||
btn.top().left();
|
||||
btn.margin(12f);
|
||||
btn.table(con -> {
|
||||
con.left();
|
||||
con.add("[accent]" + mod.name + "[white]\n[lightgray]Author:[] " + mod.author + "\n[lightgray]\uE809 " + mod.stars +
|
||||
(Version.isAtLeast(mod.minGameVersion) ? "" : "\n" + Core.bundle.format("mod.requiresversion", mod.minGameVersion)))
|
||||
.width(388f).wrap().growX().pad(0f, 6f, 0f, 6f).left().labelAlign(Align.left);
|
||||
con.add().growX().pad(0f, 6f, 0f, 6f);
|
||||
}).fillY().growX().pad(0f, 6f, 0f, 6f);
|
||||
}, Styles.modsb, () -> {
|
||||
var sel = new BaseDialog(mod.name);
|
||||
sel.cont.add(mod.description).width(mobile ? 400f : 500f).wrap().pad(4f).labelAlign(Align.center, Align.left);
|
||||
sel.buttons.defaults().size(150f, 54f).pad(2f);
|
||||
sel.setFillParent(false);
|
||||
sel.buttons.button("@back", Icon.left, () -> {
|
||||
sel.clear();
|
||||
sel.hide();
|
||||
});
|
||||
sel.buttons.button("@mods.browser.add", Icon.download, () -> {
|
||||
sel.hide();
|
||||
githubImportMod(mod.repo);
|
||||
});
|
||||
sel.buttons.button("@mods.github.open", Icon.link, () -> {
|
||||
Core.app.openURI("https://github.com/" + mod.repo);
|
||||
});
|
||||
sel.keyDown(KeyCode.escape, sel::hide);
|
||||
sel.keyDown(KeyCode.back, sel::hide);
|
||||
sel.show();
|
||||
}).width(480f).growX().left().fillY();
|
||||
tablebrow.row();
|
||||
}
|
||||
});
|
||||
};
|
||||
rebuildBrowser[0].run();
|
||||
});
|
||||
browser.addCloseButton();
|
||||
browser.show();
|
||||
}).margin(12f);
|
||||
});
|
||||
dialog.addCloseButton();
|
||||
|
||||
@@ -223,96 +219,118 @@ public class ModsDialog extends BaseDialog{
|
||||
|
||||
}).margin(margin);
|
||||
|
||||
if(!mobile){
|
||||
buttons.button("@mods.openfolder", Icon.link, style, () -> Core.app.openFolder(modDirectory.absolutePath())).margin(margin);
|
||||
}
|
||||
buttons.button("@mods.browser", Icon.menu, style, this::showModBrowser).margin(margin);
|
||||
}).width(w);
|
||||
|
||||
cont.row();
|
||||
|
||||
if(!mods.list().isEmpty()){
|
||||
boolean[] anyDisabled = {false};
|
||||
SearchBar.add(cont, mods.list(),
|
||||
mod -> mod.meta.displayName(),
|
||||
(table, mod) -> {
|
||||
if(!mod.enabled() && !anyDisabled[0] && mods.list().size > 0){
|
||||
anyDisabled[0] = true;
|
||||
table.row();
|
||||
table.image().growX().height(4f).pad(6f).color(Pal.gray);
|
||||
table.row();
|
||||
}
|
||||
Table[] pane = {null};
|
||||
|
||||
table.button(t -> {
|
||||
t.top().left();
|
||||
t.margin(12f);
|
||||
Cons<String> rebuild = query -> {
|
||||
pane[0].clear();
|
||||
boolean any = false;
|
||||
for(LoadedMod item : mods.list()){
|
||||
if(Strings.matches(query, item.meta.displayName)){
|
||||
any = true;
|
||||
if(!item.enabled() && !anyDisabled[0] && mods.list().size > 0){
|
||||
anyDisabled[0] = true;
|
||||
pane[0].row();
|
||||
pane[0].image().growX().height(4f).pad(6f).color(Pal.gray).row();
|
||||
}
|
||||
|
||||
t.defaults().left().top();
|
||||
t.table(title -> {
|
||||
title.left();
|
||||
pane[0].button(t -> {
|
||||
t.top().left();
|
||||
t.margin(12f);
|
||||
|
||||
title.add(new BorderImage(){{
|
||||
if(mod.iconTexture != null){
|
||||
setDrawable(new TextureRegion(mod.iconTexture));
|
||||
}else{
|
||||
setDrawable(Tex.nomap);
|
||||
}
|
||||
border(Pal.accent);
|
||||
}}).size(h - 8f).padTop(-8f).padLeft(-8f).padRight(8f);
|
||||
String stateDetails = getStateDetails(item);
|
||||
if(stateDetails != null){
|
||||
t.addListener(new Tooltip(f -> f.background(Styles.black8).margin(4f).add(stateDetails).growX().width(400f).wrap()));
|
||||
}
|
||||
|
||||
title.table(text -> {
|
||||
boolean hideDisabled = !mod.isSupported() || mod.hasUnmetDependencies() || mod.hasContentErrors();
|
||||
t.defaults().left().top();
|
||||
t.table(title1 -> {
|
||||
title1.left();
|
||||
|
||||
text.add("" + mod.meta.displayName() + "\n[lightgray]v" + mod.meta.version + (mod.enabled() || hideDisabled ? "" : "\n" + Core.bundle.get("mod.disabled") + ""))
|
||||
title1.add(new BorderImage(){{
|
||||
if(item.iconTexture != null){
|
||||
setDrawable(new TextureRegion(item.iconTexture));
|
||||
}else{
|
||||
setDrawable(Tex.nomap);
|
||||
}
|
||||
border(Pal.accent);
|
||||
}}).size(h - 8f).padTop(-8f).padLeft(-8f).padRight(8f);
|
||||
|
||||
title1.table(text -> {
|
||||
boolean hideDisabled = !item.isSupported() || item.hasUnmetDependencies() || item.hasContentErrors();
|
||||
String shortDesc = item.meta.shortDescription();
|
||||
|
||||
text.add("[accent]" + Strings.stripColors(item.meta.displayName) + "\n" +
|
||||
(shortDesc.length() > 0 ? "[lightgray]" + shortDesc + "\n" : "")
|
||||
//so does anybody care about version?
|
||||
//+ "[gray]v" + Strings.stripColors(trimText(item.meta.version)) + "\n"
|
||||
+ (item.enabled() || hideDisabled ? "" : Core.bundle.get("mod.disabled") + ""))
|
||||
.wrap().top().width(300f).growX().left();
|
||||
|
||||
text.row();
|
||||
|
||||
if(mod.isOutdated()){
|
||||
text.labelWrap("@mod.outdated").growX();
|
||||
text.row();
|
||||
}else if(!mod.isSupported()){
|
||||
text.labelWrap(Core.bundle.format("mod.requiresversion", mod.meta.minGameVersion)).growX();
|
||||
text.row();
|
||||
}else if(mod.hasUnmetDependencies()){
|
||||
text.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX();
|
||||
t.row();
|
||||
}else if(mod.hasContentErrors()){
|
||||
text.labelWrap("@mod.erroredcontent").growX();
|
||||
text.row();
|
||||
}
|
||||
}).top().growX();
|
||||
|
||||
title.add().growX();
|
||||
}).growX().growY().left();
|
||||
String state = getStateText(item);
|
||||
if(state != null){
|
||||
text.labelWrap(state).growX().row();
|
||||
}
|
||||
}).top().growX();
|
||||
|
||||
t.table(right -> {
|
||||
right.right();
|
||||
right.button(mod.enabled() ? Icon.downOpen : Icon.upOpen, Styles.clearPartiali, () -> {
|
||||
mods.setEnabled(mod, !mod.enabled());
|
||||
setup();
|
||||
}).size(50f).disabled(!mod.isSupported());
|
||||
title1.add().growX();
|
||||
}).growX().growY().left();
|
||||
|
||||
right.button(mod.hasSteamID() ? Icon.link : Icon.trash, Styles.clearPartiali, () -> {
|
||||
if(!mod.hasSteamID()){
|
||||
ui.showConfirm("@confirm", "@mod.remove.confirm", () -> {
|
||||
mods.removeMod(mod);
|
||||
setup();
|
||||
});
|
||||
}else{
|
||||
platform.viewListing(mod);
|
||||
}
|
||||
}).size(50f);
|
||||
t.table(right -> {
|
||||
right.right();
|
||||
right.button(item.enabled() ? Icon.downOpen : Icon.upOpen, Styles.clearNonei, () -> {
|
||||
mods.setEnabled(item, !item.enabled());
|
||||
setup();
|
||||
}).size(50f).disabled(!item.isSupported());
|
||||
|
||||
if(steam && !mod.hasSteamID()){
|
||||
right.row();
|
||||
right.button(Icon.download, Styles.clearTransi, () -> {
|
||||
platform.publish(mod);
|
||||
right.button(item.hasSteamID() ? Icon.link : Icon.trash, Styles.clearNonei, () -> {
|
||||
if(!item.hasSteamID()){
|
||||
ui.showConfirm("@confirm", "@mod.remove.confirm", () -> {
|
||||
mods.removeMod(item);
|
||||
setup();
|
||||
});
|
||||
}else{
|
||||
platform.viewListing(item);
|
||||
}
|
||||
}).size(50f);
|
||||
}
|
||||
}).growX().right().padRight(-8f).padTop(-8f);
|
||||
}, Styles.clearPartialt, () -> showMod(mod)).size(w, h).growX().pad(4f);
|
||||
table.row();
|
||||
}, !mobile || Core.graphics.isPortrait()).margin(10f).top();
|
||||
|
||||
if(steam && !item.hasSteamID()){
|
||||
right.row();
|
||||
right.button(Icon.export, Styles.clearNonei, () -> {
|
||||
platform.publish(item);
|
||||
}).size(50f);
|
||||
}
|
||||
}).growX().right().padRight(-8f).padTop(-8f);
|
||||
}, Styles.flatBordert, () -> showMod(item)).size(w, h).growX().pad(4f);
|
||||
pane[0].row();
|
||||
}
|
||||
}
|
||||
|
||||
if(!any){
|
||||
pane[0].add("@none.found").color(Color.lightGray).pad(4);
|
||||
}
|
||||
};
|
||||
|
||||
if(!mobile || Core.graphics.isPortrait()){
|
||||
cont.table(search -> {
|
||||
search.image(Icon.zoom).padRight(8f);
|
||||
search.field("", rebuild).growX();
|
||||
}).fillX().padBottom(4);
|
||||
}
|
||||
|
||||
cont.row();
|
||||
cont.pane(table1 -> {
|
||||
pane[0] = table1.margin(10f).top();
|
||||
rebuild.get("");
|
||||
}).scrollX(false).update(s -> scroll = s.getScrollY()).get().setScrollYForce(scroll);
|
||||
}else{
|
||||
cont.table(Styles.black6, t -> t.add("@mods.none")).height(80f);
|
||||
}
|
||||
@@ -320,12 +338,55 @@ public class ModsDialog extends BaseDialog{
|
||||
cont.row();
|
||||
}
|
||||
|
||||
private @Nullable String getStateText(LoadedMod item){
|
||||
if(item.isOutdated()){
|
||||
return "@mod.incompatiblemod";
|
||||
}else if(item.isBlacklisted()){
|
||||
return "@mod.blacklisted";
|
||||
}else if(!item.isSupported()){
|
||||
return "@mod.incompatiblegame";
|
||||
}else if(item.state == ModState.circularDependencies){
|
||||
return "@mod.circulardependencies";
|
||||
}else if(item.state == ModState.incompleteDependencies){
|
||||
return "@mod.incompletedependencies";
|
||||
}else if(item.hasUnmetDependencies()){
|
||||
return "@mod.unmetdependencies";
|
||||
}else if(item.hasContentErrors()){
|
||||
return "@mod.erroredcontent";
|
||||
}else if(item.meta.hidden){
|
||||
return "@mod.multiplayer.compatible";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable String getStateDetails(LoadedMod item){
|
||||
if(item.isOutdated()){
|
||||
return "@mod.outdatedv7.details";
|
||||
}else if(item.isBlacklisted()){
|
||||
return "@mod.blacklisted.details";
|
||||
}else if(!item.isSupported()){
|
||||
return Core.bundle.format("mod.requiresversion.details", item.meta.minGameVersion);
|
||||
}else if(item.state == ModState.circularDependencies){
|
||||
return "@mod.circulardependencies.details";
|
||||
}else if(item.state == ModState.incompleteDependencies){
|
||||
return Core.bundle.format("mod.incompletedependencies.details", item.missingDependencies.toString(", "));
|
||||
}else if(item.hasUnmetDependencies()){
|
||||
return Core.bundle.format("mod.missingdependencies.details", item.missingDependencies.toString(", "));
|
||||
}else if(item.hasContentErrors()){
|
||||
return "@mod.erroredcontent.details";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void reload(){
|
||||
ui.showInfo("@mods.reloadexit", () -> Core.app.exit());
|
||||
ui.showInfoOnHidden("@mods.reloadexit", () -> {
|
||||
Log.info("Exiting to reload mods.");
|
||||
Core.app.exit();
|
||||
});
|
||||
}
|
||||
|
||||
private void showMod(LoadedMod mod){
|
||||
BaseDialog dialog = new BaseDialog(mod.meta.displayName());
|
||||
BaseDialog dialog = new BaseDialog(mod.meta.displayName);
|
||||
|
||||
dialog.addCloseButton();
|
||||
|
||||
@@ -333,14 +394,20 @@ public class ModsDialog extends BaseDialog{
|
||||
dialog.buttons.button("@mods.openfolder", Icon.link, () -> Core.app.openFolder(mod.file.absolutePath()));
|
||||
}
|
||||
|
||||
//TODO improve this menu later
|
||||
if(mod.getRepo() != null){
|
||||
boolean showImport = !mod.hasSteamID();
|
||||
dialog.buttons.button("@mods.github.open", Icon.link, () -> Core.app.openURI("https://github.com/" + mod.getRepo()));
|
||||
if(mobile && showImport) dialog.buttons.row();
|
||||
if(showImport) dialog.buttons.button("@mods.browser.reinstall", Icon.download, () -> githubImportMod(mod.getRepo(), mod.isJava(), null));
|
||||
}
|
||||
|
||||
dialog.cont.pane(desc -> {
|
||||
desc.center();
|
||||
desc.defaults().padTop(10).left();
|
||||
|
||||
desc.add("@editor.name").padRight(10).color(Color.gray).padTop(0);
|
||||
desc.row();
|
||||
desc.add(mod.meta.displayName()).growX().wrap().padTop(2);
|
||||
desc.add(mod.meta.displayName).growX().wrap().padTop(2);
|
||||
desc.row();
|
||||
if(mod.meta.author != null){
|
||||
desc.add("@editor.author").padRight(10).color(Color.gray);
|
||||
@@ -348,6 +415,12 @@ public class ModsDialog extends BaseDialog{
|
||||
desc.add(mod.meta.author).growX().wrap().padTop(2);
|
||||
desc.row();
|
||||
}
|
||||
if(mod.meta.version != null){
|
||||
desc.add("@mod.version").padRight(10).color(Color.gray).top();
|
||||
desc.row();
|
||||
desc.add(mod.meta.version).growX().wrap().padTop(2);
|
||||
desc.row();
|
||||
}
|
||||
if(mod.meta.description != null){
|
||||
desc.add("@editor.description").padRight(10).color(Color.gray).top();
|
||||
desc.row();
|
||||
@@ -355,41 +428,230 @@ public class ModsDialog extends BaseDialog{
|
||||
desc.row();
|
||||
}
|
||||
|
||||
String state = getStateDetails(mod);
|
||||
|
||||
if(state != null){
|
||||
desc.add("@mod.disabled").padTop(13f).padBottom(-6f).row();
|
||||
desc.add(state).growX().wrap().row();
|
||||
}
|
||||
|
||||
}).width(400f);
|
||||
|
||||
//TODO maybe enable later
|
||||
if(false){
|
||||
Seq<UnlockableContent> all = Seq.with(content.getContentMap()).<Content>flatten().select(c -> c.minfo.mod == mod && c instanceof UnlockableContent).as();
|
||||
if(all.any()){
|
||||
dialog.cont.row();
|
||||
dialog.cont.pane(cs -> {
|
||||
Seq<UnlockableContent> all = Seq.with(content.getContentMap()).<Content>flatten().select(c -> c.minfo.mod == mod && c instanceof UnlockableContent u && !u.isHidden()).as();
|
||||
if(all.any()){
|
||||
dialog.cont.row();
|
||||
dialog.cont.button("@mods.viewcontent", Icon.book, () -> {
|
||||
BaseDialog d = new BaseDialog(mod.meta.displayName);
|
||||
d.cont.pane(cs -> {
|
||||
int i = 0;
|
||||
for(UnlockableContent c : all){
|
||||
cs.button(new TextureRegionDrawable(c.icon(Cicon.medium)), Styles.cleari, Cicon.medium.size, () -> {
|
||||
cs.button(new TextureRegionDrawable(c.uiIcon), Styles.flati, iconMed, () -> {
|
||||
ui.content.show(c);
|
||||
}).size(50f).with(im -> {
|
||||
var click = im.getClickListener();
|
||||
im.update(() -> im.getImage().color.lerp(!click.isOver() ? Color.lightGray : Color.white, 0.4f * Time.delta));
|
||||
});
|
||||
|
||||
if(++i % 8 == 0) cs.row();
|
||||
}).tooltip(c.localizedName);
|
||||
|
||||
if(++i % (int)Math.min(Core.graphics.getWidth() / Scl.scl(110), 14) == 0) cs.row();
|
||||
}
|
||||
}).growX().minHeight(60f);
|
||||
}
|
||||
}).grow();
|
||||
d.addCloseButton();
|
||||
d.show();
|
||||
currentContent = d;
|
||||
}).size(300, 50).pad(4);
|
||||
}
|
||||
|
||||
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void showModBrowser(){
|
||||
rebuildBrowser();
|
||||
browser.show();
|
||||
}
|
||||
|
||||
private void rebuildBrowser(){
|
||||
ObjectSet<String> installed = mods.list().map(m -> m.getRepo()).asSet();
|
||||
|
||||
browserTable.clear();
|
||||
browserTable.add("@loading");
|
||||
|
||||
int cols = (int)Math.max(Core.graphics.getWidth() / Scl.scl(480), 1);
|
||||
|
||||
getModList(0, rlistings -> {
|
||||
browserTable.clear();
|
||||
int i = 0;
|
||||
|
||||
var listings = rlistings;
|
||||
if(!orderDate){
|
||||
listings = rlistings.copy();
|
||||
listings.sortComparing(m1 -> -m1.stars);
|
||||
}
|
||||
|
||||
for(ModListing mod : listings){
|
||||
if(((mod.hasJava || mod.hasScripts) && Vars.ios) ||
|
||||
(!Strings.matches(searchtxt, mod.name) && !Strings.matches(searchtxt, mod.repo))
|
||||
//hack, I'm basically testing if 135.10 >= modVersion, which is equivalent to modVersion >= 136
|
||||
|| (Version.isAtLeast(135, 10, mod.minGameVersion))
|
||||
) continue;
|
||||
|
||||
float s = 64f;
|
||||
|
||||
browserTable.button(con -> {
|
||||
con.margin(0f);
|
||||
con.left();
|
||||
|
||||
String repo = mod.repo;
|
||||
con.add(new BorderImage(){
|
||||
TextureRegion last;
|
||||
|
||||
{
|
||||
border(installed.contains(repo) ? Pal.accent : Color.lightGray);
|
||||
setDrawable(Tex.nomap);
|
||||
pad = Scl.scl(4f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
super.draw();
|
||||
|
||||
//textures are only requested when the rendering happens; this assists with culling
|
||||
if(!textureCache.containsKey(repo)){
|
||||
textureCache.put(repo, last = Core.atlas.find("nomap"));
|
||||
Http.get("https://raw.githubusercontent.com/Anuken/MindustryMods/master/icons/" + repo.replace("/", "_"), res -> {
|
||||
Pixmap pix = new Pixmap(res.getResult());
|
||||
Core.app.post(() -> {
|
||||
try{
|
||||
var tex = new Texture(pix);
|
||||
tex.setFilter(TextureFilter.linear);
|
||||
textureCache.put(repo, new TextureRegion(tex));
|
||||
pix.dispose();
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
}
|
||||
});
|
||||
}, err -> {});
|
||||
}
|
||||
|
||||
var next = textureCache.get(repo);
|
||||
if(last != next){
|
||||
last = next;
|
||||
setDrawable(next);
|
||||
}
|
||||
}
|
||||
}).size(s).pad(4f * 2f);
|
||||
|
||||
con.add(
|
||||
"[accent]" + mod.name.replace("\n", "") +
|
||||
(installed.contains(mod.repo) ? "\n[lightgray]" + Core.bundle.get("mod.installed") : "") +
|
||||
"\n[lightgray]\uE809 " + mod.stars +
|
||||
(Version.isAtLeast(mod.minGameVersion) ? "" :
|
||||
"\n" + Core.bundle.format("mod.requiresversion", mod.minGameVersion)))
|
||||
.width(358f).wrap().grow().pad(4f, 2f, 4f, 6f).top().left().labelAlign(Align.topLeft);
|
||||
|
||||
}, Styles.flatBordert, () -> {
|
||||
var sel = new BaseDialog(mod.name);
|
||||
sel.cont.pane(p -> p.add(mod.description + "\n\n[accent]" + Core.bundle.get("editor.author") + "[lightgray] " + mod.author)
|
||||
.width(mobile ? 400f : 500f).wrap().pad(4f).labelAlign(Align.center, Align.left)).grow();
|
||||
sel.buttons.defaults().size(150f, 54f).pad(2f);
|
||||
sel.buttons.button("@back", Icon.left, () -> {
|
||||
sel.clear();
|
||||
sel.hide();
|
||||
});
|
||||
|
||||
var found = mods.list().find(l -> mod.repo != null && mod.repo.equals(l.getRepo()));
|
||||
sel.buttons.button(found == null ? "@mods.browser.add" : "@mods.browser.reinstall", Icon.download, () -> {
|
||||
sel.hide();
|
||||
githubImportMod(mod.repo, mod.hasJava, null);
|
||||
});
|
||||
|
||||
if(Core.graphics.isPortrait()){
|
||||
sel.buttons.row();
|
||||
}
|
||||
|
||||
sel.buttons.button("@mods.github.open", Icon.link, () -> {
|
||||
Core.app.openURI("https://github.com/" + mod.repo);
|
||||
});
|
||||
sel.buttons.button("@mods.browser.view-releases", Icon.zoom, () -> {
|
||||
BaseDialog load = new BaseDialog("");
|
||||
load.cont.add("[accent]Fetching Releases...");
|
||||
load.show();
|
||||
Http.get(ghApi + "/repos/" + mod.repo + "/releases", res -> {
|
||||
var json = Jval.read(res.getResultAsString());
|
||||
JsonArray releases = json.asArray();
|
||||
|
||||
Core.app.post(() -> {
|
||||
load.hide();
|
||||
|
||||
if(releases.size == 0){
|
||||
ui.showInfo("@mods.browser.noreleases");
|
||||
}else{
|
||||
sel.hide();
|
||||
var downloads = new BaseDialog("@mods.browser.releases");
|
||||
downloads.cont.pane(p -> {
|
||||
for(int j = 0; j < releases.size; j++){
|
||||
var release = releases.get(j);
|
||||
|
||||
int index = j;
|
||||
p.table(((TextureRegionDrawable)Tex.whiteui).tint(Pal.darkestGray), t -> {
|
||||
t.add("[accent]" + release.getString("name") + (index == 0 ? " " + Core.bundle.get("mods.browser.latest") : "")).top().left().growX().wrap().pad(5f);
|
||||
t.row();
|
||||
t.add((release.getString("published_at")).substring(0, 10).replaceAll("-", "/")).top().left().growX().wrap().pad(5f).color(Color.gray);
|
||||
t.row();
|
||||
t.table(b -> {
|
||||
b.defaults().size(150f, 54f).pad(2f);
|
||||
b.button("@mods.github.open-release", Icon.link, () -> Core.app.openURI(release.getString("html_url")));
|
||||
b.button("@mods.browser.add", Icon.download, () -> {
|
||||
String releaseUrl = release.getString("url");
|
||||
githubImportMod(mod.repo, mod.hasJava, releaseUrl.substring(releaseUrl.lastIndexOf("/") + 1));
|
||||
});
|
||||
}).right();
|
||||
}).margin(5f).growX().pad(5f);
|
||||
|
||||
if(j < releases.size - 1) p.row();
|
||||
}
|
||||
}).width(500f).scrollX(false).fillY();
|
||||
downloads.buttons.button("@back", Icon.left, () -> {
|
||||
downloads.clear();
|
||||
downloads.hide();
|
||||
sel.show();
|
||||
}).size(150f, 54f).pad(2f);
|
||||
downloads.keyDown(KeyCode.escape, downloads::hide);
|
||||
downloads.keyDown(KeyCode.back, downloads::hide);
|
||||
downloads.hidden(sel::show);
|
||||
downloads.show();
|
||||
}
|
||||
});
|
||||
}, t -> Core.app.post(() -> {
|
||||
modError(t);
|
||||
load.hide();
|
||||
}));
|
||||
});
|
||||
sel.keyDown(KeyCode.escape, sel::hide);
|
||||
sel.keyDown(KeyCode.back, sel::hide);
|
||||
sel.show();
|
||||
}).width(438f).pad(4).growX().left().height(s + 8*2f).fillY();
|
||||
|
||||
if(++i % cols == 0) browserTable.row();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleMod(String repo, HttpResponse result){
|
||||
try{
|
||||
Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip");
|
||||
Streams.copy(result.getResultAsStream(), file.write(false));
|
||||
mods.importMod(file);
|
||||
long len = result.getContentLength();
|
||||
Floatc cons = len <= 0 ? f -> {} : p -> modImportProgress = p;
|
||||
|
||||
try(var stream = file.write(false)){
|
||||
Streams.copyProgress(result.getResultAsStream(), stream, len, 4096, cons);
|
||||
}
|
||||
|
||||
var mod = mods.importMod(file);
|
||||
mod.setRepo(repo);
|
||||
file.delete();
|
||||
Core.app.post(() -> {
|
||||
|
||||
try{
|
||||
setup();
|
||||
ui.loadfrag.hide();
|
||||
@@ -402,36 +664,83 @@ public class ModsDialog extends BaseDialog{
|
||||
}
|
||||
}
|
||||
|
||||
private void githubImportMod(String name){
|
||||
//try several branches
|
||||
//TODO use only the main branch as specified in meta
|
||||
githubImportBranch("6.0", name, e1 -> {
|
||||
githubImportBranch("master", name, e2 -> {
|
||||
githubImportBranch("main", name, e3 -> {
|
||||
ui.showErrorMessage(Core.bundle.format("connectfail", e2));
|
||||
ui.loadfrag.hide();
|
||||
});
|
||||
});
|
||||
});
|
||||
private void importFail(Throwable t){
|
||||
Core.app.post(() -> modError(t));
|
||||
}
|
||||
|
||||
private void githubImportBranch(String branch, String repo, Cons<HttpStatus> err){
|
||||
Core.net.httpGet("https://api.github.com/repos/" + repo + "/zipball/" + branch, loc -> {
|
||||
if(loc.getStatus() == HttpStatus.OK){
|
||||
if(loc.getHeader("Location") != null){
|
||||
Core.net.httpGet(loc.getHeader("Location"), result -> {
|
||||
if(result.getStatus() != HttpStatus.OK){
|
||||
err.get(result.getStatus());
|
||||
}else{
|
||||
public void githubImportMod(String repo, boolean isJava){
|
||||
githubImportMod(repo, isJava, null);
|
||||
}
|
||||
|
||||
public void githubImportMod(String repo, boolean isJava, @Nullable String release){
|
||||
modImportProgress = 0f;
|
||||
ui.loadfrag.show("@downloading");
|
||||
ui.loadfrag.setProgress(() -> modImportProgress);
|
||||
|
||||
if(isJava){
|
||||
githubImportJavaMod(repo, release);
|
||||
}else{
|
||||
Http.get(ghApi + "/repos/" + repo, res -> {
|
||||
var json = Jval.read(res.getResultAsString());
|
||||
String mainBranch = json.getString("default_branch");
|
||||
String language = json.getString("language", "<none>");
|
||||
|
||||
//this is a crude heuristic for class mods; only required for direct github import
|
||||
//TODO make a more reliable way to distinguish java mod repos
|
||||
if(language.equals("Java") || language.equals("Kotlin")){
|
||||
githubImportJavaMod(repo, release);
|
||||
}else{
|
||||
githubImportBranch(mainBranch, repo, release);
|
||||
}
|
||||
}, this::importFail);
|
||||
}
|
||||
}
|
||||
|
||||
private void githubImportJavaMod(String repo, @Nullable String release){
|
||||
//grab latest release
|
||||
Http.get(ghApi + "/repos/" + repo + "/releases/" + (release == null ? "latest" : release), res -> {
|
||||
var json = Jval.read(res.getResultAsString());
|
||||
var assets = json.get("assets").asArray();
|
||||
|
||||
//prioritize dexed jar, as that's what Sonnicon's mod template outputs
|
||||
var dexedAsset = assets.find(j -> j.getString("name").startsWith("dexed") && j.getString("name").endsWith(".jar"));
|
||||
var asset = dexedAsset == null ? assets.find(j -> j.getString("name").endsWith(".jar")) : dexedAsset;
|
||||
|
||||
if(asset != null){
|
||||
//grab actual file
|
||||
var url = asset.getString("browser_download_url");
|
||||
|
||||
Http.get(url, result -> handleMod(repo, result), this::importFail);
|
||||
}else{
|
||||
throw new ArcRuntimeException("No JAR file found in releases. Make sure you have a valid jar file in the mod's latest Github Release.");
|
||||
}
|
||||
}, this::importFail);
|
||||
}
|
||||
|
||||
private void githubImportBranch(String branch, String repo, @Nullable String release){
|
||||
if(release != null) {
|
||||
Http.get(ghApi + "/repos/" + repo + "/releases/" + release, res -> {
|
||||
String zipUrl = Jval.read(res.getResultAsString()).getString("zipball_url");
|
||||
Http.get(zipUrl, loc -> {
|
||||
if(loc.getHeader("Location") != null){
|
||||
Http.get(loc.getHeader("Location"), result -> {
|
||||
handleMod(repo, result);
|
||||
}
|
||||
}, t2 -> Core.app.post(() -> modError(t2)));
|
||||
}, this::importFail);
|
||||
}else{
|
||||
handleMod(repo, loc);
|
||||
}
|
||||
}, this::importFail);
|
||||
});
|
||||
}else{
|
||||
Http.get(ghApi + "/repos/" + repo + "/zipball/" + branch, loc -> {
|
||||
if(loc.getHeader("Location") != null){
|
||||
Http.get(loc.getHeader("Location"), result -> {
|
||||
handleMod(repo, result);
|
||||
}, this::importFail);
|
||||
}else{
|
||||
handleMod(repo, loc);
|
||||
}
|
||||
}else{
|
||||
err.get(loc.getStatus());
|
||||
}
|
||||
}, t2 -> Core.app.post(() -> modError(t2)));
|
||||
}, this::importFail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class PaletteDialog extends Dialog{
|
||||
for(int i = 0; i < playerColors.length; i++){
|
||||
Color color = playerColors[i];
|
||||
|
||||
ImageButton button = table.button(Tex.whiteui, Styles.clearTogglei, 34, () -> {
|
||||
ImageButton button = table.button(Tex.whiteui, Styles.squareTogglei, 34, () -> {
|
||||
cons.get(color);
|
||||
hide();
|
||||
}).size(48).get();
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.*;
|
||||
import mindustry.editor.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class PausedDialog extends BaseDialog{
|
||||
private MapProcessorsDialog processors = new MapProcessorsDialog();
|
||||
private SaveDialog save = new SaveDialog();
|
||||
private LoadDialog load = new LoadDialog();
|
||||
private boolean wasClient = false;
|
||||
private CustomRulesDialog rulesDialog = new CustomRulesDialog();
|
||||
|
||||
public PausedDialog(){
|
||||
super("@menu");
|
||||
shouldPause = true;
|
||||
|
||||
clearChildren();
|
||||
add(titleTable).growX().row();
|
||||
|
||||
stack(cont, new Table(t -> {
|
||||
t.bottom().left();
|
||||
t.button(Icon.book, () -> {
|
||||
Rules toEdit = Vars.state.rules.copy();
|
||||
rulesDialog.show(toEdit, () -> state.rules.copy());
|
||||
rulesDialog.hidden(() -> {
|
||||
//apply rule changes only once it is hidden
|
||||
Vars.state.rules = toEdit;
|
||||
Call.setRules(toEdit);
|
||||
});
|
||||
}).size(70f).tooltip("@customize").visible(() -> state.rules.allowEditRules && (net.server() || !net.active()));
|
||||
})).grow().row();
|
||||
|
||||
shown(this::rebuild);
|
||||
|
||||
addCloseListener();
|
||||
@@ -32,6 +53,12 @@ public class PausedDialog extends BaseDialog{
|
||||
float dw = 220f;
|
||||
cont.defaults().width(dw).height(55).pad(5f);
|
||||
|
||||
cont.button("@objective", Icon.info, () -> ui.fullText.show("@objective", state.rules.sector.preset.description))
|
||||
.visible(() -> state.rules.sector != null && state.rules.sector.preset != null && state.rules.sector.preset.description != null).padTop(-60f);
|
||||
|
||||
cont.button("@abandon", Icon.cancel, () -> ui.planet.abandonSectorConfirm(state.rules.sector, this::hide)).padTop(-60f)
|
||||
.disabled(b -> net.client()).visible(() -> state.rules.sector != null).row();
|
||||
|
||||
cont.button("@back", Icon.left, this::hide).name("back");
|
||||
cont.button("@settings", Icon.settings, ui.settings::show).name("settings");
|
||||
|
||||
@@ -43,21 +70,26 @@ public class PausedDialog extends BaseDialog{
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.button("@hostserver", Icon.host, () -> {
|
||||
//the button runs out of space when the editor button is added, so use the mobile text
|
||||
cont.button(state.isEditor() ? "@hostserver.mobile" : "@hostserver", Icon.host, () -> {
|
||||
if(net.server() && steam){
|
||||
platform.inviteFriends();
|
||||
}else{
|
||||
if(steam){
|
||||
ui.host.runHost();
|
||||
}else{
|
||||
ui.host.show();
|
||||
}
|
||||
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"));
|
||||
}).disabled(b -> !((steam && net.server()) || !net.active())).colspan(state.isEditor() ? 1 : 2).width(state.isEditor() ? dw : dw * 2 + 10f)
|
||||
.update(e -> e.setText(net.server() && steam ? "@invitefriends" : state.isEditor() ? "@hostserver.mobile" : "@hostserver"));
|
||||
|
||||
if(state.isEditor()){
|
||||
cont.button("@editor.worldprocessors", Icon.logic, () -> {
|
||||
hide();
|
||||
processors.show();
|
||||
});
|
||||
}
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.button("@quit", Icon.exit, this::showQuitConfirm).colspan(2).width(dw + 20f).update(s -> s.setText(control.saves.getCurrent() != null && control.saves.getCurrent().isAutosave() ? "@save.quit" : "@quit"));
|
||||
cont.button("@quit", Icon.exit, 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);
|
||||
@@ -93,21 +125,41 @@ public class PausedDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
void showQuitConfirm(){
|
||||
ui.showConfirm("@confirm", "@quit.confirm", () -> {
|
||||
wasClient = net.client();
|
||||
if(net.client()) netClient.disconnectQuietly();
|
||||
Runnable quit = () -> {
|
||||
runExitSave();
|
||||
hide();
|
||||
});
|
||||
};
|
||||
|
||||
if(confirmExit){
|
||||
ui.showConfirm("@confirm", "@quit.confirm", quit);
|
||||
}else{
|
||||
quit.run();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkPlaytest(){
|
||||
if(state.playtestingMap != null){
|
||||
//no exit save here
|
||||
var testing = state.playtestingMap;
|
||||
logic.reset();
|
||||
ui.editor.resumeAfterPlaytest(testing);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void runExitSave(){
|
||||
boolean wasClient = net.client();
|
||||
if(net.client()) netClient.disconnectQuietly();
|
||||
|
||||
if(state.isEditor() && !wasClient){
|
||||
ui.editor.resumeEditing();
|
||||
return;
|
||||
}else if(checkPlaytest()){
|
||||
return;
|
||||
}
|
||||
|
||||
if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || wasClient){
|
||||
if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || wasClient || state.gameOver || disableSave){
|
||||
logic.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ import arc.scene.actions.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.TextButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
@@ -30,85 +31,110 @@ import mindustry.ui.layout.TreeLayout.*;
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.gen.Tex.*;
|
||||
|
||||
public class ResearchDialog extends BaseDialog{
|
||||
public static boolean debugShowRequirements = false;
|
||||
|
||||
public final float nodeSize = Scl.scl(60f);
|
||||
public ObjectSet<TechTreeNode> nodes = new ObjectSet<>();
|
||||
public TechTreeNode root = new TechTreeNode(TechTree.root, null);
|
||||
public TechTreeNode root = new TechTreeNode(TechTree.roots.first(), null);
|
||||
public TechNode lastNode = root.node;
|
||||
public Rect bounds = new Rect();
|
||||
public ItemsDisplay itemDisplay;
|
||||
public View view;
|
||||
|
||||
public ItemSeq items;
|
||||
|
||||
private boolean showTechSelect;
|
||||
private boolean needsRebuild;
|
||||
|
||||
public ResearchDialog(){
|
||||
super("");
|
||||
|
||||
Events.on(ResetEvent.class, e -> {
|
||||
hide();
|
||||
});
|
||||
|
||||
Events.on(UnlockEvent.class, e -> {
|
||||
if(net.client() && !needsRebuild){
|
||||
needsRebuild = true;
|
||||
Core.app.post(() -> {
|
||||
needsRebuild = false;
|
||||
|
||||
checkNodes(root);
|
||||
view.hoverNode = null;
|
||||
treeLayout();
|
||||
view.rebuild();
|
||||
Core.scene.act();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
titleTable.remove();
|
||||
titleTable.clear();
|
||||
titleTable.top();
|
||||
titleTable.button(b -> {
|
||||
//TODO custom icon here.
|
||||
b.imageDraw(() -> root.node.icon()).padRight(8).size(iconMed);
|
||||
b.add().growX();
|
||||
b.label(() -> root.node.localizedName()).color(Pal.accent);
|
||||
b.add().growX();
|
||||
b.add().size(iconMed);
|
||||
}, () -> {
|
||||
new BaseDialog("@techtree.select"){{
|
||||
cont.pane(t -> {
|
||||
t.table(Tex.button, in -> {
|
||||
in.defaults().width(300f).height(60f);
|
||||
for(TechNode node : TechTree.roots){
|
||||
if(node.requiresUnlock && !node.content.unlockedHost() && node != getPrefRoot()) continue;
|
||||
|
||||
//TODO toggle
|
||||
in.button(node.localizedName(), node.icon(), Styles.flatTogglet, iconMed, () -> {
|
||||
if(node == lastNode){
|
||||
return;
|
||||
}
|
||||
|
||||
rebuildTree(node);
|
||||
hide();
|
||||
}).marginLeft(12f).checked(node == lastNode).row();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
addCloseButton();
|
||||
}}.show();
|
||||
}).visible(() -> showTechSelect = TechTree.roots.count(node -> !(node.requiresUnlock && !node.content.unlockedHost())) > 1).minWidth(300f);
|
||||
|
||||
margin(0f).marginBottom(8);
|
||||
cont.stack(view = new View(), itemDisplay = new ItemsDisplay()).grow();
|
||||
cont.stack(titleTable, view = new View(), itemDisplay = new ItemsDisplay()).grow();
|
||||
itemDisplay.visible(() -> !net.client());
|
||||
|
||||
titleTable.toFront();
|
||||
|
||||
shouldPause = true;
|
||||
|
||||
onResize(this::checkMargin);
|
||||
|
||||
shown(() -> {
|
||||
checkMargin();
|
||||
Core.app.post(this::checkMargin);
|
||||
|
||||
items = new ItemSeq(){
|
||||
//store sector item amounts for modifications
|
||||
ObjectMap<Sector, ItemSeq> cache = new ObjectMap<>();
|
||||
Planet currPlanet = ui.planet.isShown() ?
|
||||
ui.planet.state.planet :
|
||||
state.isCampaign() ? state.rules.sector.planet : null;
|
||||
|
||||
{
|
||||
//add global counts of each sector
|
||||
for(Planet planet : content.planets()){
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasSave() && sector.hasBase()){
|
||||
ItemSeq cached = sector.items();
|
||||
cache.put(sector, cached);
|
||||
cached.each((item, amount) -> {
|
||||
values[item.id] += Math.max(amount, 0);
|
||||
total += Math.max(amount, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//this is the only method that actually modifies the sequence itself.
|
||||
@Override
|
||||
public void add(Item item, int amount){
|
||||
//only have custom removal logic for when the sequence gets items taken out of it (e.g. research)
|
||||
if(amount < 0){
|
||||
//remove items from each sector's storage, one by one
|
||||
|
||||
//negate amount since it's being *removed* - this makes it positive
|
||||
amount = -amount;
|
||||
|
||||
//% that gets removed from each sector
|
||||
double percentage = (double)amount / get(item);
|
||||
int[] counter = {amount};
|
||||
cache.each((sector, seq) -> {
|
||||
if(counter[0] == 0) return;
|
||||
|
||||
//amount that will be removed
|
||||
int toRemove = Math.min((int)Math.ceil(percentage * seq.get(item)), counter[0]);
|
||||
|
||||
//actually remove it from the sector
|
||||
sector.removeItem(item, toRemove);
|
||||
seq.remove(item, toRemove);
|
||||
|
||||
counter[0] -= toRemove;
|
||||
});
|
||||
|
||||
//negate again to display correct number
|
||||
amount = -amount;
|
||||
}
|
||||
|
||||
super.add(item, amount);
|
||||
}
|
||||
};
|
||||
if(currPlanet != null && currPlanet.techTree != null){
|
||||
switchTree(currPlanet.techTree);
|
||||
}
|
||||
rebuildItems();
|
||||
|
||||
checkNodes(root);
|
||||
treeLayout();
|
||||
|
||||
view.hoverNode = null;
|
||||
view.infoTable.remove();
|
||||
view.infoTable.clear();
|
||||
});
|
||||
|
||||
hidden(ui.planet::setup);
|
||||
@@ -172,6 +198,109 @@ public class ResearchDialog extends BaseDialog{
|
||||
});
|
||||
}
|
||||
|
||||
void checkMargin(){
|
||||
if(Core.graphics.isPortrait() && showTechSelect){
|
||||
itemDisplay.marginTop(60f);
|
||||
}else{
|
||||
itemDisplay.marginTop(0f);
|
||||
}
|
||||
itemDisplay.invalidate();
|
||||
itemDisplay.layout();
|
||||
}
|
||||
|
||||
public void rebuildItems(){
|
||||
items = new ItemSeq(){
|
||||
//store sector item amounts for modifications
|
||||
ObjectMap<Sector, ItemSeq> cache = new ObjectMap<>();
|
||||
|
||||
{
|
||||
//first, find a planet associated with the current tech tree
|
||||
Planet rootPlanet = lastNode.planet != null ? lastNode.planet : content.planets().find(p -> p.techTree == lastNode);
|
||||
|
||||
//if there is no root, fall back to serpulo
|
||||
if(rootPlanet == null) rootPlanet = Planets.serpulo;
|
||||
|
||||
//add global counts of each sector
|
||||
for(Sector sector : rootPlanet.sectors){
|
||||
if(sector.hasBase()){
|
||||
ItemSeq cached = sector.items();
|
||||
cache.put(sector, cached);
|
||||
cached.each((item, amount) -> {
|
||||
values[item.id] += Math.max(amount, 0);
|
||||
total += Math.max(amount, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//this is the only method that actually modifies the sequence itself.
|
||||
@Override
|
||||
public void add(Item item, int amount){
|
||||
//only have custom removal logic for when the sequence gets items taken out of it (e.g. research)
|
||||
if(amount < 0){
|
||||
//remove items from each sector's storage, one by one
|
||||
|
||||
//negate amount since it's being *removed* - this makes it positive
|
||||
amount = -amount;
|
||||
|
||||
//% that gets removed from each sector
|
||||
double percentage = (double)amount / get(item);
|
||||
int[] counter = {amount};
|
||||
cache.each((sector, seq) -> {
|
||||
if(counter[0] == 0) return;
|
||||
|
||||
//amount that will be removed
|
||||
int toRemove = Math.min((int)Math.ceil(percentage * seq.get(item)), counter[0]);
|
||||
|
||||
//actually remove it from the sector
|
||||
sector.removeItem(item, toRemove);
|
||||
seq.remove(item, toRemove);
|
||||
|
||||
counter[0] -= toRemove;
|
||||
});
|
||||
|
||||
//negate again to display correct number
|
||||
amount = -amount;
|
||||
}
|
||||
|
||||
super.add(item, amount);
|
||||
}
|
||||
};
|
||||
|
||||
itemDisplay.rebuild(items);
|
||||
}
|
||||
|
||||
public @Nullable TechNode getPrefRoot(){
|
||||
Planet currPlanet = ui.planet.isShown() ?
|
||||
ui.planet.state.planet :
|
||||
state.isCampaign() ? state.rules.sector.planet : null;
|
||||
return currPlanet == null ? null : currPlanet.techTree;
|
||||
}
|
||||
|
||||
public void switchTree(TechNode node){
|
||||
if(lastNode == node || node == null) return;
|
||||
nodes.clear();
|
||||
root = new TechTreeNode(node, null);
|
||||
lastNode = node;
|
||||
view.rebuildAll();
|
||||
|
||||
rebuildItems();
|
||||
}
|
||||
|
||||
public void rebuildTree(TechNode node){
|
||||
switchTree(node);
|
||||
view.panX = 0f;
|
||||
view.panY = -200f;
|
||||
view.setScale(1f);
|
||||
|
||||
view.hoverNode = null;
|
||||
view.infoTable.remove();
|
||||
view.infoTable.clear();
|
||||
|
||||
checkNodes(root);
|
||||
treeLayout();
|
||||
}
|
||||
|
||||
void treeLayout(){
|
||||
float spacing = 20f;
|
||||
LayoutNode node = new LayoutNode(root, null);
|
||||
@@ -233,10 +362,10 @@ public class ResearchDialog extends BaseDialog{
|
||||
|
||||
void checkNodes(TechTreeNode node){
|
||||
boolean locked = locked(node.node);
|
||||
if(!locked) node.visible = true;
|
||||
if(!locked && (node.parent == null || node.parent.visible)) node.visible = true;
|
||||
node.selectable = selectable(node.node);
|
||||
for(TechTreeNode l : node.children){
|
||||
l.visible = !locked;
|
||||
l.visible = !locked && l.parent.visible;
|
||||
checkNodes(l);
|
||||
}
|
||||
|
||||
@@ -244,11 +373,12 @@ public class ResearchDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
boolean selectable(TechNode node){
|
||||
return node.content.unlocked() || !node.objectives.contains(i -> !i.complete());
|
||||
//there's a desync here as far as sectors go, since the client doesn't know about that, but I'm not too concerned
|
||||
return node.content.unlockedHost() || !node.objectives.contains(i -> !i.complete());
|
||||
}
|
||||
|
||||
boolean locked(TechNode node){
|
||||
return node.content.locked();
|
||||
return !node.content.unlockedHost();
|
||||
}
|
||||
|
||||
class LayoutNode extends TreeNode<LayoutNode>{
|
||||
@@ -273,11 +403,9 @@ public class ResearchDialog extends BaseDialog{
|
||||
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);
|
||||
}
|
||||
children = new TechTreeNode[node.children.size];
|
||||
for(int i = 0; i < children.length; i++){
|
||||
children[i] = new TechTreeNode(node.children.get(i), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,36 +417,48 @@ public class ResearchDialog extends BaseDialog{
|
||||
public Table infoTable = new Table();
|
||||
|
||||
{
|
||||
rebuildAll();
|
||||
}
|
||||
|
||||
public void rebuildAll(){
|
||||
clear();
|
||||
hoverNode = null;
|
||||
infoTable.clear();
|
||||
infoTable.touchable = Touchable.enabled;
|
||||
|
||||
for(TechTreeNode node : nodes){
|
||||
ImageButton button = new ImageButton(node.node.content.icon(Cicon.medium), Styles.nodei);
|
||||
ImageButton button = new ImageButton(node.node.content.uiIcon, Styles.nodei);
|
||||
button.resizeImage(32f);
|
||||
button.getImage().setScaling(Scaling.fit);
|
||||
button.visible(() -> node.visible);
|
||||
button.clicked(() -> {
|
||||
if(moved) return;
|
||||
if(!net.client()){
|
||||
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(Interp.fade);
|
||||
}
|
||||
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(Interp.fade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateRelative(float percentDelta){
|
||||
panX -= moveBy * percentDelta;
|
||||
}
|
||||
});
|
||||
@Override
|
||||
protected void updateRelative(float percentDelta){
|
||||
panX -= moveBy * percentDelta;
|
||||
}
|
||||
});
|
||||
}
|
||||
}else if(canSpend(node.node) && locked(node.node)){
|
||||
spend(node.node);
|
||||
}
|
||||
}else if(canSpend(node.node) && locked(node.node)){
|
||||
spend(node.node);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
button.hovered(() -> {
|
||||
if(!mobile && hoverNode != button && node.visible){
|
||||
hoverNode = button;
|
||||
@@ -335,20 +475,21 @@ public class ResearchDialog extends BaseDialog{
|
||||
button.userObject = node.node;
|
||||
button.setSize(nodeSize);
|
||||
button.update(() -> {
|
||||
button.setDisabled(net.client() && !mobile);
|
||||
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 : !selectable(node.node) || !canSpend(node.node) ? Tex.buttonRed : Tex.button;
|
||||
button.getStyle().up = !locked(node.node) ? Tex.buttonOver : !selectable(node.node) || (!canSpend(node.node) && !net.client()) ? Tex.buttonRed : Tex.button;
|
||||
|
||||
((TextureRegionDrawable)button.getStyle().imageUp).setRegion(node.selectable ? node.node.content.icon(Cicon.medium) : Icon.lock.getRegion());
|
||||
((TextureRegionDrawable)button.getStyle().imageUp).setRegion(node.selectable ? node.node.content.uiIcon : Icon.lock.getRegion());
|
||||
button.getImage().setColor(!locked(node.node) ? Color.white : node.selectable ? Color.gray : Pal.gray);
|
||||
button.getImage().setScaling(Scaling.bounded);
|
||||
button.getImage().layout();
|
||||
});
|
||||
addChild(button);
|
||||
}
|
||||
|
||||
if(mobile){
|
||||
tapped(() -> {
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
if(e == this){
|
||||
hoverNode = null;
|
||||
rebuild();
|
||||
@@ -374,7 +515,7 @@ public class ResearchDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
boolean canSpend(TechNode node){
|
||||
if(!selectable(node)) return false;
|
||||
if(!selectable(node) || net.client()) return false;
|
||||
|
||||
if(node.requirements.length == 0) return true;
|
||||
|
||||
@@ -390,6 +531,8 @@ public class ResearchDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
void spend(TechNode node){
|
||||
if(net.client()) return;
|
||||
|
||||
boolean complete = true;
|
||||
|
||||
boolean[] shine = new boolean[node.requirements.length];
|
||||
@@ -425,6 +568,7 @@ public class ResearchDialog extends BaseDialog{
|
||||
Core.scene.act();
|
||||
rebuild(shine);
|
||||
itemDisplay.rebuild(items, usedShine);
|
||||
checkMargin();
|
||||
}
|
||||
|
||||
void unlock(TechNode node){
|
||||
@@ -479,97 +623,107 @@ public class ResearchDialog extends BaseDialog{
|
||||
infoTable.table(b -> {
|
||||
b.margin(0).left().defaults().left();
|
||||
|
||||
if(selectable && (node.content.description != null || node.content.stats.toMap().size > 0)){
|
||||
b.button(Icon.info, Styles.cleari, () -> ui.content.show(node.content)).growY().width(50f);
|
||||
if(selectable){
|
||||
b.button(Icon.info, Styles.flati, () -> ui.content.show(node.content)).growY().width(50f);
|
||||
}
|
||||
b.add().grow();
|
||||
b.table(desc -> {
|
||||
desc.left().defaults().left();
|
||||
desc.add(selectable ? node.content.localizedName : "[accent]???");
|
||||
desc.row();
|
||||
if(locked(node)){
|
||||
if(locked(node) || (debugShowRequirements && !net.client())){
|
||||
|
||||
desc.table(t -> {
|
||||
t.left();
|
||||
if(selectable){
|
||||
if(net.client()){
|
||||
desc.add("@locked").color(Pal.remove);
|
||||
}else{
|
||||
desc.table(t -> {
|
||||
t.left();
|
||||
if(selectable){
|
||||
|
||||
//check if there is any progress, add research progress text
|
||||
if(Structs.contains(node.finishedRequirements, s -> s.amount > 0)){
|
||||
float sum = 0f, used = 0f;
|
||||
boolean shiny = false;
|
||||
//check if there is any progress, add research progress text
|
||||
if(Structs.contains(node.finishedRequirements, s -> s.amount > 0)){
|
||||
float sum = 0f, used = 0f;
|
||||
boolean shiny = false;
|
||||
|
||||
for(int i = 0; i < node.requirements.length; i++){
|
||||
sum += node.requirements[i].item.cost * node.requirements[i].amount;
|
||||
used += node.finishedRequirements[i].item.cost * node.finishedRequirements[i].amount;
|
||||
if(shine != null) shiny |= shine[i];
|
||||
}
|
||||
for(int i = 0; i < node.requirements.length; i++){
|
||||
sum += node.requirements[i].item.cost * node.requirements[i].amount;
|
||||
used += node.finishedRequirements[i].item.cost * node.finishedRequirements[i].amount;
|
||||
if(shine != null) shiny |= shine[i];
|
||||
}
|
||||
|
||||
Label label = t.add(Core.bundle.format("research.progress", Math.min((int)(used / sum * 100), 99))).left().get();
|
||||
|
||||
if(shiny){
|
||||
label.setColor(Pal.accent);
|
||||
label.actions(Actions.color(Color.lightGray, 0.75f, Interp.fade));
|
||||
}else{
|
||||
label.setColor(Color.lightGray);
|
||||
}
|
||||
|
||||
t.row();
|
||||
}
|
||||
|
||||
for(int i = 0; i < node.requirements.length; i++){
|
||||
ItemStack req = node.requirements[i];
|
||||
ItemStack completed = node.finishedRequirements[i];
|
||||
|
||||
//skip finished stacks
|
||||
if(req.amount <= completed.amount) continue;
|
||||
boolean shiny = shine != null && shine[i];
|
||||
|
||||
t.table(list -> {
|
||||
int reqAmount = req.amount - completed.amount;
|
||||
|
||||
list.left();
|
||||
list.image(req.item.icon(Cicon.small)).size(8 * 3).padRight(3);
|
||||
list.add(req.item.localizedName).color(Color.lightGray);
|
||||
Label label = list.label(() -> " " +
|
||||
UI.formatAmount(Math.min(items.get(req.item), reqAmount)) + " / "
|
||||
+ UI.formatAmount(reqAmount)).get();
|
||||
|
||||
Color targetColor = items.has(req.item) ? Color.lightGray : Color.scarlet;
|
||||
Label label = t.add(Core.bundle.format("research.progress", Math.min((int)(used / sum * 100), 99))).left().get();
|
||||
|
||||
if(shiny){
|
||||
label.setColor(Pal.accent);
|
||||
label.actions(Actions.color(targetColor, 0.75f, Interp.fade));
|
||||
label.actions(Actions.color(Color.lightGray, 0.75f, Interp.fade));
|
||||
}else{
|
||||
label.setColor(targetColor);
|
||||
label.setColor(Color.lightGray);
|
||||
}
|
||||
|
||||
}).fillX().left();
|
||||
t.row();
|
||||
}
|
||||
|
||||
for(int i = 0; i < node.requirements.length; i++){
|
||||
ItemStack req = node.requirements[i];
|
||||
ItemStack completed = node.finishedRequirements[i];
|
||||
|
||||
//skip finished stacks
|
||||
if(req.amount <= completed.amount && !debugShowRequirements) continue;
|
||||
boolean shiny = shine != null && shine[i];
|
||||
|
||||
t.table(list -> {
|
||||
int reqAmount = debugShowRequirements ? req.amount : req.amount - completed.amount;
|
||||
|
||||
list.left();
|
||||
list.image(req.item.uiIcon).size(8 * 3).padRight(3);
|
||||
list.add(req.item.localizedName).color(Color.lightGray);
|
||||
Label label = list.label(() -> " " +
|
||||
UI.formatAmount(Math.min(items.get(req.item), reqAmount)) + " / "
|
||||
+ UI.formatAmount(reqAmount)).get();
|
||||
|
||||
Color targetColor = items.has(req.item) ? Color.lightGray : Color.scarlet;
|
||||
|
||||
if(shiny){
|
||||
label.setColor(Pal.accent);
|
||||
label.actions(Actions.color(targetColor, 0.75f, Interp.fade));
|
||||
}else{
|
||||
label.setColor(targetColor);
|
||||
}
|
||||
|
||||
}).fillX().left();
|
||||
t.row();
|
||||
}
|
||||
}else if(node.objectives.size > 0){
|
||||
t.table(r -> {
|
||||
r.add("@complete").colspan(2).left();
|
||||
r.row();
|
||||
for(Objective o : node.objectives){
|
||||
if(o.complete()) continue;
|
||||
|
||||
r.add("> " + o.display()).color(Color.lightGray).left();
|
||||
r.image(o.complete() ? Icon.ok : Icon.cancel, o.complete() ? Color.lightGray : Color.scarlet).padLeft(3);
|
||||
r.row();
|
||||
}
|
||||
});
|
||||
t.row();
|
||||
}
|
||||
}else if(node.objectives.size > 0){
|
||||
t.table(r -> {
|
||||
r.add("@complete").colspan(2).left();
|
||||
r.row();
|
||||
for(Objective o : node.objectives){
|
||||
if(o.complete()) continue;
|
||||
|
||||
r.add("> " + o.display()).color(Color.lightGray).left();
|
||||
r.image(o.complete() ? Icon.ok : Icon.cancel, o.complete() ? Color.lightGray : Color.scarlet).padLeft(3);
|
||||
r.row();
|
||||
}
|
||||
});
|
||||
t.row();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}else{
|
||||
desc.add("@completed");
|
||||
}
|
||||
}).pad(9);
|
||||
|
||||
if(mobile && locked(node)){
|
||||
if(mobile && locked(node) && !net.client()){
|
||||
b.row();
|
||||
b.button("@research", Icon.ok, Styles.nodet, () -> spend(node))
|
||||
.disabled(i -> !canSpend(node)).growX().height(44f).colspan(3);
|
||||
b.button("@research", Icon.ok, new TextButtonStyle(){{
|
||||
disabled = Tex.button;
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
disabledFontColor = Color.gray;
|
||||
up = buttonOver;
|
||||
over = buttonDown;
|
||||
}}, () -> spend(node)).disabled(i -> !canSpend(node)).growX().height(44f).colspan(3);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -579,6 +733,10 @@ public class ResearchDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
addChild(infoTable);
|
||||
|
||||
checkMargin();
|
||||
Core.app.post(() -> checkMargin());
|
||||
|
||||
infoTable.pack();
|
||||
infoTable.act(Core.graphics.getDeltaTime());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
@@ -11,8 +12,10 @@ import arc.scene.ui.*;
|
||||
import arc.scene.ui.ImageButton.*;
|
||||
import arc.scene.ui.TextButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.scene.utils.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
@@ -20,28 +23,42 @@ import mindustry.input.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import java.util.regex.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class SchematicsDialog extends BaseDialog{
|
||||
private static final float tagh = 42f;
|
||||
private SchematicInfoDialog info = new SchematicInfoDialog();
|
||||
private Schematic firstSchematic;
|
||||
private String search = "";
|
||||
private TextField searchField;
|
||||
private Runnable rebuildPane = () -> {}, rebuildTags = () -> {};
|
||||
private Pattern ignoreSymbols = Pattern.compile("[`~!@#$%^&*()\\-_=+{}|;:'\",<.>/?]");
|
||||
private Seq<String> tags, selectedTags = new Seq<>();
|
||||
private boolean checkedTags;
|
||||
|
||||
public SchematicsDialog(){
|
||||
super("@schematics");
|
||||
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> ((Texture)t).setWrap(TextureWrap.repeat);
|
||||
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> t.setWrap(TextureWrap.repeat);
|
||||
|
||||
tags = Core.settings.getJson("schematic-tags", Seq.class, String.class, Seq::new);
|
||||
|
||||
shouldPause = true;
|
||||
addCloseButton();
|
||||
buttons.button("@schematic.import", Icon.download, this::showImport);
|
||||
makeButtonOverlay();
|
||||
shown(this::setup);
|
||||
onResize(this::setup);
|
||||
}
|
||||
|
||||
void setup(){
|
||||
if(!checkedTags){
|
||||
checkTags();
|
||||
checkedTags = true;
|
||||
}
|
||||
|
||||
search = "";
|
||||
Runnable[] rebuildPane = {null};
|
||||
|
||||
cont.top();
|
||||
cont.clear();
|
||||
@@ -51,19 +68,56 @@ public class SchematicsDialog extends BaseDialog{
|
||||
s.image(Icon.zoom);
|
||||
searchField = s.field(search, res -> {
|
||||
search = res;
|
||||
rebuildPane[0].run();
|
||||
rebuildPane.run();
|
||||
}).growX().get();
|
||||
searchField.setMessageText("@schematic.search");
|
||||
searchField.clicked(KeyCode.mouseRight, () -> {
|
||||
if(!search.isEmpty()){
|
||||
search = "";
|
||||
searchField.clearText();
|
||||
rebuildPane.run();
|
||||
}
|
||||
});
|
||||
}).fillX().padBottom(4);
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.table(in -> {
|
||||
in.left();
|
||||
in.add("@schematic.tags").padRight(4);
|
||||
|
||||
//tags (no scroll pane visible)
|
||||
in.pane(Styles.noBarPane, t -> {
|
||||
rebuildTags = () -> {
|
||||
t.clearChildren();
|
||||
t.left();
|
||||
|
||||
t.defaults().pad(2).height(tagh);
|
||||
for(var tag : tags){
|
||||
t.button(tag, Styles.togglet, () -> {
|
||||
if(selectedTags.contains(tag)){
|
||||
selectedTags.remove(tag);
|
||||
}else{
|
||||
selectedTags.add(tag);
|
||||
}
|
||||
rebuildPane.run();
|
||||
}).checked(selectedTags.contains(tag)).with(c -> c.getLabel().setWrap(false));
|
||||
}
|
||||
};
|
||||
rebuildTags.run();
|
||||
}).fillX().height(tagh).scrollY(false);
|
||||
|
||||
in.button(Icon.pencilSmall, this::showAllTags).size(tagh).pad(2).tooltip("@schematic.edittags");
|
||||
}).height(tagh).fillX();
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.pane(t -> {
|
||||
t.top();
|
||||
t.margin(20f);
|
||||
|
||||
t.update(() -> {
|
||||
if(Core.input.keyTap(Binding.chat) && Core.scene.getKeyboardFocus() == searchField && firstSchematic != null){
|
||||
if(!Vars.state.rules.schematicsAllowed){
|
||||
if(!state.rules.schematicsAllowed){
|
||||
ui.showInfo("@schematic.disabled");
|
||||
}else{
|
||||
control.input.useSchematic(firstSchematic);
|
||||
@@ -72,18 +126,20 @@ public class SchematicsDialog extends BaseDialog{
|
||||
}
|
||||
});
|
||||
|
||||
rebuildPane[0] = () -> {
|
||||
rebuildPane = () -> {
|
||||
int cols = Math.max((int)(Core.graphics.getWidth() / Scl.scl(230)), 1);
|
||||
|
||||
t.clear();
|
||||
int i = 0;
|
||||
String regex = "[`~!@#$%^&*()-_=+{}|;:'\",<.>/?]";
|
||||
String searchString = search.toLowerCase().replaceAll(regex, " ");
|
||||
String searchString = ignoreSymbols.matcher(search.toLowerCase()).replaceAll("");
|
||||
|
||||
firstSchematic = null;
|
||||
|
||||
for(Schematic s : schematics.all()){
|
||||
if(!search.isEmpty() && !s.name().toLowerCase().replaceAll(regex, " ").contains(searchString)) continue;
|
||||
//make sure *tags* fit
|
||||
if(selectedTags.any() && !s.labels.containsAll(selectedTags)) continue;
|
||||
//make sure search fits
|
||||
if(!search.isEmpty() && !ignoreSymbols.matcher(s.name().toLowerCase()).replaceAll("").contains(searchString)) continue;
|
||||
if(firstSchematic == null) firstSchematic = s;
|
||||
|
||||
Button[] sel = {null};
|
||||
@@ -94,64 +150,26 @@ public class SchematicsDialog extends BaseDialog{
|
||||
buttons.left();
|
||||
buttons.defaults().size(50f);
|
||||
|
||||
ImageButtonStyle style = Styles.clearPartiali;
|
||||
ImageButtonStyle style = Styles.emptyi;
|
||||
|
||||
buttons.button(Icon.info, style, () -> {
|
||||
showInfo(s);
|
||||
});
|
||||
|
||||
buttons.button(Icon.upload, style, () -> {
|
||||
showExport(s);
|
||||
});
|
||||
|
||||
buttons.button(Icon.pencil, style, () -> {
|
||||
new Dialog("@schematic.rename"){{
|
||||
cont.margin(30).add("@name").padRight(6f);
|
||||
TextField nameField = cont.field(s.name(), null).size(400f, 55f).addInputDialog().get();
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.margin(30).add("@editor.description").padRight(6f);
|
||||
TextField descripionField = cont.area(s.description(), Styles.areaField, t -> {}).size(400f, 140f).addInputDialog().get();
|
||||
|
||||
Runnable accept = () -> {
|
||||
s.tags.put("name", nameField.getText());
|
||||
s.tags.put("description", descripionField.getText());
|
||||
s.save();
|
||||
hide();
|
||||
rebuildPane[0].run();
|
||||
};
|
||||
|
||||
buttons.defaults().size(120, 54).pad(4);
|
||||
buttons.button("@ok", accept).disabled(b -> nameField.getText().isEmpty());
|
||||
buttons.button("@cancel", this::hide);
|
||||
|
||||
keyDown(KeyCode.enter, () -> {
|
||||
if(!nameField.getText().isEmpty() && Core.scene.getKeyboardFocus() != descripionField){
|
||||
accept.run();
|
||||
}
|
||||
});
|
||||
keyDown(KeyCode.escape, this::hide);
|
||||
keyDown(KeyCode.back, this::hide);
|
||||
show();
|
||||
}};
|
||||
});
|
||||
buttons.button(Icon.info, style, () -> showInfo(s)).tooltip("@info.title");
|
||||
buttons.button(Icon.upload, style, () -> showExport(s)).tooltip("@editor.export");
|
||||
buttons.button(Icon.pencil, style, () -> showEdit(s)).tooltip("@schematic.edit");
|
||||
|
||||
if(s.hasSteamID()){
|
||||
buttons.button(Icon.link, style, () -> platform.viewListing(s));
|
||||
buttons.button(Icon.link, style, () -> platform.viewListing(s)).tooltip("@view.workshop");
|
||||
}else{
|
||||
buttons.button(Icon.trash, style, () -> {
|
||||
if(s.mod != null){
|
||||
ui.showInfo(Core.bundle.format("mod.item.remove", s.mod.meta.displayName()));
|
||||
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();
|
||||
rebuildPane.run();
|
||||
});
|
||||
}
|
||||
});
|
||||
}).tooltip("@save.delete");
|
||||
}
|
||||
|
||||
}).growX().height(50f);
|
||||
b.row();
|
||||
b.stack(new SchematicImage(s).setScaling(Scaling.fit), new Table(n -> {
|
||||
@@ -167,14 +185,14 @@ public class SchematicsDialog extends BaseDialog{
|
||||
if(state.isMenu()){
|
||||
showInfo(s);
|
||||
}else{
|
||||
if(!Vars.state.rules.schematicsAllowed){
|
||||
if(!state.rules.schematicsAllowed){
|
||||
ui.showInfo("@schematic.disabled");
|
||||
}else{
|
||||
control.input.useSchematic(s);
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}).pad(4).style(Styles.cleari).get();
|
||||
}).pad(4).style(Styles.flati).get();
|
||||
|
||||
sel[0].getStyle().up = Tex.pane;
|
||||
|
||||
@@ -184,12 +202,16 @@ public class SchematicsDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
if(firstSchematic == null){
|
||||
t.add("@none");
|
||||
if(!searchString.isEmpty() || selectedTags.any()){
|
||||
t.add("@none.found");
|
||||
}else{
|
||||
t.add("@none").color(Color.lightGray);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rebuildPane[0].run();
|
||||
}).get().setScrollingDisabled(true, false);
|
||||
rebuildPane.run();
|
||||
}).grow().scrollX(false);
|
||||
}
|
||||
|
||||
public void showInfo(Schematic schematic){
|
||||
@@ -197,11 +219,11 @@ public class SchematicsDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
public void showImport(){
|
||||
BaseDialog dialog = new BaseDialog("@editor.export");
|
||||
BaseDialog dialog = new BaseDialog("@editor.import");
|
||||
dialog.cont.pane(p -> {
|
||||
p.margin(10f);
|
||||
p.table(Tex.button, t -> {
|
||||
TextButtonStyle style = Styles.cleart;
|
||||
TextButtonStyle style = Styles.flatt;
|
||||
t.defaults().size(280f, 60f).left();
|
||||
t.row();
|
||||
t.button("@schematic.copy.import", Icon.copy, style, () -> {
|
||||
@@ -212,6 +234,7 @@ public class SchematicsDialog extends BaseDialog{
|
||||
schematics.add(s);
|
||||
setup();
|
||||
ui.showInfoFade("@schematic.saved");
|
||||
checkTags(s);
|
||||
showInfo(s);
|
||||
}catch(Throwable e){
|
||||
ui.showException(e);
|
||||
@@ -227,6 +250,7 @@ public class SchematicsDialog extends BaseDialog{
|
||||
schematics.add(s);
|
||||
setup();
|
||||
showInfo(s);
|
||||
checkTags(s);
|
||||
}catch(Exception e){
|
||||
ui.showException(e);
|
||||
}
|
||||
@@ -248,9 +272,9 @@ public class SchematicsDialog extends BaseDialog{
|
||||
public void showExport(Schematic s){
|
||||
BaseDialog dialog = new BaseDialog("@editor.export");
|
||||
dialog.cont.pane(p -> {
|
||||
p.margin(10f);
|
||||
p.table(Tex.button, t -> {
|
||||
TextButtonStyle style = Styles.cleart;
|
||||
p.margin(10f);
|
||||
p.table(Tex.button, t -> {
|
||||
TextButtonStyle style = Styles.flatt;
|
||||
t.defaults().size(280f, 60f).left();
|
||||
if(steam && !s.hasSteamID()){
|
||||
t.button("@schematic.shareworkshop", Icon.book, style,
|
||||
@@ -275,18 +299,384 @@ public class SchematicsDialog extends BaseDialog{
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public void focusSearchField(){
|
||||
if(searchField == null) return;
|
||||
public void showEdit(Schematic s){
|
||||
new BaseDialog("@schematic.edit"){{
|
||||
setFillParent(true);
|
||||
addCloseListener();
|
||||
|
||||
Core.scene.setKeyboardFocus(searchField);
|
||||
cont.margin(30);
|
||||
|
||||
cont.add("@schematic.tags").padRight(6f);
|
||||
cont.table(tags -> buildTags(s, tags, false)).maxWidth(400f).fillX().left().row();
|
||||
|
||||
cont.margin(30).add("@name").padRight(6f);
|
||||
TextField nameField = cont.field(s.name(), null).size(400f, 55f).left().get();
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.margin(30).add("@editor.description").padRight(6f);
|
||||
TextField descField = cont.area(s.description(), Styles.areaField, t -> {}).size(400f, 140f).left().get();
|
||||
|
||||
Runnable accept = () -> {
|
||||
s.tags.put("name", nameField.getText());
|
||||
s.tags.put("description", descField.getText());
|
||||
s.save();
|
||||
hide();
|
||||
rebuildPane.run();
|
||||
};
|
||||
|
||||
buttons.defaults().size(210f, 64f).pad(4);
|
||||
buttons.button("@ok", Icon.ok, accept).disabled(b -> nameField.getText().isEmpty());
|
||||
buttons.button("@cancel", Icon.cancel, this::hide);
|
||||
|
||||
keyDown(KeyCode.enter, () -> {
|
||||
if(!nameField.getText().isEmpty() && Core.scene.getKeyboardFocus() != descField){
|
||||
accept.run();
|
||||
}
|
||||
});
|
||||
}}.show();
|
||||
}
|
||||
|
||||
//adds all new tags to the global list of tags
|
||||
//alternatively, unknown tags could be discarded on import?
|
||||
void checkTags(){
|
||||
ObjectSet<String> encountered = new ObjectSet<>();
|
||||
encountered.addAll(tags);
|
||||
for(Schematic s : schematics.all()){
|
||||
for(var tag : s.labels){
|
||||
if(encountered.add(tag)){
|
||||
tags.add(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//adds any new tags found to the global tag list
|
||||
//TODO remove tags from it instead?
|
||||
void checkTags(Schematic s){
|
||||
boolean any = false;
|
||||
for(var tag : s.labels){
|
||||
if(!tags.contains(tag)){
|
||||
tags.add(tag);
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if(any){
|
||||
rebuildTags.run();
|
||||
}
|
||||
}
|
||||
|
||||
void tagsChanged(){
|
||||
rebuildTags.run();
|
||||
if(selectedTags.any()){
|
||||
rebuildPane.run();
|
||||
}
|
||||
|
||||
Core.settings.putJson("schematic-tags", String.class, tags);
|
||||
}
|
||||
|
||||
void addTag(Schematic s, String tag){
|
||||
s.labels.add(tag);
|
||||
s.save();
|
||||
tagsChanged();
|
||||
}
|
||||
|
||||
void removeTag(Schematic s, String tag){
|
||||
s.labels.remove(tag);
|
||||
s.save();
|
||||
tagsChanged();
|
||||
}
|
||||
|
||||
//shows a dialog for creating a new tag
|
||||
void showNewTag(Cons<String> result){
|
||||
ui.showTextInput("@schematic.addtag", "", "", out -> {
|
||||
if(tags.contains(out)){
|
||||
ui.showInfo("@schematic.tagexists");
|
||||
}else{
|
||||
tags.add(out);
|
||||
tagsChanged();
|
||||
result.get(out);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void showNewIconTag(Cons<String> cons){
|
||||
new Dialog(){{
|
||||
closeOnBack();
|
||||
setFillParent(true);
|
||||
|
||||
//TODO: use IconSelectDialog
|
||||
cont.pane(t -> {
|
||||
resized(true, () -> {
|
||||
t.clearChildren();
|
||||
t.marginRight(19f).marginLeft(12f);
|
||||
t.defaults().size(48f);
|
||||
|
||||
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
|
||||
|
||||
int i = 0;
|
||||
for(String icon : accessibleIcons){
|
||||
String out = (char)Iconc.codes.get(icon) + "";
|
||||
if(tags.contains(out)) continue;
|
||||
|
||||
t.button(Icon.icons.get(icon), Styles.flati, iconMed, () -> {
|
||||
tags.add(out);
|
||||
tagsChanged();
|
||||
cons.get(out);
|
||||
hide();
|
||||
});
|
||||
|
||||
if(++i % cols == 0) t.row();
|
||||
}
|
||||
|
||||
for(ContentType ctype : defaultContentIcons){
|
||||
var all = content.getBy(ctype).<UnlockableContent>as().select(u -> !u.isHidden() && u.unlockedNow() && u.hasEmoji());
|
||||
|
||||
t.row();
|
||||
if(all.count(u -> !tags.contains(u.emoji())) > 0) t.image().colspan(cols).growX().width(Float.NEGATIVE_INFINITY).height(3f).color(Pal.accent);
|
||||
t.row();
|
||||
|
||||
i = 0;
|
||||
for(UnlockableContent u : all){
|
||||
if(tags.contains(u.emoji())) continue;
|
||||
t.button(new TextureRegionDrawable(u.uiIcon), Styles.flati, iconMed, () -> {
|
||||
String out = u.emoji() + "";
|
||||
|
||||
tags.add(out);
|
||||
tagsChanged();
|
||||
cons.get(out);
|
||||
|
||||
hide();
|
||||
}).tooltip(u.localizedName);
|
||||
|
||||
if(++i % cols == 0) t.row();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).scrollX(false);
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
void showAllTags(){
|
||||
var dialog = new BaseDialog("@schematic.edittags");
|
||||
dialog.addCloseButton();
|
||||
Runnable[] rebuild = {null};
|
||||
dialog.cont.pane(p -> {
|
||||
rebuild[0] = () -> {
|
||||
p.clearChildren();
|
||||
p.margin(12f).defaults().fillX().left();
|
||||
|
||||
float sum = 0f;
|
||||
Table current = new Table().left();
|
||||
|
||||
for(var tag : tags){
|
||||
float si = 40f;
|
||||
|
||||
var next = new Table(Tex.whiteui, n -> {
|
||||
n.setColor(Pal.gray);
|
||||
n.margin(5f);
|
||||
|
||||
n.table(move -> {
|
||||
|
||||
//move up
|
||||
move.button(Icon.upOpen, Styles.emptyi, () -> {
|
||||
int idx = tags.indexOf(tag);
|
||||
if(idx > 0){
|
||||
tags.swap(idx, idx - 1);
|
||||
tagsChanged();
|
||||
rebuild[0].run();
|
||||
}
|
||||
}).size(si).tooltip("@editor.moveup").row();
|
||||
//move down
|
||||
move.button(Icon.downOpen, Styles.emptyi, () -> {
|
||||
int idx = tags.indexOf(tag);
|
||||
if(idx < tags.size - 1){
|
||||
tags.swap(idx, idx + 1);
|
||||
tagsChanged();
|
||||
rebuild[0].run();
|
||||
}
|
||||
}).size(si).tooltip("@editor.movedown");
|
||||
}).fillY();
|
||||
|
||||
n.table(t -> {
|
||||
t.add(tag).left().row();
|
||||
t.add(Core.bundle.format("schematic.tagged", schematics.all().count(s -> s.labels.contains(tag)))).left()
|
||||
.update(b -> b.setColor(b.hasMouse() ? Pal.accent : Color.lightGray)).get().clicked(() -> {
|
||||
dialog.hide();
|
||||
selectedTags.clear().add(tag);
|
||||
rebuildTags.run();
|
||||
rebuildPane.run();
|
||||
});
|
||||
}).growX().fillY();
|
||||
|
||||
n.table(b -> {
|
||||
b.margin(2);
|
||||
|
||||
//rename tag
|
||||
b.button(Icon.pencil, Styles.emptyi, () -> {
|
||||
ui.showTextInput("@schematic.renametag", "@name", tag, result -> {
|
||||
//same tag, nothing was renamed
|
||||
if(result.equals(tag)) return;
|
||||
|
||||
if(tags.contains(result)){
|
||||
ui.showInfo("@schematic.tagexists");
|
||||
}else{
|
||||
for(Schematic s : schematics.all()){
|
||||
if(s.labels.any()){
|
||||
s.labels.replace(tag, result);
|
||||
s.save();
|
||||
}
|
||||
}
|
||||
selectedTags.replace(tag, result);
|
||||
tags.replace(tag, result);
|
||||
tagsChanged();
|
||||
rebuild[0].run();
|
||||
}
|
||||
});
|
||||
}).size(si).tooltip("@schematic.renametag").row();
|
||||
//delete tag
|
||||
b.button(Icon.trash, Styles.emptyi, () -> {
|
||||
ui.showConfirm("@schematic.tagdelconfirm", () -> {
|
||||
for(Schematic s : schematics.all()){
|
||||
if(s.labels.any()){
|
||||
s.labels.remove(tag);
|
||||
s.save();
|
||||
}
|
||||
}
|
||||
selectedTags.remove(tag);
|
||||
tags.remove(tag);
|
||||
tagsChanged();
|
||||
rebuildPane.run();
|
||||
rebuild[0].run();
|
||||
});
|
||||
}).size(si).tooltip("@save.delete");
|
||||
}).fillY();
|
||||
});
|
||||
|
||||
next.pack();
|
||||
float w = next.getWidth() + Scl.scl(9f);
|
||||
|
||||
if(w + sum >= Core.graphics.getWidth() * 0.9f){
|
||||
p.add(current).row();
|
||||
current = new Table();
|
||||
current.left();
|
||||
sum = 0;
|
||||
}
|
||||
|
||||
current.add(next).minWidth(210).pad(4);
|
||||
|
||||
sum += w;
|
||||
}
|
||||
|
||||
if(sum > 0){
|
||||
p.add(current).row();
|
||||
}
|
||||
|
||||
p.table(t -> {
|
||||
t.left().defaults().fillX().height(tagh).pad(2);
|
||||
|
||||
t.button("@schematic.texttag", Icon.add, () -> showNewTag(res -> rebuild[0].run())).wrapLabel(false).get().getLabelCell().padLeft(5);
|
||||
t.button("@schematic.icontag", Icon.add, () -> showNewIconTag(res -> rebuild[0].run())).wrapLabel(false).get().getLabelCell().padLeft(5);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
resized(true, rebuild[0]);
|
||||
}).scrollX(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
void buildTags(Schematic schem, Table t){
|
||||
buildTags(schem, t, true);
|
||||
}
|
||||
|
||||
void buildTags(Schematic schem, Table t, boolean name){
|
||||
t.clearChildren();
|
||||
t.left();
|
||||
|
||||
//sort by order in the main target array. the complexity of this is probably awful
|
||||
schem.labels.sort(s -> tags.indexOf(s));
|
||||
|
||||
if(name) t.add("@schematic.tags").padRight(4);
|
||||
t.pane(s -> {
|
||||
s.left();
|
||||
s.defaults().pad(3).height(tagh);
|
||||
for(var tag : schem.labels){
|
||||
s.table(Tex.button, i -> {
|
||||
i.add(tag).padRight(4).height(tagh).labelAlign(Align.center);
|
||||
i.button(Icon.cancelSmall, Styles.emptyi, () -> {
|
||||
removeTag(schem, tag);
|
||||
buildTags(schem, t, name);
|
||||
}).size(tagh).padRight(-9f).padLeft(-9f);
|
||||
});
|
||||
}
|
||||
|
||||
}).fillX().left().height(tagh).scrollY(false);
|
||||
|
||||
t.button(Icon.addSmall, () -> {
|
||||
var dialog = new BaseDialog("@schematic.addtag");
|
||||
dialog.addCloseButton();
|
||||
dialog.cont.pane(p -> resized(true, () -> {
|
||||
p.clearChildren();
|
||||
p.defaults().fillX().left();
|
||||
|
||||
float sum = 0f;
|
||||
Table current = new Table().left();
|
||||
for(var tag : tags){
|
||||
if(schem.labels.contains(tag)) continue;
|
||||
|
||||
var next = Elem.newButton(tag, () -> {
|
||||
addTag(schem, tag);
|
||||
buildTags(schem, t, name);
|
||||
dialog.hide();
|
||||
});
|
||||
next.getLabel().setWrap(false);
|
||||
|
||||
next.pack();
|
||||
float w = next.getPrefWidth() + Scl.scl(6f);
|
||||
|
||||
if(w + sum >= Core.graphics.getWidth() * (Core.graphics.isPortrait() ? 1f : 0.8f)){
|
||||
p.add(current).row();
|
||||
current = new Table();
|
||||
current.left();
|
||||
current.add(next).height(tagh).pad(2);
|
||||
sum = 0;
|
||||
}else{
|
||||
current.add(next).height(tagh).pad(2);
|
||||
}
|
||||
|
||||
sum += w;
|
||||
}
|
||||
|
||||
if(sum > 0){
|
||||
p.add(current).row();
|
||||
}
|
||||
|
||||
Cons<String> handleTag = res -> {
|
||||
dialog.hide();
|
||||
addTag(schem, res);
|
||||
buildTags(schem, t, name);
|
||||
};
|
||||
|
||||
p.row();
|
||||
|
||||
p.table(v -> {
|
||||
v.left().defaults().fillX().height(tagh).pad(2);
|
||||
v.button("@schematic.texttag", Icon.add, () -> showNewTag(handleTag)).wrapLabel(false).get().getLabelCell().padLeft(4);
|
||||
v.button("@schematic.icontag", Icon.add, () -> showNewIconTag(handleTag)).wrapLabel(false).get().getLabelCell().padLeft(4);
|
||||
});
|
||||
}));
|
||||
dialog.show();
|
||||
}).size(tagh).tooltip("@schematic.addtag");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog show(){
|
||||
super.show();
|
||||
|
||||
if(Core.app.isDesktop()){
|
||||
focusSearchField();
|
||||
if(Core.app.isDesktop() && searchField != null){
|
||||
Core.scene.setKeyboardFocus(searchField);
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -298,6 +688,7 @@ public class SchematicsDialog extends BaseDialog{
|
||||
public Color borderColor = Pal.gray;
|
||||
|
||||
private Schematic schematic;
|
||||
private Texture lastTexture;
|
||||
boolean set;
|
||||
|
||||
public SchematicImage(Schematic s){
|
||||
@@ -320,6 +711,8 @@ public class SchematicsDialog extends BaseDialog{
|
||||
if(!set){
|
||||
Core.app.post(this::setPreview);
|
||||
set = true;
|
||||
}else if(lastTexture != null && lastTexture.isDisposed()){
|
||||
set = wasSet = false;
|
||||
}
|
||||
|
||||
Texture background = Core.assets.get("sprites/schematic-background.png", Texture.class);
|
||||
@@ -346,37 +739,36 @@ public class SchematicsDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
private void setPreview(){
|
||||
TextureRegionDrawable draw = new TextureRegionDrawable(new TextureRegion(schematics.getPreview(schematic)));
|
||||
TextureRegionDrawable draw = new TextureRegionDrawable(new TextureRegion(lastTexture = schematics.getPreview(schematic)));
|
||||
setDrawable(draw);
|
||||
setScaling(Scaling.fit);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SchematicInfoDialog extends BaseDialog{
|
||||
public class SchematicInfoDialog extends BaseDialog{
|
||||
|
||||
SchematicInfoDialog(){
|
||||
super("");
|
||||
setFillParent(true);
|
||||
addCloseButton();
|
||||
addCloseListener();
|
||||
}
|
||||
|
||||
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();
|
||||
cont.add(Core.bundle.format("schematic.info", schem.width, schem.height, schem.tiles.size)).color(Color.lightGray).row();
|
||||
cont.table(tags -> buildTags(schem, tags)).fillX().left().row();
|
||||
cont.add(new SchematicImage(schem)).maxSize(800f).row();
|
||||
|
||||
ItemSeq arr = schem.requirements();
|
||||
cont.table(r -> {
|
||||
int i = 0;
|
||||
for(ItemStack s : arr){
|
||||
r.image(s.item.icon(Cicon.small)).left();
|
||||
r.image(s.item.uiIcon).left().size(iconMed);
|
||||
r.label(() -> {
|
||||
Building core = player.core();
|
||||
if(core == null || state.rules.infiniteResources || core.items.has(s.item, s.amount)) return "[lightgray]" + s.amount + "";
|
||||
if(core == null || state.isMenu() || state.rules.infiniteResources || core.items.has(s.item, s.amount)) return "[lightgray]" + s.amount + "";
|
||||
return (core.items.has(s.item, s.amount) ? "[lightgray]" : "[scarlet]") + Math.min(core.items.get(s.item), s.amount) + "[lightgray]/" + s.amount;
|
||||
}).padLeft(2).left().padRight(4);
|
||||
|
||||
@@ -406,7 +798,13 @@ public class SchematicsDialog extends BaseDialog{
|
||||
});
|
||||
}
|
||||
|
||||
buttons.clearChildren();
|
||||
buttons.defaults().size(Core.graphics.isPortrait() ? 150f : 210f, 64f);
|
||||
buttons.button("@back", Icon.left, this::hide);
|
||||
buttons.button("@editor.export", Icon.upload, () -> showExport(schem));
|
||||
buttons.button("@edit", Icon.edit, () -> showEdit(schem));
|
||||
|
||||
show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.input.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.TextButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
@@ -15,7 +17,6 @@ import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.content.TechTree.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
@@ -28,42 +29,37 @@ import java.io.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.net;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class SettingsMenuDialog extends SettingsDialog{
|
||||
public class SettingsMenuDialog extends BaseDialog{
|
||||
public SettingsTable graphics;
|
||||
public SettingsTable game;
|
||||
public SettingsTable sound;
|
||||
public SettingsTable main;
|
||||
|
||||
private Table prefs;
|
||||
private Table menu;
|
||||
private BaseDialog dataDialog;
|
||||
private boolean wasPaused;
|
||||
private Seq<SettingsCategory> categories = new Seq<>();
|
||||
|
||||
public SettingsMenuDialog(){
|
||||
hidden(() -> {
|
||||
Sounds.back.play();
|
||||
if(state.isGame()){
|
||||
if(!wasPaused || net.active())
|
||||
state.set(State.playing);
|
||||
}
|
||||
});
|
||||
super(bundle.get("settings", "Settings"));
|
||||
addCloseButton();
|
||||
|
||||
cont.add(main = new SettingsTable());
|
||||
shouldPause = true;
|
||||
|
||||
shown(() -> {
|
||||
back();
|
||||
if(state.isGame()){
|
||||
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);
|
||||
onResize(() -> {
|
||||
graphics.rebuild();
|
||||
sound.rebuild();
|
||||
game.rebuild();
|
||||
updateScrollFocus();
|
||||
});
|
||||
|
||||
cont.clearChildren();
|
||||
cont.remove();
|
||||
@@ -89,7 +85,7 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
|
||||
dataDialog.cont.table(Tex.button, t -> {
|
||||
t.defaults().size(280f, 60f).left();
|
||||
TextButtonStyle style = Styles.cleart;
|
||||
TextButtonStyle style = Styles.flatt;
|
||||
|
||||
t.button("@settings.cleardata", Icon.trash, style, () -> ui.showConfirm("@confirm", "@settings.clearall.confirm", () -> {
|
||||
ObjectMap<String, Object> map = new ObjectMap<>();
|
||||
@@ -146,7 +142,7 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for(var slot : control.saves.getSaveSlots().copy()){
|
||||
if(slot.isSector()){
|
||||
slot.delete();
|
||||
@@ -184,6 +180,8 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
t.button("@data.import", Icon.download, style, () -> ui.showConfirm("@confirm", "@data.import.confirm", () -> platform.showFileChooser(true, "zip", file -> {
|
||||
try{
|
||||
importData(file);
|
||||
control.saves.resetSave();
|
||||
state = new GameState();
|
||||
Core.app.exit();
|
||||
}catch(IllegalArgumentException e){
|
||||
ui.showErrorMessage("@data.invalid");
|
||||
@@ -214,37 +212,20 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
platform.shareFile(logs);
|
||||
}else{
|
||||
platform.showFileChooser(false, "txt", file -> {
|
||||
file.writeString(getLogs());
|
||||
app.post(() -> ui.showInfo("@crash.exported"));
|
||||
try{
|
||||
file.writeBytes(getLogs().getBytes(Strings.utf8));
|
||||
app.post(() -> ui.showInfo("@crash.exported"));
|
||||
}catch(Throwable e){
|
||||
ui.showException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).marginLeft(4);
|
||||
});
|
||||
|
||||
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();
|
||||
pane(prefs).grow().top();
|
||||
row();
|
||||
add(buttons).fillX();
|
||||
|
||||
@@ -266,39 +247,76 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/** Adds a custom settings category, with the icon being the specified region. */
|
||||
public void addCategory(String name, @Nullable String region, Cons<SettingsTable> builder){
|
||||
categories.add(new SettingsCategory(name, region == null ? null : new TextureRegionDrawable(atlas.find(region)), builder));
|
||||
}
|
||||
|
||||
/** Adds a custom settings category, for use in mods. The specified consumer should add all relevant mod settings to the table. */
|
||||
public void addCategory(String name, @Nullable Drawable icon, Cons<SettingsTable> builder){
|
||||
categories.add(new SettingsCategory(name, icon, builder));
|
||||
}
|
||||
|
||||
/** Adds a custom settings category, for use in mods. The specified consumer should add all relevant mod settings to the table. */
|
||||
public void addCategory(String name, Cons<SettingsTable> builder){
|
||||
addCategory(name, (Drawable)null, builder);
|
||||
}
|
||||
|
||||
public Seq<SettingsCategory> getCategories(){
|
||||
return categories;
|
||||
}
|
||||
|
||||
void rebuildMenu(){
|
||||
menu.clearChildren();
|
||||
|
||||
TextButtonStyle style = Styles.cleart;
|
||||
TextButtonStyle style = Styles.flatt;
|
||||
|
||||
float marg = 8f, isize = iconMed;
|
||||
|
||||
menu.defaults().size(300f, 60f);
|
||||
menu.button("@settings.game", style, () -> visible(0));
|
||||
menu.row();
|
||||
menu.button("@settings.graphics", style, () -> visible(1));
|
||||
menu.row();
|
||||
menu.button("@settings.sound", style, () -> visible(2));
|
||||
menu.row();
|
||||
menu.button("@settings.language", style, ui.language::show);
|
||||
menu.button("@settings.game", Icon.settings, style, isize, () -> visible(0)).marginLeft(marg).row();
|
||||
menu.button("@settings.graphics", Icon.image, style, isize, () -> visible(1)).marginLeft(marg).row();
|
||||
menu.button("@settings.sound", Icon.filters, style, isize, () -> visible(2)).marginLeft(marg).row();
|
||||
menu.button("@settings.language", Icon.chat, style, isize, ui.language::show).marginLeft(marg).row();
|
||||
if(!mobile || Core.settings.getBool("keyboard")){
|
||||
menu.row();
|
||||
menu.button("@settings.controls", style, ui.controls::show);
|
||||
menu.button("@settings.controls", Icon.move, style, isize, ui.controls::show).marginLeft(marg).row();
|
||||
}
|
||||
|
||||
menu.row();
|
||||
menu.button("@settings.data", style, () -> dataDialog.show());
|
||||
menu.button("@settings.data", Icon.save, style, isize, () -> dataDialog.show()).marginLeft(marg).row();
|
||||
|
||||
int i = 3;
|
||||
for(var cat : categories){
|
||||
int index = i;
|
||||
if(cat.icon == null){
|
||||
menu.button(cat.name, style, () -> visible(index)).marginLeft(marg).row();
|
||||
}else{
|
||||
menu.button(cat.name, cat.icon, style, isize, () -> visible(index)).with(b -> ((Image)b.getChildren().get(1)).setScaling(Scaling.fit)).marginLeft(marg).row();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
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 + "%");
|
||||
sound.checkPref("alwaysmusic", false);
|
||||
sound.sliderPref("musicvol", 100, 0, 100, 1, i -> i + "%");
|
||||
sound.sliderPref("sfxvol", 100, 0, 100, 1, i -> i + "%");
|
||||
sound.sliderPref("ambientvol", 100, 0, 100, 1, i -> i + "%");
|
||||
|
||||
game.sliderPref("saveinterval", 60, 10, 5 * 120, 10, i -> Core.bundle.format("setting.seconds", 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());
|
||||
if(!ios){
|
||||
game.checkPref("keyboard", false, val -> {
|
||||
control.setInput(val ? new DesktopInput() : new MobileInput());
|
||||
input.setUseKeyboard(val);
|
||||
});
|
||||
if(Core.settings.getBool("keyboard")){
|
||||
control.setInput(new DesktopInput());
|
||||
input.setUseKeyboard(true);
|
||||
}
|
||||
}else{
|
||||
Core.settings.put("keyboard", false);
|
||||
}
|
||||
}
|
||||
//the issue with touchscreen support on desktop is that:
|
||||
@@ -310,20 +328,35 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
control.setInput(new MobileInput());
|
||||
}
|
||||
}*/
|
||||
game.sliderPref("saveinterval", 60, 10, 5 * 120, 10, i -> Core.bundle.format("setting.seconds", i));
|
||||
|
||||
if(!mobile){
|
||||
game.checkPref("crashreport", true);
|
||||
}
|
||||
|
||||
game.checkPref("communityservers", true, val -> {
|
||||
defaultServers.clear();
|
||||
if(val){
|
||||
JoinDialog.fetchServers();
|
||||
}
|
||||
});
|
||||
|
||||
game.checkPref("savecreate", true);
|
||||
game.checkPref("blockreplace", true);
|
||||
game.checkPref("conveyorpathfinding", true);
|
||||
game.checkPref("hints", true);
|
||||
game.checkPref("logichints", true);
|
||||
|
||||
if(!mobile){
|
||||
game.checkPref("backgroundpause", true);
|
||||
game.checkPref("buildautopause", false);
|
||||
game.checkPref("distinctcontrolgroups", true);
|
||||
}
|
||||
|
||||
game.checkPref("doubletapmine", false);
|
||||
game.checkPref("commandmodehold", true);
|
||||
|
||||
if(!ios){
|
||||
game.checkPref("modcrashdisable", true);
|
||||
}
|
||||
|
||||
if(steam){
|
||||
@@ -333,19 +366,30 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
});
|
||||
|
||||
if(!Version.modifier.contains("beta")){
|
||||
game.checkPref("publichost", false, i -> {
|
||||
game.checkPref("steampublichost", false, i -> {
|
||||
platform.updateLobby();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
graphics.sliderPref("uiscale", 100, 25, 300, 25, s -> {
|
||||
if(ui.settings != null){
|
||||
Core.settings.put("uiscalechanged", true);
|
||||
}
|
||||
if(!mobile){
|
||||
game.checkPref("console", false);
|
||||
}
|
||||
|
||||
int[] lastUiScale = {settings.getInt("uiscale", 100)};
|
||||
|
||||
graphics.sliderPref("uiscale", 100, 25, 300, 5, s -> {
|
||||
//if the user changed their UI scale, but then put it back, don't consider it 'changed'
|
||||
Core.settings.put("uiscalechanged", s != lastUiScale[0]);
|
||||
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("screenshake", 4, 0, 8, i -> (i / 4f) + "x");
|
||||
|
||||
graphics.sliderPref("bloomintensity", 6, 0, 16, i -> (int)(i/4f * 100f) + "%");
|
||||
graphics.sliderPref("bloomblur", 2, 1, 16, i -> i + "x");
|
||||
|
||||
graphics.sliderPref("fpscap", 240, 10, 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){
|
||||
@@ -353,27 +397,42 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
}
|
||||
return s + "%";
|
||||
});
|
||||
graphics.sliderPref("unitlaseropacity", 100, 0, 100, 5, s -> s + "%");
|
||||
graphics.sliderPref("bridgeopacity", 100, 0, 100, 5, s -> s + "%");
|
||||
|
||||
if(!mobile){
|
||||
graphics.checkPref("vsync", true, b -> Core.graphics.setVSync(b));
|
||||
graphics.checkPref("fullscreen", false, b -> {
|
||||
if(b && settings.getBool("borderlesswindow")){
|
||||
Core.graphics.setWindowedMode(Core.graphics.getWidth(), Core.graphics.getHeight());
|
||||
settings.put("borderlesswindow", false);
|
||||
graphics.rebuild();
|
||||
}
|
||||
|
||||
if(b){
|
||||
Core.graphics.setFullscreenMode(Core.graphics.getDisplayMode());
|
||||
Core.graphics.setFullscreen();
|
||||
}else{
|
||||
Core.graphics.setWindowedMode(Core.graphics.getWidth(), Core.graphics.getHeight());
|
||||
}
|
||||
});
|
||||
|
||||
graphics.checkPref("borderlesswindow", false, b -> Core.graphics.setUndecorated(b));
|
||||
graphics.checkPref("borderlesswindow", false, b -> {
|
||||
if(b && settings.getBool("fullscreen")){
|
||||
Core.graphics.setWindowedMode(Core.graphics.getWidth(), Core.graphics.getHeight());
|
||||
settings.put("fullscreen", false);
|
||||
graphics.rebuild();
|
||||
}
|
||||
Core.graphics.setBorderless(b);
|
||||
});
|
||||
|
||||
Core.graphics.setVSync(Core.settings.getBool("vsync"));
|
||||
|
||||
if(Core.settings.getBool("fullscreen")){
|
||||
Core.app.post(() -> Core.graphics.setFullscreenMode(Core.graphics.getDisplayMode()));
|
||||
Core.app.post(() -> Core.graphics.setFullscreen());
|
||||
}
|
||||
|
||||
if(Core.settings.getBool("borderlesswindow")){
|
||||
Core.app.post(() -> Core.graphics.setUndecorated(true));
|
||||
Core.app.post(() -> Core.graphics.setBorderless(true));
|
||||
}
|
||||
}else if(!ios){
|
||||
graphics.checkPref("landscape", false, b -> {
|
||||
@@ -391,6 +450,7 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
|
||||
graphics.checkPref("effects", true);
|
||||
graphics.checkPref("atmosphere", !mobile);
|
||||
graphics.checkPref("drawlight", true);
|
||||
graphics.checkPref("destroyedblocks", true);
|
||||
graphics.checkPref("blockstatus", false);
|
||||
graphics.checkPref("playerchat", true);
|
||||
@@ -400,20 +460,20 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
graphics.checkPref("minimap", !mobile);
|
||||
graphics.checkPref("smoothcamera", true);
|
||||
graphics.checkPref("position", false);
|
||||
if(!mobile){
|
||||
graphics.checkPref("mouseposition", false);
|
||||
}
|
||||
graphics.checkPref("fps", false);
|
||||
graphics.checkPref("playerindicators", true);
|
||||
graphics.checkPref("indicators", true);
|
||||
graphics.checkPref("showweather", true);
|
||||
graphics.checkPref("animatedwater", true);
|
||||
|
||||
if(Shaders.shield != null){
|
||||
graphics.checkPref("animatedshields", !mobile);
|
||||
}
|
||||
|
||||
if(!ios){
|
||||
graphics.checkPref("bloom", true, val -> renderer.toggleBloom(val));
|
||||
}else{
|
||||
Core.settings.put("bloom", false);
|
||||
}
|
||||
graphics.checkPref("bloom", true, val -> renderer.toggleBloom(val));
|
||||
|
||||
graphics.checkPref("pixelate", false, val -> {
|
||||
if(val){
|
||||
@@ -421,12 +481,17 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
}
|
||||
});
|
||||
|
||||
graphics.checkPref("linear", !mobile, b -> {
|
||||
for(Texture tex : Core.atlas.getTextures()){
|
||||
TextureFilter filter = b ? TextureFilter.linear : TextureFilter.nearest;
|
||||
tex.setFilter(filter, filter);
|
||||
}
|
||||
});
|
||||
//iOS (and possibly Android) devices do not support linear filtering well, so disable it
|
||||
if(!ios){
|
||||
graphics.checkPref("linear", !mobile, b -> {
|
||||
for(Texture tex : Core.atlas.getTextures()){
|
||||
TextureFilter filter = b ? TextureFilter.linear : TextureFilter.nearest;
|
||||
tex.setFilter(filter, filter);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
settings.put("linear", false);
|
||||
}
|
||||
|
||||
if(Core.settings.getBool("linear")){
|
||||
for(Texture tex : Core.atlas.getTextures()){
|
||||
@@ -435,11 +500,16 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
}
|
||||
}
|
||||
|
||||
graphics.checkPref("skipcoreanimation", false);
|
||||
graphics.checkPref("hidedisplays", false);
|
||||
|
||||
if(OS.isMac){
|
||||
graphics.checkPref("macnotch", false);
|
||||
}
|
||||
|
||||
if(!mobile){
|
||||
Core.settings.put("swapdiagonal", false);
|
||||
}
|
||||
|
||||
graphics.checkPref("flow", true);
|
||||
}
|
||||
|
||||
public void exportData(Fi file) throws IOException{
|
||||
@@ -447,16 +517,30 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
files.add(Core.settings.getSettingsFile());
|
||||
files.addAll(customMapDirectory.list());
|
||||
files.addAll(saveDirectory.list());
|
||||
files.addAll(screenshotDirectory.list());
|
||||
files.addAll(modDirectory.list());
|
||||
files.addAll(schematicDirectory.list());
|
||||
String base = Core.settings.getDataDirectory().path();
|
||||
|
||||
//add directories
|
||||
for(Fi other : files.copy()){
|
||||
Fi parent = other.parent();
|
||||
while(!files.contains(parent) && !parent.equals(settings.getDataDirectory())){
|
||||
files.add(parent);
|
||||
}
|
||||
}
|
||||
|
||||
try(OutputStream fos = file.write(false, 2048); ZipOutputStream zos = new ZipOutputStream(fos)){
|
||||
for(Fi add : files){
|
||||
if(add.isDirectory()) continue;
|
||||
zos.putNextEntry(new ZipEntry(add.path().substring(base.length())));
|
||||
Streams.copy(add.read(), zos);
|
||||
String path = add.path().substring(base.length());
|
||||
if(add.isDirectory()) path += "/";
|
||||
//fix trailing / in path
|
||||
path = path.startsWith("/") ? path.substring(1) : path;
|
||||
zos.putNextEntry(new ZipEntry(path));
|
||||
if(!add.isDirectory()){
|
||||
try(var stream = add.read()){
|
||||
Streams.copy(stream, zos);
|
||||
}
|
||||
}
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
@@ -495,18 +579,25 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
|
||||
private void visible(int index){
|
||||
prefs.clearChildren();
|
||||
prefs.add(new Table[]{game, graphics, sound}[index]);
|
||||
|
||||
Seq<Table> tables = new Seq<>();
|
||||
tables.addAll(game, graphics, sound);
|
||||
for(var custom : categories){
|
||||
tables.add(custom.table);
|
||||
}
|
||||
|
||||
prefs.add(tables.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCloseButton(){
|
||||
buttons.button("@back", Icon.leftOpen, () -> {
|
||||
buttons.button("@back", Icon.left, () -> {
|
||||
if(prefs.getChildren().first() != menu){
|
||||
back();
|
||||
}else{
|
||||
hide();
|
||||
}
|
||||
}).size(230f, 64f);
|
||||
}).size(210f, 64f);
|
||||
|
||||
keyDown(key -> {
|
||||
if(key == KeyCode.escape || key == KeyCode.back){
|
||||
@@ -518,4 +609,250 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface StringProcessor{
|
||||
String get(int i);
|
||||
}
|
||||
|
||||
public static class SettingsCategory{
|
||||
public String name;
|
||||
public @Nullable Drawable icon;
|
||||
public Cons<SettingsTable> builder;
|
||||
public SettingsTable table;
|
||||
|
||||
public SettingsCategory(String name, Drawable icon, Cons<SettingsTable> builder){
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.builder = builder;
|
||||
|
||||
table = new SettingsTable();
|
||||
builder.get(table);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SettingsTable extends Table{
|
||||
protected Seq<Setting> list = new Seq<>();
|
||||
|
||||
public SettingsTable(){
|
||||
left();
|
||||
}
|
||||
|
||||
public Seq<Setting> getSettings(){
|
||||
return list;
|
||||
}
|
||||
|
||||
public void pref(Setting setting){
|
||||
list.add(setting);
|
||||
rebuild();
|
||||
}
|
||||
|
||||
public SliderSetting sliderPref(String name, int def, int min, int max, StringProcessor s){
|
||||
return sliderPref(name, def, min, max, 1, s);
|
||||
}
|
||||
|
||||
public SliderSetting sliderPref(String name, int def, int min, int max, int step, StringProcessor s){
|
||||
SliderSetting res;
|
||||
list.add(res = new SliderSetting(name, def, min, max, step, s));
|
||||
settings.defaults(name, def);
|
||||
rebuild();
|
||||
return res;
|
||||
}
|
||||
|
||||
public void checkPref(String name, boolean def){
|
||||
list.add(new CheckSetting(name, def, null));
|
||||
settings.defaults(name, def);
|
||||
rebuild();
|
||||
}
|
||||
|
||||
public void checkPref(String name, boolean def, Boolc changed){
|
||||
list.add(new CheckSetting(name, def, changed));
|
||||
settings.defaults(name, def);
|
||||
rebuild();
|
||||
}
|
||||
|
||||
public void textPref(String name, String def){
|
||||
list.add(new TextSetting(name, def, null));
|
||||
settings.defaults(name, def);
|
||||
rebuild();
|
||||
}
|
||||
|
||||
public void textPref(String name, String def, Cons<String> changed){
|
||||
list.add(new TextSetting(name, def, changed));
|
||||
settings.defaults(name, def);
|
||||
rebuild();
|
||||
}
|
||||
|
||||
public void areaTextPref(String name, String def){
|
||||
list.add(new AreaTextSetting(name, def, null));
|
||||
settings.defaults(name, def);
|
||||
rebuild();
|
||||
}
|
||||
|
||||
public void areaTextPref(String name, String def, Cons<String> changed){
|
||||
list.add(new AreaTextSetting(name, def, changed));
|
||||
settings.defaults(name, def);
|
||||
rebuild();
|
||||
}
|
||||
|
||||
public void rebuild(){
|
||||
clearChildren();
|
||||
|
||||
for(Setting setting : list){
|
||||
setting.add(this);
|
||||
}
|
||||
|
||||
button(bundle.get("settings.reset", "Reset to Defaults"), () -> {
|
||||
for(Setting setting : list){
|
||||
if(setting.name == null || setting.title == null) continue;
|
||||
settings.remove(setting.name);
|
||||
}
|
||||
rebuild();
|
||||
}).margin(14).width(240f).pad(6);
|
||||
}
|
||||
|
||||
public abstract static class Setting{
|
||||
public String name;
|
||||
public String title;
|
||||
public @Nullable String description;
|
||||
|
||||
public Setting(String name){
|
||||
this.name = name;
|
||||
String winkey = "setting." + name + ".name.windows";
|
||||
title = OS.isWindows && bundle.has(winkey) ? bundle.get(winkey) : bundle.get("setting." + name + ".name", name);
|
||||
description = bundle.getOrNull("setting." + name + ".description");
|
||||
}
|
||||
|
||||
public abstract void add(SettingsTable table);
|
||||
|
||||
public void addDesc(Element elem){
|
||||
ui.addDescTooltip(elem, description);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CheckSetting extends Setting{
|
||||
boolean def;
|
||||
Boolc changed;
|
||||
|
||||
public CheckSetting(String name, boolean def, Boolc changed){
|
||||
super(name);
|
||||
this.def = def;
|
||||
this.changed = changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(SettingsTable table){
|
||||
CheckBox box = new CheckBox(title);
|
||||
|
||||
box.update(() -> box.setChecked(settings.getBool(name)));
|
||||
|
||||
box.changed(() -> {
|
||||
settings.put(name, box.isChecked());
|
||||
if(changed != null){
|
||||
changed.get(box.isChecked());
|
||||
}
|
||||
});
|
||||
|
||||
box.left();
|
||||
addDesc(table.add(box).left().padTop(3f).get());
|
||||
table.row();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SliderSetting extends Setting{
|
||||
int def, min, max, step;
|
||||
StringProcessor sp;
|
||||
|
||||
public SliderSetting(String name, int def, int min, int max, int step, StringProcessor s){
|
||||
super(name);
|
||||
this.def = def;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = step;
|
||||
this.sp = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(SettingsTable table){
|
||||
Slider slider = new Slider(min, max, step, false);
|
||||
|
||||
slider.setValue(settings.getInt(name));
|
||||
|
||||
Label value = new Label("", Styles.outlineLabel);
|
||||
Table content = new Table();
|
||||
content.add(title, Styles.outlineLabel).left().growX().wrap();
|
||||
content.add(value).padLeft(10f).right();
|
||||
content.margin(3f, 33f, 3f, 33f);
|
||||
content.touchable = Touchable.disabled;
|
||||
|
||||
slider.changed(() -> {
|
||||
settings.put(name, (int)slider.getValue());
|
||||
value.setText(sp.get((int)slider.getValue()));
|
||||
});
|
||||
|
||||
slider.change();
|
||||
|
||||
addDesc(table.stack(slider, content).width(Math.min(Core.graphics.getWidth() / 1.2f, 460f)).left().padTop(4f).get());
|
||||
table.row();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextSetting extends Setting{
|
||||
String def;
|
||||
Cons<String> changed;
|
||||
|
||||
public TextSetting(String name, String def, Cons<String> changed){
|
||||
super(name);
|
||||
this.def = def;
|
||||
this.changed = changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(SettingsTable table){
|
||||
TextField field = new TextField();
|
||||
|
||||
field.update(() -> field.setText(settings.getString(name)));
|
||||
|
||||
field.changed(() -> {
|
||||
settings.put(name, field.getText());
|
||||
if(changed != null){
|
||||
changed.get(field.getText());
|
||||
}
|
||||
});
|
||||
|
||||
Table prefTable = table.table().left().padTop(3f).get();
|
||||
prefTable.add(field);
|
||||
prefTable.label(() -> title);
|
||||
addDesc(prefTable);
|
||||
table.row();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AreaTextSetting extends TextSetting{
|
||||
public AreaTextSetting(String name, String def, Cons<String> changed){
|
||||
super(name, def, changed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(SettingsTable table){
|
||||
TextArea area = new TextArea("");
|
||||
area.setPrefRows(5);
|
||||
|
||||
area.update(() -> {
|
||||
area.setText(settings.getString(name));
|
||||
area.setWidth(table.getWidth());
|
||||
});
|
||||
|
||||
area.changed(() -> {
|
||||
settings.put(name, area.getText());
|
||||
if(changed != null){
|
||||
changed.get(area.getText());
|
||||
}
|
||||
});
|
||||
|
||||
addDesc(table.label(() -> title).left().padTop(3f).get());
|
||||
table.row().add(area).left();
|
||||
table.row();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import arc.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.net.Administration.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class TraceDialog extends BaseDialog{
|
||||
|
||||
@@ -11,7 +14,6 @@ public class TraceDialog extends BaseDialog{
|
||||
super("@trace");
|
||||
|
||||
addCloseButton();
|
||||
setFillParent(false);
|
||||
}
|
||||
|
||||
public void show(Player player, TraceInfo info){
|
||||
@@ -22,16 +24,38 @@ public class TraceDialog extends BaseDialog{
|
||||
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();
|
||||
|
||||
var style = Styles.emptyi;
|
||||
float s = 28f;
|
||||
|
||||
table.table(c -> {
|
||||
c.left().defaults().left();
|
||||
c.button(Icon.copySmall, style, () -> copy(player.name)).size(s).padRight(4f);
|
||||
c.add(Core.bundle.format("trace.playername", player.name)).row();
|
||||
c.button(Icon.copySmall, style, () -> copy(info.ip)).size(s).padRight(4f);
|
||||
c.add(Core.bundle.format("trace.ip", info.ip)).row();
|
||||
c.button(Icon.copySmall, style, () -> copy(info.locale)).size(s).padRight(4f);
|
||||
c.add(Core.bundle.format("trace.language", info.locale)).row();
|
||||
c.button(Icon.copySmall, style, () -> copy(info.uuid)).size(s).padRight(4f);
|
||||
c.add(Core.bundle.format("trace.id", info.uuid)).row();
|
||||
}).row();
|
||||
|
||||
table.add(Core.bundle.format("trace.modclient", info.modded)).row();
|
||||
table.add(Core.bundle.format("trace.mobile", info.mobile)).row();
|
||||
table.add(Core.bundle.format("trace.times.joined", info.timesJoined)).row();
|
||||
table.add(Core.bundle.format("trace.times.kicked", info.timesKicked)).row();
|
||||
|
||||
for(int i = 0; i < 2; i++){
|
||||
table.add(i == 0 ? "@trace.ips" : "@trace.names").row();
|
||||
String[] list = i == 0 ? info.ips : info.names;
|
||||
|
||||
table.pane(t -> {
|
||||
t.left();
|
||||
for(String val : list){
|
||||
t.add("[lightgray]" + val).left().row();
|
||||
}
|
||||
}).padLeft(20f).fill().left().row();
|
||||
}
|
||||
|
||||
table.add().pad(5);
|
||||
table.row();
|
||||
@@ -40,4 +64,9 @@ public class TraceDialog extends BaseDialog{
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
private void copy(String content){
|
||||
Core.app.setClipboardText(content);
|
||||
ui.showInfoFade("@copied");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,48 +12,38 @@ import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BlockConfigFragment extends Fragment{
|
||||
public class BlockConfigFragment{
|
||||
Table table = new Table();
|
||||
Building configTile;
|
||||
Building selected;
|
||||
|
||||
@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.isMenu()){
|
||||
table.visible = false;
|
||||
configTile = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
Events.on(ResetEvent.class, e -> forceHide());
|
||||
}
|
||||
|
||||
Events.on(ResetEvent.class, e -> {
|
||||
table.visible = false;
|
||||
configTile = null;
|
||||
});
|
||||
public void forceHide(){
|
||||
table.visible = false;
|
||||
selected = null;
|
||||
}
|
||||
|
||||
public boolean isShown(){
|
||||
return table.visible && configTile != null;
|
||||
return table.visible && selected != null;
|
||||
}
|
||||
|
||||
public Building getSelectedTile(){
|
||||
return configTile;
|
||||
public Building getSelected(){
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void showConfig(Building tile){
|
||||
if(selected != null) selected.onConfigureClosed();
|
||||
if(tile.configTapped()){
|
||||
configTile = tile;
|
||||
selected = tile;
|
||||
|
||||
table.visible = true;
|
||||
table.clear();
|
||||
table.background(null);
|
||||
tile.buildConfiguration(table);
|
||||
table.pack();
|
||||
table.setTransform(true);
|
||||
@@ -61,28 +51,29 @@ public class BlockConfigFragment extends Fragment{
|
||||
Actions.scaleTo(1f, 1f, 0.07f, Interp.pow3Out));
|
||||
|
||||
table.update(() -> {
|
||||
if(configTile != null && configTile.shouldHideConfigure(player)){
|
||||
if(selected != null && selected.shouldHideConfigure(player)){
|
||||
hideConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
table.setOrigin(Align.center);
|
||||
if(configTile == null || configTile.block == Blocks.air || !configTile.isValid()){
|
||||
if(selected == null || selected.block == Blocks.air || !selected.isValid()){
|
||||
hideConfig();
|
||||
}else{
|
||||
configTile.updateTableAlign(table);
|
||||
selected.updateTableAlign(table);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasConfigMouse(){
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.graphics.getHeight() - Core.input.mouseY(), true);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
return e != null && (e == table || e.isDescendantOf(table));
|
||||
}
|
||||
|
||||
public void hideConfig(){
|
||||
configTile = null;
|
||||
if(selected != null) selected.onConfigureClosed();
|
||||
selected = null;
|
||||
table.actions(Actions.scaleTo(0f, 1f, 0.06f, Interp.pow3Out), Actions.visible(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,23 +14,23 @@ import arc.scene.ui.layout.Stack;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BlockInventoryFragment extends Fragment{
|
||||
public class BlockInventoryFragment{
|
||||
private static final float holdWithdraw = 20f;
|
||||
private static final float holdShrink = 120f;
|
||||
|
||||
Table table = new Table();
|
||||
Building tile;
|
||||
Building build;
|
||||
float holdTime = 0f, emptyTime;
|
||||
boolean holding;
|
||||
boolean holding, held;
|
||||
float[] shrinkHoldTimes = new float[content.items().size];
|
||||
Item lastItem;
|
||||
|
||||
@@ -38,7 +38,6 @@ public class BlockInventoryFragment extends Fragment{
|
||||
Events.on(WorldLoadEvent.class, e -> hide());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build(Group parent){
|
||||
table.name = "inventory";
|
||||
table.setTransform(true);
|
||||
@@ -47,13 +46,14 @@ public class BlockInventoryFragment extends Fragment{
|
||||
}
|
||||
|
||||
public void showFor(Building t){
|
||||
if(this.tile == t){
|
||||
if(this.build == t){
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
this.tile = t;
|
||||
if(tile == null || !tile.block.isAccessible() || tile.items.total() == 0)
|
||||
this.build = t;
|
||||
if(build == null || !build.block.isAccessible() || build.items == null || build.items.total() == 0){
|
||||
return;
|
||||
}
|
||||
rebuild(true);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,23 @@ public class BlockInventoryFragment extends Fragment{
|
||||
table.update(null);
|
||||
}), Actions.visible(false));
|
||||
table.touchable = Touchable.disabled;
|
||||
tile = null;
|
||||
build = null;
|
||||
}
|
||||
|
||||
private void takeItem(int requested){
|
||||
if(!build.canWithdraw()) return;
|
||||
|
||||
//take everything
|
||||
int amount = Math.min(requested, player.unit().maxAccepted(lastItem));
|
||||
|
||||
if(amount > 0){
|
||||
Call.requestItem(player, build, lastItem, amount);
|
||||
holding = false;
|
||||
holdTime = 0f;
|
||||
held = true;
|
||||
|
||||
if(net.client()) Events.fire(new WithdrawEvent(build, player, lastItem, amount));
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuild(boolean actions){
|
||||
@@ -81,35 +97,29 @@ public class BlockInventoryFragment extends Fragment{
|
||||
table.touchable = Touchable.enabled;
|
||||
table.update(() -> {
|
||||
|
||||
if(state.isMenu() || tile == null || !tile.isValid() || !tile.block.isAccessible() || emptyTime >= holdShrink){
|
||||
if(state.isMenu() || build == null || !build.isValid() || !build.block.isAccessible() || emptyTime >= holdShrink){
|
||||
hide();
|
||||
}else{
|
||||
if(tile.items.total() == 0){
|
||||
if(build.items.total() == 0){
|
||||
emptyTime += Time.delta;
|
||||
}else{
|
||||
emptyTime = 0f;
|
||||
}
|
||||
|
||||
if(holding && lastItem != null){
|
||||
holdTime += Time.delta;
|
||||
if(holding && lastItem != null && (holdTime += Time.delta) >= holdWithdraw){
|
||||
holdTime = 0f;
|
||||
|
||||
if(holdTime >= holdWithdraw){
|
||||
int amount = Math.min(tile.items.get(lastItem), player.unit().maxAccepted(lastItem));
|
||||
Call.requestItem(player, tile, lastItem, amount);
|
||||
holding = false;
|
||||
holdTime = 0f;
|
||||
|
||||
if(net.client()) Events.fire(new WithdrawEvent(tile, player, lastItem, amount));
|
||||
}
|
||||
//take one when held
|
||||
takeItem(1);
|
||||
}
|
||||
|
||||
updateTablePosition();
|
||||
if(tile.block.hasItems){
|
||||
if(build.block.hasItems){
|
||||
boolean dirty = false;
|
||||
if(shrinkHoldTimes.length != content.items().size) shrinkHoldTimes = new float[content.items().size];
|
||||
|
||||
for(int i = 0; i < content.items().size; i++){
|
||||
boolean has = tile.items.has(content.item(i));
|
||||
boolean has = build.items.has(content.item(i));
|
||||
boolean had = container.contains(i);
|
||||
if(has){
|
||||
shrinkHoldTimes[i] = 0f;
|
||||
@@ -134,44 +144,54 @@ public class BlockInventoryFragment extends Fragment{
|
||||
table.margin(4f);
|
||||
table.defaults().size(8 * 5).pad(4f);
|
||||
|
||||
if(tile.block.hasItems){
|
||||
if(build.block.hasItems){
|
||||
|
||||
for(int i = 0; i < content.items().size; i++){
|
||||
Item item = content.item(i);
|
||||
if(!tile.items.has(item)) continue;
|
||||
if(!build.items.has(item)) continue;
|
||||
|
||||
container.add(i);
|
||||
|
||||
Boolp canPick = () -> player.unit().acceptsItem(item) && !state.isPaused() && player.within(tile, itemTransferRange);
|
||||
Boolp canPick = () -> !player.dead() && player.unit().acceptsItem(item) && !state.isPaused() && player.within(build, itemTransferRange);
|
||||
|
||||
HandCursorListener l = new HandCursorListener();
|
||||
l.enabled = canPick;
|
||||
|
||||
Element image = itemImage(item.icon(Cicon.xlarge), () -> {
|
||||
if(tile == null || !tile.isValid()){
|
||||
Element image = itemImage(item.uiIcon, () -> {
|
||||
if(build == null || !build.isValid()){
|
||||
return "";
|
||||
}
|
||||
return round(tile.items.get(item));
|
||||
return round(build.items.get(item));
|
||||
});
|
||||
image.addListener(l);
|
||||
|
||||
image.addListener(new InputListener(){
|
||||
Boolp validClick = () -> !(!canPick.get() || build == null || !build.isValid() || build.items == null || !build.items.has(item));
|
||||
|
||||
image.addListener(new ClickListener(){
|
||||
|
||||
@Override
|
||||
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
if(!canPick.get() || tile == null || !tile.isValid() || tile.items == null || !tile.items.has(item)) return false;
|
||||
int amount = Math.min(1, player.unit().maxAccepted(item));
|
||||
if(amount > 0){
|
||||
Call.requestItem(player, tile, item, amount);
|
||||
held = false;
|
||||
if(validClick.get()){
|
||||
lastItem = item;
|
||||
holding = true;
|
||||
holdTime = 0f;
|
||||
if(net.client()) Events.fire(new WithdrawEvent(tile, player, item, amount));
|
||||
}
|
||||
return true;
|
||||
|
||||
return super.touchDown(event, x, y, pointer, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clicked(InputEvent event, float x, float y){
|
||||
if(!validClick.get() || held) return;
|
||||
|
||||
//take all
|
||||
takeItem(build.items.get(lastItem = item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
super.touchUp(event, x, y, pointer, button);
|
||||
|
||||
holding = false;
|
||||
lastItem = null;
|
||||
}
|
||||
@@ -201,16 +221,16 @@ public class BlockInventoryFragment extends Fragment{
|
||||
private String round(float f){
|
||||
f = (int)f;
|
||||
if(f >= 1000000){
|
||||
return (int)(f / 1000000f) + "[gray]" + Core.bundle.getOrNull("unit.millions") + "[]";
|
||||
return (int)(f / 1000000f) + "[gray]" + UI.millions;
|
||||
}else if(f >= 1000){
|
||||
return (int)(f / 1000) + Core.bundle.getOrNull("unit.thousands");
|
||||
return (int)(f / 1000) + UI.thousands;
|
||||
}else{
|
||||
return (int)f + "";
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTablePosition(){
|
||||
Vec2 v = Core.input.mouseScreen(tile.x + tile.block.size * tilesize / 2f, tile.y + tile.block.size * tilesize / 2f);
|
||||
Vec2 v = Core.input.mouseScreen(build.x + build.block.size * tilesize / 2f, build.y + build.block.size * tilesize / 2f);
|
||||
table.pack();
|
||||
table.setPosition(v.x, v.y, Align.topLeft);
|
||||
}
|
||||
|
||||
@@ -9,22 +9,22 @@ import arc.math.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.Label.*;
|
||||
import arc.scene.ui.TextField.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.net;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class ChatFragment extends Table{
|
||||
private static final int messagesShown = 10;
|
||||
private Seq<ChatMessage> messages = new Seq<>();
|
||||
private Seq<String> messages = new Seq<>();
|
||||
private float fadetime;
|
||||
private boolean shown = false;
|
||||
private TextField chatfield;
|
||||
@@ -33,17 +33,11 @@ public class ChatFragment extends Table{
|
||||
private Font font;
|
||||
private GlyphLayout layout = new GlyphLayout();
|
||||
private float offsetx = Scl.scl(4), offsety = Scl.scl(4), fontoffsetx = Scl.scl(2), chatspace = Scl.scl(50);
|
||||
private Color shadowColor = new Color(0, 0, 0, 0.4f);
|
||||
private Color shadowColor = new Color(0, 0, 0, 0.5f);
|
||||
private float textspacing = Scl.scl(10);
|
||||
private Seq<String> history = new Seq<>();
|
||||
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();
|
||||
@@ -65,7 +59,7 @@ public class ChatFragment extends Table{
|
||||
|
||||
update(() -> {
|
||||
|
||||
if(net.active() && input.keyTap(Binding.chat) && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null || ui.minimapfrag.shown()) && !ui.scriptfrag.shown()){
|
||||
if(net.active() && input.keyTap(Binding.chat) && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null || ui.minimapfrag.shown()) && !ui.consolefrag.shown()){
|
||||
toggle();
|
||||
}
|
||||
|
||||
@@ -90,8 +84,8 @@ public class ChatFragment extends Table{
|
||||
setup();
|
||||
}
|
||||
|
||||
public Fragment container(){
|
||||
return container;
|
||||
public void build(Group parent){
|
||||
scene.add(this);
|
||||
}
|
||||
|
||||
public void clearMessages(){
|
||||
@@ -105,13 +99,14 @@ public class ChatFragment extends Table{
|
||||
fieldlabel.getStyle().font = font;
|
||||
fieldlabel.setStyle(fieldlabel.getStyle());
|
||||
|
||||
chatfield = new TextField("", new TextField.TextFieldStyle(scene.getStyle(TextField.TextFieldStyle.class)));
|
||||
chatfield = new TextField("", new TextFieldStyle(scene.getStyle(TextFieldStyle.class)));
|
||||
chatfield.setMaxLength(Vars.maxTextLength);
|
||||
chatfield.getStyle().background = null;
|
||||
chatfield.getStyle().font = Fonts.chat;
|
||||
chatfield.getStyle().fontColor = Color.white;
|
||||
chatfield.setStyle(chatfield.getStyle());
|
||||
|
||||
chatfield.typed(this::handleType);
|
||||
|
||||
bottom().left().marginBottom(offsety).marginLeft(offsetx * 2).add(fieldlabel).padBottom(6f);
|
||||
|
||||
add(chatfield).padBottom(offsety).padLeft(offsetx).growX().padRight(offsetx).height(28);
|
||||
@@ -122,6 +117,27 @@ public class ChatFragment extends Table{
|
||||
}
|
||||
}
|
||||
|
||||
//no mobile support.
|
||||
private void handleType(char c){
|
||||
int cursor = chatfield.getCursorPosition();
|
||||
if(c == ':'){
|
||||
int index = chatfield.getText().lastIndexOf(':', cursor - 2);
|
||||
if(index >= 0 && index < cursor){
|
||||
String text = chatfield.getText().substring(index + 1, cursor - 1);
|
||||
String uni = Fonts.getUnicodeStr(text);
|
||||
if(uni != null && uni.length() > 0){
|
||||
chatfield.setText(chatfield.getText().substring(0, index) + uni + chatfield.getText().substring(cursor));
|
||||
chatfield.setCursorPosition(index + uni.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void rect(float x, float y, float w, float h){
|
||||
//prevents texture bindings; the string lookup is irrelevant as it is only called <10 times per frame, and maps are very fast anyway
|
||||
Draw.rect("whiteui", x + w/2f, y + h/2f, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
float opacity = Core.settings.getInt("chatopacity") / 100f;
|
||||
@@ -130,7 +146,7 @@ public class ChatFragment extends Table{
|
||||
Draw.color(shadowColor);
|
||||
|
||||
if(shown){
|
||||
Fill.crect(offsetx, chatfield.y + scene.marginBottom, chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
|
||||
rect(offsetx, chatfield.y + scene.marginBottom, chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
|
||||
}
|
||||
|
||||
super.draw();
|
||||
@@ -146,12 +162,13 @@ public class ChatFragment extends Table{
|
||||
float theight = offsety + spacing + getMarginBottom() + scene.marginBottom;
|
||||
for(int i = scrollPos; i < messages.size && i < messagesShown + scrollPos && (i < fadetime || shown); i++){
|
||||
|
||||
layout.setText(font, messages.get(i).formattedMessage, Color.white, textWidth, Align.bottomLeft, true);
|
||||
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).formattedMessage, fontoffsetx + offsetx, offsety + theight, textWidth, Align.bottomLeft, true);
|
||||
font.getCache().setColor(Color.white);
|
||||
font.getCache().addText(messages.get(i), fontoffsetx + offsetx, offsety + theight, textWidth, Align.bottomLeft, true);
|
||||
|
||||
if(!shown && fadetime - i < 1f && fadetime - i >= 0f){
|
||||
font.getCache().setAlphas((fadetime - i) * opacity);
|
||||
@@ -160,7 +177,7 @@ public class ChatFragment extends Table{
|
||||
font.getCache().setAlphas(opacity);
|
||||
}
|
||||
|
||||
Fill.crect(offsetx, theight - layout.height - 2, textWidth + Scl.scl(4f), layout.height + textspacing);
|
||||
rect(offsetx, theight - layout.height - 2, textWidth + Scl.scl(4f), layout.height + textspacing);
|
||||
Draw.color(shadowColor);
|
||||
Draw.alpha(opacity * shadowColor.a);
|
||||
|
||||
@@ -178,10 +195,13 @@ public class ChatFragment extends Table{
|
||||
String message = chatfield.getText().trim();
|
||||
clearChatInput();
|
||||
|
||||
if(message.isEmpty()) return;
|
||||
//avoid sending prefix-empty messages
|
||||
if(message.isEmpty() || (message.startsWith(mode.prefix) && message.substring(mode.prefix.length()).isEmpty())) return;
|
||||
|
||||
history.insert(1, message);
|
||||
|
||||
Events.fire(new ClientChatEvent(message));
|
||||
|
||||
Call.sendChatMessage(message);
|
||||
}
|
||||
|
||||
@@ -206,7 +226,7 @@ public class ChatFragment extends Table{
|
||||
}
|
||||
}else{
|
||||
//sending chat has a delay; workaround for issue #1943
|
||||
Time.run(2f, () ->{
|
||||
Time.runTask(2f, () ->{
|
||||
scene.setKeyboardFocus(null);
|
||||
shown = false;
|
||||
scrollPos = 0;
|
||||
@@ -257,32 +277,16 @@ public class ChatFragment extends Table{
|
||||
return shown;
|
||||
}
|
||||
|
||||
public void addMessage(String message, String sender){
|
||||
if(sender == null && message == null) return;
|
||||
messages.insert(0, new ChatMessage(message, sender));
|
||||
public void addMessage(String message){
|
||||
if(message == null) return;
|
||||
messages.insert(0, message);
|
||||
|
||||
fadetime += 1f;
|
||||
fadetime = Math.min(fadetime, messagesShown) + 1f;
|
||||
|
||||
|
||||
if(scrollPos > 0) scrollPos++;
|
||||
}
|
||||
|
||||
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 == null ? "" : message;
|
||||
}else{
|
||||
formattedMessage = "[coral][[" + sender + "[coral]]:[white] " + message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ChatMode{
|
||||
normal(""),
|
||||
team("/t"),
|
||||
|
||||
@@ -8,16 +8,18 @@ import arc.math.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.Label.*;
|
||||
import arc.scene.ui.TextField.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class ScriptConsoleFragment extends Table{
|
||||
public class ConsoleFragment extends Table{
|
||||
private static final int messagesShown = 30;
|
||||
private Seq<String> messages = new Seq<>();
|
||||
private boolean open = false, shown;
|
||||
@@ -31,23 +33,23 @@ public class ScriptConsoleFragment extends Table{
|
||||
private Seq<String> history = new Seq<>();
|
||||
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(){
|
||||
public ConsoleFragment(){
|
||||
setFillParent(true);
|
||||
font = Fonts.def;
|
||||
|
||||
visible(() -> {
|
||||
if(input.keyTap(Binding.console) && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null) && !ui.chatfrag.shown()){
|
||||
if(input.keyTap(Binding.console) && settings.getBool("console") && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null) && !ui.chatfrag.shown()){
|
||||
shown = !shown;
|
||||
if(shown && !open && enableConsole){
|
||||
if(shown && !open && settings.getBool("console")){
|
||||
toggle();
|
||||
}
|
||||
if(shown){
|
||||
chatfield.requestKeyboard();
|
||||
}else if(scene.getKeyboardFocus() == chatfield){
|
||||
scene.setKeyboardFocus(null);
|
||||
scene.setScrollFocus(null);
|
||||
}
|
||||
clearChatInput();
|
||||
}
|
||||
|
||||
@@ -55,7 +57,7 @@ public class ScriptConsoleFragment extends Table{
|
||||
});
|
||||
|
||||
update(() -> {
|
||||
if(input.keyTap(Binding.chat) && enableConsole && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){
|
||||
if(input.keyTap(Binding.chat) && settings.getBool("console") && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){
|
||||
toggle();
|
||||
}
|
||||
|
||||
@@ -71,15 +73,15 @@ public class ScriptConsoleFragment extends Table{
|
||||
}
|
||||
}
|
||||
|
||||
scrollPos = (int)Mathf.clamp(scrollPos + input.axis(Binding.chat_scroll), 0, Math.max(0, messages.size - messagesShown));
|
||||
scrollPos = (int)Mathf.clamp(scrollPos + input.axis(Binding.chat_scroll), 0, Math.max(0, messages.size));
|
||||
});
|
||||
|
||||
history.insert(0, "");
|
||||
setup();
|
||||
}
|
||||
|
||||
public Fragment container(){
|
||||
return container;
|
||||
public void build(Group parent){
|
||||
scene.add(this);
|
||||
}
|
||||
|
||||
public void clearMessages(){
|
||||
@@ -93,9 +95,8 @@ public class ScriptConsoleFragment extends Table{
|
||||
fieldlabel.getStyle().font = font;
|
||||
fieldlabel.setStyle(fieldlabel.getStyle());
|
||||
|
||||
chatfield = new TextField("", new TextField.TextFieldStyle(scene.getStyle(TextField.TextFieldStyle.class)));
|
||||
chatfield = new TextField("", new TextFieldStyle(scene.getStyle(TextFieldStyle.class)));
|
||||
chatfield.getStyle().background = null;
|
||||
chatfield.getStyle().font = Fonts.chat;
|
||||
chatfield.getStyle().fontColor = Color.white;
|
||||
chatfield.setStyle(chatfield.getStyle());
|
||||
|
||||
@@ -104,6 +105,10 @@ public class ScriptConsoleFragment extends Table{
|
||||
add(chatfield).padBottom(offsety).padLeft(offsetx).growX().padRight(offsetx).height(28);
|
||||
}
|
||||
|
||||
protected void rect(float x, float y, float w, float h){
|
||||
Draw.rect("whiteui", x + w/2f, y + h/2f, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
float opacity = 1f;
|
||||
@@ -112,7 +117,7 @@ public class ScriptConsoleFragment extends Table{
|
||||
Draw.color(shadowColor);
|
||||
|
||||
if(open){
|
||||
Fill.crect(offsetx, chatfield.y + scene.marginBottom, chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
|
||||
rect(offsetx, chatfield.y + scene.marginBottom, chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
|
||||
}
|
||||
|
||||
super.draw();
|
||||
@@ -133,6 +138,7 @@ public class ScriptConsoleFragment extends Table{
|
||||
if(i - scrollPos == 0) theight -= textspacing + 1;
|
||||
|
||||
font.getCache().clear();
|
||||
font.getCache().setColor(Color.white);
|
||||
font.getCache().addText(messages.get(i), fontoffsetx + offsetx, offsety + theight, textWidth, Align.bottomLeft, true);
|
||||
|
||||
if(!open){
|
||||
@@ -142,7 +148,7 @@ public class ScriptConsoleFragment extends Table{
|
||||
font.getCache().setAlphas(opacity);
|
||||
}
|
||||
|
||||
Fill.crect(offsetx, theight - layout.height - 2, textWidth + Scl.scl(4f), layout.height + textspacing);
|
||||
rect(offsetx, theight - layout.height - 2, textWidth + Scl.scl(4f), layout.height + textspacing);
|
||||
Draw.color(shadowColor);
|
||||
Draw.alpha(opacity * shadowColor.a);
|
||||
|
||||
@@ -167,12 +173,24 @@ public class ScriptConsoleFragment extends Table{
|
||||
history.insert(1, message);
|
||||
|
||||
addMessage("[lightgray]> " + message.replace("[", "[["));
|
||||
addMessage(mods.getScripts().runConsole(message).replace("[", "[["));
|
||||
addMessage(mods.getScripts().runConsole(injectConsoleVariables() + message).replace("[", "[["));
|
||||
}
|
||||
|
||||
public String injectConsoleVariables(){
|
||||
return
|
||||
"var unit = Vars.player.unit();" +
|
||||
"var team = Vars.player.team();" +
|
||||
"var core = Vars.player.core();" +
|
||||
"var items = Vars.player.team().items();" +
|
||||
"var build = Vars.world.buildWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());" +
|
||||
"var cursor = Vars.world.tileWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());" +
|
||||
"\n";
|
||||
}
|
||||
|
||||
public void toggle(){
|
||||
|
||||
if(!open){
|
||||
Events.fire(Trigger.openConsole);
|
||||
scene.setKeyboardFocus(chatfield);
|
||||
open = !open;
|
||||
if(mobile){
|
||||
@@ -7,11 +7,10 @@ import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
|
||||
/** Fades in a black overlay.*/
|
||||
public class FadeInFragment extends Fragment{
|
||||
public class FadeInFragment{
|
||||
private static final float duration = 40f;
|
||||
float time = 0f;
|
||||
|
||||
@Override
|
||||
public void build(Group parent){
|
||||
parent.addChild(new Element(){
|
||||
{
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package mindustry.ui.fragments;
|
||||
|
||||
import arc.scene.*;
|
||||
|
||||
public abstract class Fragment{
|
||||
public abstract void build(Group parent);
|
||||
}
|
||||
@@ -11,22 +11,26 @@ import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
import mindustry.world.blocks.payloads.PayloadBlock.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class HintsFragment extends Fragment{
|
||||
public class HintsFragment{
|
||||
private static final Boolp isTutorial = () -> Vars.state.rules.sector == SectorPresets.groundZero.sector;
|
||||
private static final float foutTime = 0.6f;
|
||||
|
||||
/** All hints to be displayed in the game. */
|
||||
public Seq<Hint> hints = new Seq<>().and(DefaultHint.values()).as();
|
||||
public Seq<Hint> hints = new Seq<>().add(DefaultHint.values()).as();
|
||||
|
||||
@Nullable Hint current;
|
||||
Group group = new WidgetGroup();
|
||||
@@ -34,7 +38,6 @@ public class HintsFragment extends Fragment{
|
||||
ObjectSet<Block> placedBlocks = new ObjectSet<>();
|
||||
Table last;
|
||||
|
||||
@Override
|
||||
public void build(Group parent){
|
||||
group.setFillParent(true);
|
||||
group.touchable = Touchable.childrenOnly;
|
||||
@@ -52,8 +55,14 @@ public class HintsFragment extends Fragment{
|
||||
Hint hint = hints.find(Hint::show);
|
||||
if(hint != null && hint.complete()){
|
||||
hints.remove(hint);
|
||||
}else if(hint != null){
|
||||
}else if(hint != null && !renderer.isCutscene() && state.isGame() && control.saves.getTotalPlaytime() > 8000){
|
||||
display(hint);
|
||||
}else{
|
||||
//moused over a derelict structure
|
||||
var build = world.buildWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());
|
||||
if(build != null && build.team == Team.derelict){
|
||||
events.add("derelictmouse");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -76,6 +85,17 @@ public class HintsFragment extends Fragment{
|
||||
events.clear();
|
||||
});
|
||||
|
||||
Events.on(BuildingCommandEvent.class, e -> {
|
||||
if(e.building instanceof PayloadBlockBuild<?>){
|
||||
events.add("factorycontrol");
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(BlockBuildBeginEvent.class, e -> {
|
||||
if(e.unit == player.unit() && e.tile.build instanceof ConstructBuild c && c.prevBuild != null && c.prevBuild.contains(b -> b.team == Team.derelict)){
|
||||
events.add("derelictbreak");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void checkNext(){
|
||||
@@ -85,7 +105,7 @@ public class HintsFragment extends Fragment{
|
||||
hints.sort(Hint::order);
|
||||
|
||||
Hint first = hints.find(Hint::show);
|
||||
if(first != null){
|
||||
if(first != null && !renderer.isCutscene() && state.isGame()){
|
||||
hints.remove(first);
|
||||
display(first);
|
||||
}
|
||||
@@ -138,42 +158,49 @@ public class HintsFragment extends Fragment{
|
||||
return current != null;
|
||||
}
|
||||
|
||||
static boolean isSerpulo(){
|
||||
return !state.rules.hasEnv(Env.scorching);
|
||||
}
|
||||
|
||||
public enum DefaultHint implements Hint{
|
||||
desktopMove(visibleDesktop, () -> Core.input.axis(Binding.move_x) != 0 || Core.input.axis(Binding.move_y) != 0),
|
||||
zoom(visibleDesktop, () -> Core.input.axis(KeyCode.scroll) != 0),
|
||||
mine(() -> player.unit().canMine() && isTutorial.get(), () -> player.unit().mining()),
|
||||
placeDrill(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.mechanicalDrill)),
|
||||
placeConveyor(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.conveyor)),
|
||||
placeTurret(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.duo)),
|
||||
breaking(isTutorial, () -> ui.hints.events.contains("break")),
|
||||
desktopShoot(visibleDesktop, () -> Vars.state.enemies > 0, () -> player.shooting),
|
||||
depositItems(() -> player.unit().hasItem(), () -> !player.unit().hasItem()),
|
||||
desktopPause(visibleDesktop, () -> isTutorial.get() && !Vars.net.active(), () -> Core.input.keyTap(Binding.pause)),
|
||||
research(isTutorial, () -> ui.research.isShown()),
|
||||
unitControl(() -> state.rules.defaultTeam.data().units.size > 2 && !net.active() && !player.dead(), () -> !player.dead() && !player.unit().spawnedByCore),
|
||||
breaking(() -> isTutorial.get() && state.rules.defaultTeam.data().getCount(Blocks.conveyor) > 5, () -> ui.hints.events.contains("break")),
|
||||
desktopShoot(visibleDesktop, () -> isSerpulo() && Vars.state.enemies > 0, () -> player.shooting),
|
||||
depositItems(() -> !player.dead() && player.unit().hasItem(), () -> !player.dead() && !player.unit().hasItem()),
|
||||
desktopPause(visibleDesktop, () -> isTutorial.get() && !Vars.net.active() && state.wave >= 2, () -> Core.input.keyTap(Binding.pause)),
|
||||
unitControl(() -> isSerpulo() && state.rules.defaultTeam.data().units.size > 2 && !net.active() && !player.dead(), () -> !player.dead() && !player.unit().spawnedByCore),
|
||||
unitSelectControl(() -> isSerpulo() && state.rules.defaultTeam.data().units.size > 3 && !net.active() && !player.dead(),
|
||||
() -> control.input.commandMode && control.input.selectedUnits.size > 0 && control.input.selectedUnits.first().controller() instanceof CommandAI ai && ai.targetPos != null),
|
||||
respawn(visibleMobile, () -> !player.dead() && !player.unit().spawnedByCore, () -> !player.dead() && player.unit().spawnedByCore),
|
||||
launch(() -> isTutorial.get() && state.rules.sector.isCaptured(), () -> ui.planet.isShown()),
|
||||
schematicSelect(visibleDesktop, () -> ui.hints.placedBlocks.contains(Blocks.router), () -> Core.input.keyRelease(Binding.schematic_select) || Core.input.keyTap(Binding.pick)),
|
||||
launch(() -> (isTutorial.get() || Vars.state.rules.sector == SectorPresets.onset.sector) && state.rules.sector.isCaptured(), () -> ui.planet.isShown()),
|
||||
schematicSelect(visibleDesktop, () -> ui.hints.placedBlocks.contains(Blocks.router) || ui.hints.placedBlocks.contains(Blocks.ductRouter), () -> Core.input.keyRelease(Binding.schematic_select) || Core.input.keyTap(Binding.pick)),
|
||||
conveyorPathfind(() -> control.input.block == Blocks.titaniumConveyor, () -> Core.input.keyRelease(Binding.diagonal_placement) || (mobile && Core.settings.getBool("swapdiagonal"))),
|
||||
boost(visibleDesktop, () -> !player.dead() && player.unit().type.canBoost, () -> Core.input.keyDown(Binding.boost)),
|
||||
command(() -> state.rules.defaultTeam.data().units.size > 3 && !net.active(), () -> player.unit().isCommanding()),
|
||||
payloadPickup(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().isEmpty(), () -> player.unit() instanceof Payloadc p && p.payloads().any()),
|
||||
payloadDrop(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().any(), () -> player.unit() instanceof Payloadc p && p.payloads().isEmpty()),
|
||||
waveFire(() -> Groups.fire.size() > 0 && Blocks.wave.unlockedNow(), () -> indexer.getAllied(state.rules.defaultTeam, BlockFlag.extinguisher).size() > 0),
|
||||
blockInfo(() -> !(state.isCampaign() && state.rules.sector == SectorPresets.groundZero.sector && state.wave < 3), () -> ui.content.isShown()),
|
||||
derelict(() -> ui.hints.events.contains("derelictmouse") && !isTutorial.get(), () -> ui.hints.events.contains("derelictbreak")),
|
||||
payloadPickup(() -> isSerpulo() && !player.dead() && player.unit() instanceof Payloadc p && p.payloads().isEmpty(), () -> player.unit() instanceof Payloadc p && p.payloads().any()),
|
||||
payloadDrop(() -> !player.dead() && player.unit() instanceof Payloadc p && p.payloads().any(), () -> player.unit() instanceof Payloadc p && p.payloads().isEmpty()),
|
||||
waveFire(() -> Groups.fire.size() > 0 && Blocks.wave.unlockedNow(), () -> indexer.getFlagged(state.rules.defaultTeam, BlockFlag.extinguisher).size > 0),
|
||||
generator(() -> control.input.block == Blocks.combustionGenerator, () -> ui.hints.placedBlocks.contains(Blocks.combustionGenerator)),
|
||||
guardian(() -> state.boss() != null && state.boss().armor >= 4, () -> state.boss() == null),
|
||||
coreUpgrade(() -> state.isCampaign() && Blocks.coreFoundation.unlocked()
|
||||
rebuildSelect(() -> state.rules.defaultTeam.data().plans.size >= 10, () -> control.input.isRebuildSelecting()),
|
||||
guardian(() -> state.boss() != null && isSerpulo() && state.boss().armor >= 4, () -> state.boss() == null),
|
||||
factoryControl(() -> !(state.isCampaign() && state.rules.sector.preset == SectorPresets.onset) &&
|
||||
state.rules.defaultTeam.data().getBuildings(Blocks.tankFabricator).size + state.rules.defaultTeam.data().getBuildings(Blocks.groundFactory).size > 0, () -> ui.hints.events.contains("factorycontrol")),
|
||||
coreUpgrade(() -> state.isCampaign() && state.rules.sector.planet == Planets.serpulo && Blocks.coreFoundation.unlocked()
|
||||
&& state.rules.defaultTeam.core() != null
|
||||
&& state.rules.defaultTeam.core().block == Blocks.coreShard
|
||||
&& state.rules.defaultTeam.core().items.has(Blocks.coreFoundation.requirements),
|
||||
() -> ui.hints.placedBlocks.contains(Blocks.coreFoundation)),
|
||||
presetLaunch(() -> state.isCampaign()
|
||||
&& state.getSector().preset == null
|
||||
&& SectorPresets.frozenForest.unlocked()
|
||||
&& SectorPresets.frozenForest.sector.save == null,
|
||||
&& state.getSector().preset == null,
|
||||
() -> state.isCampaign() && state.getSector().preset == SectorPresets.frozenForest),
|
||||
coreIncinerate(() -> state.isCampaign() && state.rules.defaultTeam.core() != null && state.rules.defaultTeam.core().items.get(Items.copper) >= state.rules.defaultTeam.core().storageCapacity - 10, () -> false),
|
||||
coopCampaign(() -> net.client() && state.isCampaign() && SectorPresets.groundZero.sector.hasBase(), () -> false),
|
||||
presetDifficulty(() -> state.isCampaign()
|
||||
&& state.getSector().preset == null
|
||||
&& state.getSector().threat >= 0.5f
|
||||
&& !SectorPresets.tarFields.sector.isCaptured(), //appear only when the player hasn't progressed much in the game yet
|
||||
() -> state.isCampaign() && state.getSector().preset != null),
|
||||
coreIncinerate(() -> state.isCampaign() && state.rules.defaultTeam.core() != null && state.rules.defaultTeam.core().items.get(Items.copper) >= state.rules.defaultTeam.core().storageCapacity - 10, () -> false)
|
||||
;
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -14,9 +14,11 @@ import arc.scene.ui.ImageButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
@@ -26,17 +28,23 @@ import mindustry.input.*;
|
||||
import mindustry.net.Packets.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.gen.Tex.*;
|
||||
|
||||
public class HudFragment extends Fragment{
|
||||
private static final float dsize = 65f;
|
||||
public class HudFragment{
|
||||
private static final float dsize = 65f, pauseHeight = 36f;
|
||||
|
||||
public final PlacementFragment blockfrag = new PlacementFragment();
|
||||
public PlacementFragment blockfrag = new PlacementFragment();
|
||||
public CoreItemsDisplay coreItems = new CoreItemsDisplay();
|
||||
public boolean shown = true;
|
||||
|
||||
private ImageButton flip;
|
||||
private CoreItemsDisplay coreItems = new CoreItemsDisplay();
|
||||
|
||||
private String hudText = "";
|
||||
private boolean showHudText;
|
||||
@@ -45,13 +53,86 @@ public class HudFragment extends Fragment{
|
||||
private Table lastUnlockLayout;
|
||||
private long lastToast;
|
||||
|
||||
@Override
|
||||
private Seq<Block> blocksOut = new Seq<>();
|
||||
|
||||
private void addBlockSelection(Table cont){
|
||||
Table blockSelection = new Table();
|
||||
var pane = new ScrollPane(blockSelection, Styles.smallPane);
|
||||
pane.setFadeScrollBars(false);
|
||||
Planet[] last = {state.rules.planet};
|
||||
pane.update(() -> {
|
||||
if(pane.hasScroll()){
|
||||
Element result = Core.scene.getHoverElement();
|
||||
if(result == null || !result.isDescendantOf(pane)){
|
||||
Core.scene.setScrollFocus(null);
|
||||
}
|
||||
}
|
||||
|
||||
if(state.rules.planet != last[0]){
|
||||
last[0] = state.rules.planet;
|
||||
rebuildBlockSelection(blockSelection, "");
|
||||
}
|
||||
});
|
||||
|
||||
cont.table(search -> {
|
||||
search.image(Icon.zoom).padRight(8);
|
||||
search.field("", text -> rebuildBlockSelection(blockSelection, text)).growX()
|
||||
.name("editor/search").maxTextLength(maxNameLength).get().setMessageText("@players.search");
|
||||
}).growX().pad(-2).padLeft(6f);
|
||||
cont.row();
|
||||
cont.add(pane).expandY().top().left();
|
||||
|
||||
rebuildBlockSelection(blockSelection, "");
|
||||
}
|
||||
|
||||
private void rebuildBlockSelection(Table blockSelection, String searchText){
|
||||
blockSelection.clear();
|
||||
|
||||
blocksOut.clear();
|
||||
blocksOut.addAll(Vars.content.blocks());
|
||||
blocksOut.sort((b1, b2) -> {
|
||||
int synth = Boolean.compare(b1.synthetic(), b2.synthetic());
|
||||
if(synth != 0) return synth;
|
||||
int ore = Boolean.compare(b1 instanceof OverlayFloor && b1 != Blocks.removeOre, b2 instanceof OverlayFloor && b2 != Blocks.removeOre);
|
||||
if(ore != 0) return ore;
|
||||
return Integer.compare(b1.id, b2.id);
|
||||
});
|
||||
|
||||
int i = 0;
|
||||
|
||||
for(Block block : blocksOut){
|
||||
TextureRegion region = block.uiIcon;
|
||||
|
||||
if(!Core.atlas.isFound(region)
|
||||
|| (!block.inEditor && !(block instanceof RemoveWall) && !(block instanceof RemoveOre))
|
||||
|| !block.isOnPlanet(state.rules.planet)
|
||||
|| block.buildVisibility == BuildVisibility.debugOnly
|
||||
|| (!searchText.isEmpty() && !block.localizedName.toLowerCase().contains(searchText.toLowerCase()))
|
||||
) continue;
|
||||
|
||||
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearNoneTogglei);
|
||||
button.getStyle().imageUp = new TextureRegionDrawable(region);
|
||||
button.clicked(() -> control.input.block = block);
|
||||
button.resizeImage(8 * 4f);
|
||||
button.update(() -> button.setChecked(control.input.block == block));
|
||||
blockSelection.add(button).size(48f).tooltip(block.localizedName);
|
||||
|
||||
if(++i % 6 == 0){
|
||||
blockSelection.row();
|
||||
}
|
||||
}
|
||||
|
||||
if(i == 0){
|
||||
blockSelection.add("@none.found").padLeft(54f).padTop(10f);
|
||||
}
|
||||
}
|
||||
|
||||
public void build(Group parent){
|
||||
|
||||
//warn about guardian/boss waves
|
||||
Events.on(WaveEvent.class, e -> {
|
||||
int max = 10;
|
||||
int winWave = state.isCampaign() && state.rules.winWave > 0 ? state.rules.winWave : Integer.MAX_VALUE;
|
||||
int winWave = state.rules.winWave > 0 ? state.rules.winWave : Integer.MAX_VALUE;
|
||||
outer:
|
||||
for(int i = state.wave - 1; i <= Math.min(state.wave + max, winWave - 2); i++){
|
||||
for(SpawnGroup group : state.rules.spawns){
|
||||
@@ -60,7 +141,7 @@ public class HudFragment extends Fragment{
|
||||
|
||||
//increments at which to warn about incoming guardian
|
||||
if(diff == 1 || diff == 2 || diff == 5 || diff == 10){
|
||||
showToast(Icon.warning, Core.bundle.format("wave.guardianwarn" + (diff == 1 ? ".one" : ""), diff));
|
||||
showToast(Icon.warning, group.type.emoji() + " " + Core.bundle.format("wave.guardianwarn" + (diff == 1 ? ".one" : ""), diff));
|
||||
}
|
||||
|
||||
break outer;
|
||||
@@ -70,7 +151,11 @@ public class HudFragment extends Fragment{
|
||||
});
|
||||
|
||||
Events.on(SectorCaptureEvent.class, e -> {
|
||||
showToast(Core.bundle.format("sector.captured", e.sector.isBeingPlayed() ? "" : e.sector.name() + " "));
|
||||
if(e.sector.isBeingPlayed()){
|
||||
ui.announce("@sector.capture.current", 5f);
|
||||
}else{
|
||||
showToast(Core.bundle.format("sector.capture", e.sector.name()));
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(SectorLoseEvent.class, e -> {
|
||||
@@ -89,8 +174,17 @@ public class HudFragment extends Fragment{
|
||||
//paused table
|
||||
parent.fill(t -> {
|
||||
t.name = "paused";
|
||||
t.top().visible(() -> state.isPaused() && shown).touchable = Touchable.disabled;
|
||||
t.table(Styles.black5, top -> top.label(() -> state.gameOver && state.isCampaign() ? "@sector.curlost" : "@paused").style(Styles.outlineLabel).pad(8f)).growX();
|
||||
t.top().visible(() -> state.isPaused() && shown && !netServer.isWaitingForPlayers()).touchable = Touchable.disabled;
|
||||
t.table(Styles.black6, top -> top.label(() -> state.gameOver && state.isCampaign() ? "@sector.curlost" : "@paused")
|
||||
.style(Styles.outlineLabel).pad(8f)).height(pauseHeight).growX();
|
||||
//.padLeft(dsize * 5 + 4f) to prevent alpha overlap on left
|
||||
});
|
||||
|
||||
//"waiting for players"
|
||||
parent.fill(t -> {
|
||||
t.name = "waiting";
|
||||
t.visible(() -> netServer.isWaitingForPlayers() && state.isPaused() && shown).touchable = Touchable.disabled;
|
||||
t.table(Styles.black6, top -> top.add("@waiting.players").style(Styles.outlineLabel).pad(18f));
|
||||
});
|
||||
|
||||
//minimap + position
|
||||
@@ -101,9 +195,12 @@ public class HudFragment extends Fragment{
|
||||
t.add(new Minimap()).name("minimap");
|
||||
t.row();
|
||||
//position
|
||||
t.label(() -> player.tileX() + "," + player.tileY())
|
||||
.visible(() -> Core.settings.getBool("position"))
|
||||
t.label(() ->
|
||||
(Core.settings.getBool("position") ? player.tileX() + "," + player.tileY() + "\n" : "") +
|
||||
(Core.settings.getBool("mouseposition") ? "[lightgray]" + World.toTile(Core.input.mouseWorldX()) + "," + World.toTile(Core.input.mouseWorldY()) : ""))
|
||||
.visible(() -> Core.settings.getBool("position") || Core.settings.getBool("mouseposition"))
|
||||
.touchable(Touchable.disabled)
|
||||
.style(Styles.outlineLabel)
|
||||
.name("position");
|
||||
t.top().right();
|
||||
});
|
||||
@@ -116,12 +213,19 @@ public class HudFragment extends Fragment{
|
||||
cont.top().left();
|
||||
|
||||
if(mobile){
|
||||
//for better inset visuals
|
||||
cont.rect((x, y, w, h) -> {
|
||||
if(Core.scene.marginTop > 0){
|
||||
Tex.paneRight.draw(x, y, w, Core.scene.marginTop);
|
||||
}
|
||||
}).fillX().row();
|
||||
|
||||
cont.table(select -> {
|
||||
select.name = "mobile buttons";
|
||||
select.left();
|
||||
select.defaults().size(dsize).left();
|
||||
|
||||
ImageButtonStyle style = Styles.clearTransi;
|
||||
ImageButtonStyle style = Styles.cleari;
|
||||
|
||||
select.button(Icon.menu, style, ui.paused::show).name("menu");
|
||||
flip = select.button(Icon.upOpen, style, this::toggleMenus).get();
|
||||
@@ -134,14 +238,14 @@ public class HudFragment extends Fragment{
|
||||
if(net.active()){
|
||||
ui.listfrag.toggle();
|
||||
}else{
|
||||
state.set(state.is(State.paused) ? State.playing : State.paused);
|
||||
state.set(state.isPaused() ? State.playing : State.paused);
|
||||
}
|
||||
}).name("pause").update(i -> {
|
||||
if(net.active()){
|
||||
i.getStyle().imageUp = Icon.players;
|
||||
}else{
|
||||
i.setDisabled(false);
|
||||
i.getStyle().imageUp = state.is(State.paused) ? Icon.play : Icon.pause;
|
||||
i.getStyle().imageUp = state.isPaused() ? Icon.play : Icon.pause;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -176,7 +280,7 @@ public class HudFragment extends Fragment{
|
||||
}
|
||||
|
||||
cont.update(() -> {
|
||||
if(Core.input.keyTap(Binding.toggle_menus) && !ui.chatfrag.shown() && !Core.scene.hasDialog() && !(Core.scene.getKeyboardFocus() instanceof TextField)){
|
||||
if(Core.input.keyTap(Binding.toggle_menus) && !ui.chatfrag.shown() && !Core.scene.hasDialog() && !Core.scene.hasField()){
|
||||
Core.settings.getBoolOnce("ui-hidden", () -> {
|
||||
ui.announce(Core.bundle.format("showui", Core.keybinds.get(Binding.toggle_menus).key.toString(), 11));
|
||||
});
|
||||
@@ -186,8 +290,19 @@ public class HudFragment extends Fragment{
|
||||
|
||||
Table wavesMain, editorMain;
|
||||
|
||||
cont.stack(wavesMain = new Table(), editorMain = new Table()).height(wavesMain.getPrefHeight())
|
||||
.name("waves/editor");
|
||||
cont.stack(wavesMain = new Table(), editorMain = new Table(), new Element(){
|
||||
//this may seem insane, but adding an empty element of a specific height to this stack fixes layout issues on mobile.
|
||||
|
||||
{
|
||||
visible = false;
|
||||
touchable = Touchable.disabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPrefHeight(){
|
||||
return Scl.scl(120f);
|
||||
}
|
||||
}).name("waves/editor");
|
||||
|
||||
wavesMain.visible(() -> shown && !state.isEditor());
|
||||
wavesMain.top().left().name = "waves";
|
||||
@@ -196,45 +311,63 @@ public class HudFragment extends Fragment{
|
||||
//wave info button with text
|
||||
s.add(makeStatusTable()).grow().name("status");
|
||||
|
||||
var rightStyle = new ImageButtonStyle(){{
|
||||
over = buttonRightOver;
|
||||
down = buttonRightDown;
|
||||
up = buttonRight;
|
||||
disabled = buttonRightDisabled;
|
||||
imageDisabledColor = Color.clear;
|
||||
imageUpColor = Color.white;
|
||||
}};
|
||||
|
||||
//table with button to skip wave
|
||||
s.button(Icon.play, Styles.righti, 30f, () -> {
|
||||
s.button(Icon.play, rightStyle, 30f, () -> {
|
||||
if(net.client() && player.admin){
|
||||
Call.adminRequest(player, AdminAction.wave);
|
||||
Call.adminRequest(player, AdminAction.wave, null);
|
||||
}else{
|
||||
logic.skipWave();
|
||||
}
|
||||
}).growY().fillX().right().width(40f).disabled(b -> !canSkipWave()).name("skip");
|
||||
}).width(dsize * 5 + 4f);
|
||||
}).growY().fillX().right().width(40f).disabled(b -> !canSkipWave()).name("skip").get().toBack();
|
||||
}).width(dsize * 5 + 4f).name("statustable");
|
||||
|
||||
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).name("boss");
|
||||
|
||||
wavesMain.row();
|
||||
addInfoTable(wavesMain.table().width(dsize * 5f + 4f).left().get());
|
||||
|
||||
editorMain.name = "editor";
|
||||
editorMain.table(Tex.buttonEdge4, t -> {
|
||||
//t.margin(0f);
|
||||
t.name = "teams";
|
||||
t.add("@editor.teams").growX().left();
|
||||
t.row();
|
||||
t.table(teams -> {
|
||||
|
||||
|
||||
t.top().table(teams -> {
|
||||
teams.left();
|
||||
int i = 0;
|
||||
for(Team team : Team.baseTeams){
|
||||
ImageButton button = teams.button(Tex.whiteui, Styles.clearTogglePartiali, 40f, () -> Call.setPlayerTeamEditor(player, team))
|
||||
.size(50f).margin(6f).get();
|
||||
ImageButton button = teams.button(Tex.whiteui, Styles.clearNoneTogglei, 33f, () -> Call.setPlayerTeamEditor(player, team))
|
||||
.size(45f).margin(6f).get();
|
||||
button.getImageCell().grow();
|
||||
button.getStyle().imageUpColor = team.color;
|
||||
button.update(() -> button.setChecked(player.team() == team));
|
||||
|
||||
if(++i % 3 == 0){
|
||||
teams.row();
|
||||
}
|
||||
}
|
||||
}).left();
|
||||
}).width(dsize * 5 + 4f);
|
||||
|
||||
teams.button(Icon.downOpen, Styles.emptyi, () -> Core.settings.put("editor-blocks-shown", !Core.settings.getBool("editor-blocks-shown")))
|
||||
.size(45f).update(m -> m.getStyle().imageUp = (Core.settings.getBool("editor-blocks-shown") ? Icon.upOpen : Icon.downOpen));
|
||||
}).top().left().row();
|
||||
|
||||
t.collapser(this::addBlockSelection, () -> Core.settings.getBool("editor-blocks-shown"));
|
||||
|
||||
}).width(dsize * 5 + 4f).top();
|
||||
if(mobile){
|
||||
editorMain.row().spacerY(() -> {
|
||||
if(control.input instanceof MobileInput mob && Core.settings.getBool("editor-blocks-shown")){
|
||||
if(Core.graphics.isPortrait()) return Core.graphics.getHeight() / 2f / Scl.scl(1f);
|
||||
if(mob.hasSchematic()) return 156f;
|
||||
if(mob.showCancel()) return 50f;
|
||||
}
|
||||
return 0f;
|
||||
});
|
||||
}
|
||||
|
||||
editorMain.row().add().growY();
|
||||
editorMain.visible(() -> shown && state.isEditor());
|
||||
|
||||
//fps display
|
||||
@@ -244,6 +377,7 @@ public class HudFragment extends Fragment{
|
||||
info.top().left().margin(4).visible(() -> Core.settings.getBool("fps") && shown);
|
||||
IntFormat fps = new IntFormat("fps");
|
||||
IntFormat ping = new IntFormat("ping");
|
||||
IntFormat tps = new IntFormat("tps");
|
||||
IntFormat mem = new IntFormat("memory");
|
||||
IntFormat memnative = new IntFormat("memory2");
|
||||
|
||||
@@ -257,68 +391,99 @@ public class HudFragment extends Fragment{
|
||||
}
|
||||
info.row();
|
||||
|
||||
info.label(() -> ping.get(netClient.getPing())).visible(net::client).left().style(Styles.outlineLabel).name("ping");
|
||||
info.label(() -> ping.get(netClient.getPing())).visible(net::client).left().style(Styles.outlineLabel).name("ping").row();
|
||||
info.label(() -> tps.get(state.serverTps == -1 ? 60 : state.serverTps)).visible(net::client).left().style(Styles.outlineLabel).name("tps").row();
|
||||
|
||||
}).top().left();
|
||||
});
|
||||
|
||||
//core items
|
||||
//core info
|
||||
parent.fill(t -> {
|
||||
t.name = "coreitems";
|
||||
t.top().add(coreItems);
|
||||
t.visible(() -> Core.settings.getBool("coreitems") && !mobile && !state.isPaused() && shown);
|
||||
t.top();
|
||||
|
||||
if(Core.settings.getBool("macnotch") ){
|
||||
t.margin(macNotchHeight);
|
||||
}
|
||||
|
||||
t.visible(() -> shown);
|
||||
|
||||
t.name = "coreinfo";
|
||||
|
||||
t.collapser(v -> v.add().height(pauseHeight), () -> state.isPaused() && !netServer.isWaitingForPlayers()).row();
|
||||
|
||||
t.table(c -> {
|
||||
//core items
|
||||
c.top().collapser(coreItems, () -> Core.settings.getBool("coreitems") && !mobile && shown).fillX().row();
|
||||
|
||||
float notifDuration = 240f;
|
||||
float[] coreAttackTime = {0};
|
||||
|
||||
Events.run(Trigger.teamCoreDamage, () -> coreAttackTime[0] = notifDuration);
|
||||
|
||||
//'core is under attack' table
|
||||
c.collapser(top -> top.background(Styles.black6).add("@coreattack").pad(8)
|
||||
.update(label -> label.color.set(Color.orange).lerp(Color.scarlet, Mathf.absin(Time.time, 2f, 1f))), true,
|
||||
() -> {
|
||||
if(!shown || state.isPaused()) return false;
|
||||
if(state.isMenu() || !player.team().data().hasCore()){
|
||||
coreAttackTime[0] = 0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
return (coreAttackTime[0] -= Time.delta) > 0;
|
||||
})
|
||||
.touchable(Touchable.disabled)
|
||||
.fillX().row();
|
||||
}).row();
|
||||
|
||||
var bossb = new StringBuilder();
|
||||
var bossText = Core.bundle.get("guardian");
|
||||
int maxBosses = 6;
|
||||
|
||||
t.table(v -> v.margin(10f)
|
||||
.add(new Bar(() -> {
|
||||
bossb.setLength(0);
|
||||
for(int i = 0; i < Math.min(state.teams.bosses.size, maxBosses); i++){
|
||||
bossb.append(state.teams.bosses.get(i).type.emoji());
|
||||
}
|
||||
if(state.teams.bosses.size > maxBosses){
|
||||
bossb.append("[accent]+[]");
|
||||
}
|
||||
bossb.append(" ");
|
||||
bossb.append(bossText);
|
||||
return bossb;
|
||||
}, () -> Pal.health, () -> {
|
||||
if(state.boss() == null) return 0f;
|
||||
float max = 0f, val = 0f;
|
||||
for(var boss : state.teams.bosses){
|
||||
max += boss.maxHealth;
|
||||
val += boss.health;
|
||||
}
|
||||
return max == 0f ? 0f : val / max;
|
||||
}).blink(Color.white).outline(new Color(0, 0, 0, 0.6f), 7f)).grow())
|
||||
.fillX().width(320f).height(60f).name("boss").visible(() -> state.rules.waves && state.boss() != null && !(mobile && Core.graphics.isPortrait())).padTop(7).row();
|
||||
|
||||
t.table(Styles.black3, p -> p.margin(4).label(() -> hudText).style(Styles.outlineLabel)).touchable(Touchable.disabled).with(p -> p.visible(() -> {
|
||||
p.color.a = Mathf.lerpDelta(p.color.a, Mathf.num(showHudText), 0.2f);
|
||||
if(state.isMenu()){
|
||||
p.color.a = 0f;
|
||||
showHudText = false;
|
||||
}
|
||||
|
||||
return p.color.a >= 0.001f;
|
||||
}));
|
||||
});
|
||||
|
||||
//spawner warning
|
||||
parent.fill(t -> {
|
||||
t.name = "nearpoint";
|
||||
t.touchable = Touchable.disabled;
|
||||
t.table(Styles.black, c -> c.add("@nearpoint")
|
||||
t.table(Styles.black6, 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))
|
||||
.labelAlign(Align.center, Align.center))
|
||||
.margin(6).update(u -> u.color.a = Mathf.lerpDelta(u.color.a, Mathf.num(spawner.playerNear()), 0.1f)).get().color.a = 0f;
|
||||
});
|
||||
|
||||
parent.fill(t -> {
|
||||
t.name = "waiting";
|
||||
t.visible(() -> netServer.isWaitingForPlayers());
|
||||
t.table(Tex.button, c -> c.add("@waiting.players"));
|
||||
});
|
||||
|
||||
//'core is under attack' table
|
||||
parent.fill(t -> {
|
||||
t.name = "coreattack";
|
||||
t.touchable = Touchable.disabled;
|
||||
float notifDuration = 240f;
|
||||
float[] coreAttackTime = {0};
|
||||
float[] coreAttackOpacity = {0};
|
||||
|
||||
Events.run(Trigger.teamCoreDamage, () -> {
|
||||
coreAttackTime[0] = notifDuration;
|
||||
});
|
||||
|
||||
t.top().visible(() -> {
|
||||
if(!shown) return false;
|
||||
if(state.isMenu() || !state.teams.get(player.team()).hasCore()){
|
||||
coreAttackTime[0] = 0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
t.color.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.color.set(Color.orange).lerp(Color.scarlet, Mathf.absin(Time.time, 2f, 1f)))).touchable(Touchable.disabled);
|
||||
});
|
||||
|
||||
//'saving' indicator
|
||||
parent.fill(t -> {
|
||||
t.name = "saving";
|
||||
@@ -326,55 +491,40 @@ public class HudFragment extends Fragment{
|
||||
t.add("@saving").style(Styles.outlineLabel);
|
||||
});
|
||||
|
||||
parent.fill(p -> {
|
||||
p.name = "hudtext";
|
||||
p.top().table(Styles.black3, t -> t.margin(4).label(() -> hudText)
|
||||
.style(Styles.outlineLabel)).padTop(10).visible(p.color.a >= 0.001f);
|
||||
p.update(() -> {
|
||||
p.color.a = Mathf.lerpDelta(p.color.a, Mathf.num(showHudText), 0.2f);
|
||||
if(state.isMenu()){
|
||||
p.color.a = 0f;
|
||||
showHudText = false;
|
||||
}
|
||||
});
|
||||
p.touchable = Touchable.disabled;
|
||||
});
|
||||
|
||||
//TODO DEBUG: rate table
|
||||
if(false)
|
||||
parent.fill(t -> {
|
||||
t.name = "rates";
|
||||
t.bottom().left();
|
||||
t.table(Styles.black6, c -> {
|
||||
Bits used = new Bits(content.items().size);
|
||||
parent.fill(t -> {
|
||||
t.bottom().left();
|
||||
t.table(Styles.black6, c -> {
|
||||
Bits used = new Bits(content.items().size);
|
||||
|
||||
Runnable rebuild = () -> {
|
||||
c.clearChildren();
|
||||
Runnable rebuild = () -> {
|
||||
c.clearChildren();
|
||||
|
||||
for(Item item : content.items()){
|
||||
if(state.rules.sector != null && state.rules.sector.info.getExport(item) >= 1){
|
||||
c.image(item.icon(Cicon.small));
|
||||
c.label(() -> (int)state.rules.sector.info.getExport(item) + " /s").color(Color.lightGray);
|
||||
c.row();
|
||||
for(Item item : content.items()){
|
||||
if(state.rules.sector != null && state.rules.sector.info.getExport(item) >= 1){
|
||||
c.image(item.uiIcon);
|
||||
c.label(() -> (int)state.rules.sector.info.getExport(item) + " /s").color(Color.lightGray);
|
||||
c.row();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
c.update(() -> {
|
||||
boolean wrong = false;
|
||||
for(Item item : content.items()){
|
||||
boolean has = state.rules.sector != null && state.rules.sector.info.getExport(item) >= 1;
|
||||
if(used.get(item.id) != has){
|
||||
used.set(item.id, has);
|
||||
wrong = true;
|
||||
c.update(() -> {
|
||||
boolean wrong = false;
|
||||
for(Item item : content.items()){
|
||||
boolean has = state.rules.sector != null && state.rules.sector.info.getExport(item) >= 1;
|
||||
if(used.get(item.id) != has){
|
||||
used.set(item.id, has);
|
||||
wrong = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(wrong){
|
||||
rebuild.run();
|
||||
}
|
||||
});
|
||||
}).visible(() -> state.isCampaign() && content.items().contains(i -> state.rules.sector != null && state.rules.sector.info.getExport(i) > 0));
|
||||
});
|
||||
if(wrong){
|
||||
rebuild.run();
|
||||
}
|
||||
});
|
||||
}).visible(() -> state.isCampaign() && content.items().contains(i -> state.rules.sector != null && state.rules.sector.info.getExport(i) > 0));
|
||||
});
|
||||
|
||||
blockfrag.build(parent);
|
||||
}
|
||||
@@ -407,11 +557,19 @@ public class HudFragment extends Fragment{
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasToast(){
|
||||
return Time.timeSinceMillis(lastToast) < 3.5f * 1000f;
|
||||
}
|
||||
|
||||
public void showToast(String text){
|
||||
showToast(Icon.ok, text);
|
||||
}
|
||||
|
||||
public void showToast(Drawable icon, String text){
|
||||
showToast(icon, -1, text);
|
||||
}
|
||||
|
||||
public void showToast(Drawable icon, float size, String text){
|
||||
if(state.isMenu()) return;
|
||||
|
||||
scheduleToast(() -> {
|
||||
@@ -424,7 +582,8 @@ public class HudFragment extends Fragment{
|
||||
}
|
||||
});
|
||||
table.margin(12);
|
||||
table.image(icon).pad(3);
|
||||
var cell = table.image(icon).pad(3);
|
||||
if(size > 0) cell.size(size);
|
||||
table.add(text).wrap().width(280f).get().setAlignment(Align.center, Align.center);
|
||||
table.pack();
|
||||
|
||||
@@ -462,7 +621,7 @@ public class HudFragment extends Fragment{
|
||||
Table in = new Table();
|
||||
|
||||
//create texture stack for displaying
|
||||
Image image = new Image(content.icon(Cicon.xlarge));
|
||||
Image image = new Image(content.uiIcon);
|
||||
image.setScaling(Scaling.fit);
|
||||
|
||||
in.add(image).size(8 * 6).pad(2);
|
||||
@@ -516,7 +675,7 @@ public class HudFragment extends Fragment{
|
||||
//if there's space, add it
|
||||
if(esize < cap){
|
||||
|
||||
Image image = new Image(content.icon(Cicon.medium));
|
||||
Image image = new Image(content.uiIcon);
|
||||
image.setScaling(Scaling.fit);
|
||||
|
||||
lastUnlockLayout.add(image);
|
||||
@@ -528,20 +687,19 @@ public class HudFragment extends Fragment{
|
||||
}
|
||||
}
|
||||
|
||||
public void showLaunchDirect(){
|
||||
Image image = new Image();
|
||||
image.color.a = 0f;
|
||||
image.setFillParent(true);
|
||||
image.actions(Actions.fadeIn(launchDuration / 60f, Interp.pow2In), Actions.delay(8f / 60f), Actions.remove());
|
||||
Core.scene.add(image);
|
||||
}
|
||||
|
||||
/** @deprecated see {@link CoreBuild#beginLaunch(CoreBlock)} */
|
||||
@Deprecated
|
||||
public void showLaunch(){
|
||||
float margin = 30f;
|
||||
|
||||
Image image = new Image();
|
||||
image.color.a = 0f;
|
||||
image.touchable = Touchable.disabled;
|
||||
image.setFillParent(true);
|
||||
image.actions(Actions.fadeIn(40f / 60f));
|
||||
image.actions(Actions.delay((coreLandDuration - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
|
||||
image.update(() -> {
|
||||
image.toFront();
|
||||
ui.loadfrag.toFront();
|
||||
if(state.isMenu()){
|
||||
image.remove();
|
||||
}
|
||||
@@ -549,14 +707,17 @@ public class HudFragment extends Fragment{
|
||||
Core.scene.add(image);
|
||||
}
|
||||
|
||||
/** @deprecated see {@link CoreBuild#beginLaunch(CoreBlock)} */
|
||||
@Deprecated
|
||||
public void showLand(){
|
||||
Image image = new Image();
|
||||
image.color.a = 1f;
|
||||
image.touchable = Touchable.disabled;
|
||||
image.setFillParent(true);
|
||||
image.actions(Actions.fadeOut(0.8f), Actions.remove());
|
||||
image.actions(Actions.fadeOut(35f / 60f), Actions.remove());
|
||||
image.update(() -> {
|
||||
image.toFront();
|
||||
ui.loadfrag.toFront();
|
||||
if(state.isMenu()){
|
||||
image.remove();
|
||||
}
|
||||
@@ -577,13 +738,14 @@ public class HudFragment extends Fragment{
|
||||
|
||||
StringBuilder ibuild = new StringBuilder();
|
||||
|
||||
IntFormat wavef = new IntFormat("wave");
|
||||
IntFormat wavefc = new IntFormat("wave.cap");
|
||||
IntFormat enemyf = new IntFormat("wave.enemy");
|
||||
IntFormat enemiesf = new IntFormat("wave.enemies");
|
||||
IntFormat enemycf = new IntFormat("wave.enemycore");
|
||||
IntFormat enemycsf = new IntFormat("wave.enemycores");
|
||||
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
|
||||
IntFormat
|
||||
wavef = new IntFormat("wave"),
|
||||
wavefc = new IntFormat("wave.cap"),
|
||||
enemyf = new IntFormat("wave.enemy"),
|
||||
enemiesf = new IntFormat("wave.enemies"),
|
||||
enemycf = new IntFormat("wave.enemycore"),
|
||||
enemycsf = new IntFormat("wave.enemycores"),
|
||||
waitingf = new IntFormat("wave.waiting", i -> {
|
||||
ibuild.setLength(0);
|
||||
int m = i/60;
|
||||
int s = i % 60;
|
||||
@@ -637,20 +799,14 @@ public class HudFragment extends Fragment{
|
||||
|
||||
if(Float.isNaN(value) || Float.isInfinite(value)) value = 1f;
|
||||
|
||||
drawInner(Pal.darkishGray);
|
||||
|
||||
Draw.beginStencil();
|
||||
|
||||
Fill.crect(x, y, width, height * value);
|
||||
|
||||
Draw.beginStenciled();
|
||||
|
||||
drawInner(Tmp.c1.set(color).lerp(Color.white, blink));
|
||||
|
||||
Draw.endStencil();
|
||||
drawInner(Pal.darkishGray, 1f);
|
||||
drawInner(Tmp.c1.set(color).lerp(Color.white, blink), value);
|
||||
}
|
||||
|
||||
void drawInner(Color color){
|
||||
void drawInner(Color color, float fract){
|
||||
if(fract < 0) return;
|
||||
|
||||
fract = Mathf.clamp(fract);
|
||||
if(flip){
|
||||
x += width;
|
||||
width = -width;
|
||||
@@ -658,21 +814,28 @@ public class HudFragment extends Fragment{
|
||||
|
||||
float stroke = width * 0.35f;
|
||||
float bh = height/2f;
|
||||
Draw.color(color);
|
||||
Draw.color(color, parentAlpha);
|
||||
|
||||
float f1 = Math.min(fract * 2f, 1f), f2 = (fract - 0.5f) * 2f;
|
||||
|
||||
float bo = -(1f - f1) * (width - stroke);
|
||||
|
||||
Fill.quad(
|
||||
x, y,
|
||||
x + stroke, y,
|
||||
x + width, y + bh,
|
||||
x + width - stroke, y + bh
|
||||
x + width + bo, y + bh * f1,
|
||||
x + width - stroke + bo, y + bh * f1
|
||||
);
|
||||
|
||||
Fill.quad(
|
||||
x + width, y + bh,
|
||||
x + width - stroke, y + bh,
|
||||
x, y + height,
|
||||
x + stroke, y + height
|
||||
);
|
||||
if(f2 > 0){
|
||||
float bx = x + (width - stroke) * (1f - f2);
|
||||
Fill.quad(
|
||||
x + width, y + bh,
|
||||
x + width - stroke, y + bh,
|
||||
bx, y + height * fract,
|
||||
bx + stroke, y + height * fract
|
||||
);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
|
||||
@@ -687,10 +850,10 @@ public class HudFragment extends Fragment{
|
||||
new Element(){
|
||||
@Override
|
||||
public void draw(){
|
||||
Draw.color(Pal.darkerGray);
|
||||
Draw.color(Pal.darkerGray, parentAlpha);
|
||||
Fill.poly(x + width/2f, y + height/2f, 6, height / Mathf.sqrt3);
|
||||
Draw.reset();
|
||||
Drawf.shadow(x + width/2f, y + height/2f, height * 1.13f);
|
||||
Drawf.shadow(x + width/2f, y + height/2f, height * 1.13f, parentAlpha);
|
||||
}
|
||||
},
|
||||
new Table(t -> {
|
||||
@@ -700,22 +863,67 @@ public class HudFragment extends Fragment{
|
||||
t.clicked(() -> {
|
||||
if(!player.dead() && mobile){
|
||||
Call.unitClear(player);
|
||||
control.input.recentRespawnTimer = 1f;
|
||||
control.input.controlledType = null;
|
||||
}
|
||||
});
|
||||
|
||||
t.add(new SideBar(() -> player.unit().healthf(), () -> true, true)).width(bw).growY().padRight(pad);
|
||||
t.add(new SideBar(() -> player.dead() ? 0f : player.unit().healthf(), () -> true, true)).width(bw).growY().padRight(pad);
|
||||
t.image(() -> player.icon()).scaling(Scaling.bounded).grow().maxWidth(54f);
|
||||
t.add(new SideBar(() -> player.dead() ? 0f : player.displayAmmo() ? player.unit().ammof() : player.unit().healthf(), () -> !player.displayAmmo(), false)).width(bw).growY().padLeft(pad).update(b -> {
|
||||
b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type.ammoType.color : Pal.health);
|
||||
b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type.ammoType.color() : Pal.health);
|
||||
});
|
||||
|
||||
t.getChildren().get(1).toFront();
|
||||
})).size(120f, 80).padRight(4);
|
||||
|
||||
table.labelWrap(() -> {
|
||||
Cell[] lcell = {null};
|
||||
boolean[] couldSkip = {true};
|
||||
|
||||
lcell[0] = table.labelWrap(() -> {
|
||||
|
||||
//update padding depend on whether the button to the right is there
|
||||
boolean can = canSkipWave();
|
||||
if(can != couldSkip[0]){
|
||||
if(canSkipWave()){
|
||||
lcell[0].padRight(8f);
|
||||
}else{
|
||||
lcell[0].padRight(-42f);
|
||||
}
|
||||
table.invalidateHierarchy();
|
||||
table.pack();
|
||||
couldSkip[0] = can;
|
||||
}
|
||||
|
||||
builder.setLength(0);
|
||||
|
||||
//mission overrides everything
|
||||
if(state.rules.mission != null && state.rules.mission.length() > 0){
|
||||
builder.append(state.rules.mission);
|
||||
return builder;
|
||||
}
|
||||
|
||||
//objectives override mission?
|
||||
if(state.rules.objectives.any()){
|
||||
boolean first = true;
|
||||
for(var obj : state.rules.objectives){
|
||||
if(!obj.qualified() || obj.hidden) continue;
|
||||
|
||||
String text = obj.text();
|
||||
if(text != null && !text.isEmpty()){
|
||||
if(!first) builder.append("\n[white]");
|
||||
builder.append(text);
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: display standard status when empty objective?
|
||||
if(builder.length() > 0){
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
if(!state.rules.waves && state.rules.attackMode){
|
||||
int sum = Math.max(state.teams.present.sum(t -> t.team != player.team() ? t.cores.size : 0), 1);
|
||||
builder.append(sum > 1 ? enemycsf.get(sum) : enemycf.get(sum));
|
||||
@@ -730,7 +938,7 @@ public class HudFragment extends Fragment{
|
||||
return builder;
|
||||
}
|
||||
|
||||
if(state.rules.winWave > 1 && state.rules.winWave >= state.wave && state.isCampaign()){
|
||||
if(state.rules.winWave > 1 && state.rules.winWave >= state.wave){
|
||||
builder.append(wavefc.get(state.wave, state.rules.winWave));
|
||||
}else{
|
||||
builder.append(wavef.get(state.wave));
|
||||
@@ -757,6 +965,37 @@ public class HudFragment extends Fragment{
|
||||
|
||||
table.row();
|
||||
|
||||
//TODO nobody reads details anyway.
|
||||
/*
|
||||
table.clicked(() -> {
|
||||
if(state.rules.objectives.any()){
|
||||
StringBuilder text = new StringBuilder();
|
||||
|
||||
boolean first = true;
|
||||
for(var obj : state.rules.objectives){
|
||||
if(!obj.qualified()) continue;
|
||||
|
||||
String details = obj.details();
|
||||
if(details != null){
|
||||
if(!first) text.append('\n');
|
||||
text.append(details);
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO this, as said before, could be much better.
|
||||
ui.showInfo(text.toString());
|
||||
}
|
||||
});*/
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private void addInfoTable(Table table){
|
||||
table.name = "infotable";
|
||||
table.left();
|
||||
|
||||
var count = new float[]{-1};
|
||||
table.table().update(t -> {
|
||||
if(player.unit() instanceof Payloadc payload){
|
||||
@@ -765,15 +1004,39 @@ public class HudFragment extends Fragment{
|
||||
count[0] = payload.payloadUsed();
|
||||
}
|
||||
}else{
|
||||
count[0] = -1;
|
||||
t.clear();
|
||||
}
|
||||
}).growX().visible(() -> player.unit() instanceof Payloadc p && p.payloadUsed() > 0).colspan(2);
|
||||
table.row();
|
||||
|
||||
return table;
|
||||
Bits statuses = new Bits();
|
||||
|
||||
table.table().update(t -> {
|
||||
t.left();
|
||||
Bits applied = player.dead() ? null : player.unit().statusBits();
|
||||
if(!statuses.equals(applied)){
|
||||
t.clear();
|
||||
|
||||
if(applied != null){
|
||||
for(StatusEffect effect : content.statusEffects()){
|
||||
if(applied.get(effect.id) && !effect.isHidden()){
|
||||
t.image(effect.uiIcon).size(iconMed).get()
|
||||
.addListener(new Tooltip(l -> l.label(() ->
|
||||
effect.localizedName + " [lightgray]" + UI.formatTime(player.unit().getDuration(effect))).style(Styles.outlineLabel)));
|
||||
}
|
||||
}
|
||||
|
||||
statuses.set(applied);
|
||||
}else{
|
||||
statuses.clear();
|
||||
}
|
||||
}
|
||||
}).left();
|
||||
}
|
||||
|
||||
private boolean canSkipWave(){
|
||||
return state.rules.waves && ((net.server() || player.admin) || !net.active()) && state.enemies == 0 && !spawner.isSpawning();
|
||||
return state.rules.waves && state.rules.waveSending && ((net.server() || player.admin) || !net.active()) && state.enemies == 0 && !spawner.isSpawning();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ import arc.scene.ui.layout.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
public class LoadingFragment extends Fragment{
|
||||
public class LoadingFragment{
|
||||
private Table table;
|
||||
private TextButton button;
|
||||
private Bar bar;
|
||||
private Label nameLabel;
|
||||
private float progValue;
|
||||
|
||||
@Override
|
||||
public void build(Group parent){
|
||||
parent.fill(t -> {
|
||||
//rect must fill screen completely.
|
||||
@@ -38,19 +38,34 @@ public class LoadingFragment extends Fragment{
|
||||
|
||||
text("@loading");
|
||||
|
||||
bar = t.add(new Bar()).pad(3).size(500f, 40f).visible(false).get();
|
||||
bar = t.add(new Bar()).pad(3).padTop(6).size(500f, 40f).visible(false).get();
|
||||
t.row();
|
||||
button = t.button("@cancel", () -> {}).pad(20).size(250f, 70f).visible(false).get();
|
||||
table = t;
|
||||
});
|
||||
}
|
||||
|
||||
public void toFront(){
|
||||
table.toFront();
|
||||
}
|
||||
|
||||
public void setProgress(Floatp progress){
|
||||
bar.reset(0f);
|
||||
bar.visible = true;
|
||||
bar.set(() -> ((int)(progress.get() * 100) + "%"), progress, Pal.accent);
|
||||
}
|
||||
|
||||
public void snapProgress(){
|
||||
bar.snap();
|
||||
}
|
||||
|
||||
public void setProgress(float progress){
|
||||
progValue = progress;
|
||||
if(!bar.visible){
|
||||
setProgress(() -> progValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void setButton(Runnable listener){
|
||||
button.visible = true;
|
||||
button.getListeners().remove(button.getListeners().size - 1);
|
||||
@@ -67,6 +82,7 @@ public class LoadingFragment extends Fragment{
|
||||
}
|
||||
|
||||
public void show(String text){
|
||||
button.visible = false;
|
||||
nameLabel.setColor(Color.white);
|
||||
bar.visible = false;
|
||||
table.clearActions();
|
||||
|
||||
@@ -9,7 +9,10 @@ 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.TextButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.EventType.*;
|
||||
@@ -18,19 +21,15 @@ import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.gen.Tex.*;
|
||||
|
||||
public class MenuFragment extends Fragment{
|
||||
public class MenuFragment{
|
||||
private Table container, submenu;
|
||||
private Button currentMenu;
|
||||
private MenuRenderer renderer;
|
||||
private Seq<MenuButton> customButtons = new Seq<>();
|
||||
public Seq<MenuButton> desktopButtons = null;
|
||||
|
||||
public MenuFragment(){
|
||||
Events.on(DisposeEvent.class, event -> {
|
||||
renderer.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build(Group parent){
|
||||
renderer = new MenuRenderer();
|
||||
|
||||
@@ -44,22 +43,40 @@ public class MenuFragment extends Fragment{
|
||||
parent.fill((x, y, w, h) -> renderer.render());
|
||||
|
||||
parent.fill(c -> {
|
||||
container = c;
|
||||
c.name = "menu container";
|
||||
c.pane(Styles.noBarPane, cont -> {
|
||||
container = cont;
|
||||
cont.name = "menu container";
|
||||
|
||||
if(!mobile){
|
||||
buildDesktop();
|
||||
Events.on(ResizeEvent.class, event -> buildDesktop());
|
||||
}else{
|
||||
buildMobile();
|
||||
Events.on(ResizeEvent.class, event -> buildMobile());
|
||||
}
|
||||
if(!mobile){
|
||||
c.left();
|
||||
buildDesktop();
|
||||
Events.on(ResizeEvent.class, event -> buildDesktop());
|
||||
}else{
|
||||
buildMobile();
|
||||
Events.on(ResizeEvent.class, event -> buildMobile());
|
||||
}
|
||||
}).with(pane -> {
|
||||
pane.setOverscroll(false, false);
|
||||
}).grow();
|
||||
});
|
||||
|
||||
parent.fill(c -> c.bottom().right().button(Icon.discord, new ImageButtonStyle(){{
|
||||
up = discordBanner;
|
||||
}}, ui.discord::show).marginTop(9f).marginLeft(10f).tooltip("@discord").size(84, 45).name("discord"));
|
||||
|
||||
//info icon
|
||||
if(mobile){
|
||||
parent.fill(c -> c.bottom().left().button("", Styles.infot, ui.about::show).size(84, 45).name("info"));
|
||||
parent.fill(c -> c.bottom().right().button("", Styles.discordt, ui.discord::show).size(84, 45).name("discord"));
|
||||
parent.fill(c -> c.bottom().left().button("", new TextButtonStyle(){{
|
||||
font = Fonts.def;
|
||||
fontColor = Color.white;
|
||||
up = infoBanner;
|
||||
}}, ui.about::show).size(84, 45).name("info"));
|
||||
|
||||
parent.fill((x, y, w, h) -> {
|
||||
if(Core.scene.marginBottom > 0){
|
||||
Tex.paneTop.draw(0, 0, Core.graphics.getWidth(), Core.scene.marginBottom);
|
||||
}
|
||||
});
|
||||
}else if(becontrol.active()){
|
||||
parent.fill(c -> c.bottom().right().button("@be.check", Icon.refresh, () -> {
|
||||
ui.loadfrag.show();
|
||||
@@ -78,18 +95,21 @@ public class MenuFragment extends Fragment{
|
||||
parent.fill((x, y, w, h) -> {
|
||||
TextureRegion logo = Core.atlas.find("logo");
|
||||
float width = Core.graphics.getWidth(), height = Core.graphics.getHeight() - Core.scene.marginTop;
|
||||
float logoscl = Scl.scl(1);
|
||||
float logoscl = Scl.scl(1) * logo.scale;
|
||||
float logow = Math.min(logo.width * logoscl, Core.graphics.getWidth() - Scl.scl(20));
|
||||
float logoh = logow * (float)logo.height / logo.width;
|
||||
|
||||
float fx = (int)(width / 2f);
|
||||
float fy = (int)(height - 6 - logoh) + logoh / 2 - (Core.graphics.isPortrait() ? Scl.scl(30f) : 0f);
|
||||
if(Core.settings.getBool("macnotch") ){
|
||||
fy -= Scl.scl(macNotchHeight);
|
||||
}
|
||||
|
||||
Draw.color();
|
||||
Draw.rect(logo, fx, fy, logow, logoh);
|
||||
|
||||
Fonts.def.setColor(Color.white);
|
||||
Fonts.def.draw(versionText, fx, fy - logoh/2f, Align.center);
|
||||
Fonts.outline.setColor(Color.white);
|
||||
Fonts.outline.draw(versionText, fx, fy - logoh/2f - Scl.scl(2f), Align.center);
|
||||
}).touchable = Touchable.disabled;
|
||||
}
|
||||
|
||||
@@ -109,7 +129,10 @@ public class MenuFragment extends Fragment{
|
||||
editor = new MobileButton(Icon.terrain, "@editor", () -> checkPlay(ui.maps::show)),
|
||||
tools = new MobileButton(Icon.settings, "@settings", ui.settings::show),
|
||||
mods = new MobileButton(Icon.book, "@mods", ui.mods::show),
|
||||
exit = new MobileButton(Icon.exit, "@quit", () -> Core.app.exit());
|
||||
exit = new MobileButton(Icon.exit, "@quit", () -> Core.app.exit()),
|
||||
about = new MobileButton(Icon.info, "@about.button", ui.about::show);
|
||||
|
||||
Seq<MobileButton> customs = customButtons.map(b -> new MobileButton(b.icon, b.text, b.runnable == null ? () -> {} : b.runnable));
|
||||
|
||||
if(!Core.graphics.isPortrait()){
|
||||
container.marginTop(60f);
|
||||
@@ -117,18 +140,20 @@ public class MenuFragment extends Fragment{
|
||||
container.add(join);
|
||||
container.add(custom);
|
||||
container.add(maps);
|
||||
// add odd custom buttons
|
||||
for(int i = 1; i < customs.size; i += 2){
|
||||
container.add(customs.get(i));
|
||||
}
|
||||
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);
|
||||
container.add(editor);
|
||||
container.add(tools);
|
||||
container.add(mods);
|
||||
// add even custom buttons (before the exit button)
|
||||
for(int i = 0; i < customs.size; i += 2){
|
||||
container.add(customs.get(i));
|
||||
}
|
||||
container.add(ios ? about : exit);
|
||||
}else{
|
||||
container.marginTop(0f);
|
||||
container.add(play);
|
||||
@@ -140,14 +165,13 @@ public class MenuFragment extends Fragment{
|
||||
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);
|
||||
container.add(mods);
|
||||
// add custom buttons
|
||||
for(int i = 0; i < customs.size; i++){
|
||||
container.add(customs.get(i));
|
||||
if(i % 2 == 0) container.row();
|
||||
}
|
||||
container.add(ios ? about : exit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,22 +188,28 @@ public class MenuFragment extends Fragment{
|
||||
t.defaults().width(width).height(70f);
|
||||
t.name = "buttons";
|
||||
|
||||
buttons(t,
|
||||
new Buttoni("@play", Icon.play,
|
||||
new Buttoni("@campaign", Icon.play, () -> checkPlay(ui.planet::show)),
|
||||
new Buttoni("@joingame", Icon.add, () -> checkPlay(ui.join::show)),
|
||||
new Buttoni("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)),
|
||||
new Buttoni("@loadgame", Icon.download, () -> checkPlay(ui.load::show))
|
||||
),
|
||||
new Buttoni("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new Buttoni("@workshop", Icon.book, platform::openWorkshop) : null,
|
||||
new Buttoni("@mods", Icon.book, ui.mods::show),
|
||||
//not enough space for this button
|
||||
//new Buttoni("@schematics", Icon.paste, ui.schematics::show),
|
||||
new Buttoni("@settings", Icon.settings, ui.settings::show),
|
||||
new Buttoni("@about.button", Icon.info, ui.about::show),
|
||||
new Buttoni("@quit", Icon.exit, Core.app::exit)
|
||||
);
|
||||
if(desktopButtons == null){
|
||||
desktopButtons = Seq.with(
|
||||
new MenuButton("@play", Icon.play,
|
||||
new MenuButton("@campaign", Icon.play, () -> checkPlay(ui.planet::show)),
|
||||
new MenuButton("@joingame", Icon.add, () -> checkPlay(ui.join::show)),
|
||||
new MenuButton("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)),
|
||||
new MenuButton("@loadgame", Icon.download, () -> checkPlay(ui.load::show))
|
||||
),
|
||||
new MenuButton("@database.button", Icon.menu,
|
||||
new MenuButton("@schematics", Icon.paste, ui.schematics::show),
|
||||
new MenuButton("@database", Icon.book, ui.database::show),
|
||||
new MenuButton("@about.button", Icon.info, ui.about::show)
|
||||
),
|
||||
new MenuButton("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new MenuButton("@workshop", Icon.steam, platform::openWorkshop) : null,
|
||||
new MenuButton("@mods", Icon.book, ui.mods::show),
|
||||
new MenuButton("@settings", Icon.settings, ui.settings::show)
|
||||
);
|
||||
}
|
||||
|
||||
buttons(t, desktopButtons.toArray(MenuButton.class));
|
||||
buttons(t, customButtons.toArray(MenuButton.class));
|
||||
buttons(t, new MenuButton("@quit", Icon.exit, Core.app::exit));
|
||||
}).width(width).growY();
|
||||
|
||||
container.table(background, t -> {
|
||||
@@ -194,7 +224,6 @@ public class MenuFragment extends Fragment{
|
||||
}
|
||||
|
||||
private void checkPlay(Runnable run){
|
||||
|
||||
if(!mods.hasContentErrors()){
|
||||
run.run();
|
||||
}else{
|
||||
@@ -217,23 +246,23 @@ public class MenuFragment extends Fragment{
|
||||
submenu.actions(Actions.alpha(1f), Actions.alpha(0f, 0.2f, Interp.fade), Actions.run(() -> submenu.clearChildren()));
|
||||
}
|
||||
|
||||
private void buttons(Table t, Buttoni... buttons){
|
||||
for(Buttoni b : buttons){
|
||||
private void buttons(Table t, MenuButton... buttons){
|
||||
for(MenuButton b : buttons){
|
||||
if(b == null) continue;
|
||||
Button[] out = {null};
|
||||
out[0] = t.button(b.text, b.icon, Styles.clearToggleMenut, () -> {
|
||||
out[0] = t.button(b.text, b.icon, Styles.flatToggleMenut, () -> {
|
||||
if(currentMenu == out[0]){
|
||||
currentMenu = null;
|
||||
fadeOutMenu();
|
||||
}else{
|
||||
if(b.submenu != null){
|
||||
if(b.submenu != null && b.submenu.any()){
|
||||
currentMenu = out[0];
|
||||
submenu.clearChildren();
|
||||
fadeInMenu();
|
||||
//correctly offset the button
|
||||
submenu.add().height((Core.graphics.getHeight() - Core.scene.marginTop - Core.scene.marginBottom - out[0].getY(Align.topLeft)) / Scl.scl(1f));
|
||||
submenu.row();
|
||||
buttons(submenu, b.submenu);
|
||||
buttons(submenu, b.submenu.toArray());
|
||||
}else{
|
||||
currentMenu = null;
|
||||
fadeOutMenu();
|
||||
@@ -246,24 +275,56 @@ public class MenuFragment extends Fragment{
|
||||
}
|
||||
}
|
||||
|
||||
private static class Buttoni{
|
||||
final Drawable icon;
|
||||
final String text;
|
||||
final Runnable runnable;
|
||||
final Buttoni[] submenu;
|
||||
/** Adds a custom button to the menu. */
|
||||
public void addButton(String text, Drawable icon, Runnable callback){
|
||||
addButton(new MenuButton(text, icon, callback));
|
||||
}
|
||||
|
||||
public Buttoni(String text, Drawable icon, Runnable runnable){
|
||||
/** Adds a custom button to the menu. */
|
||||
public void addButton(String text, Runnable callback){
|
||||
addButton(text, Styles.none, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom button to the menu.
|
||||
* If {@link MenuButton#submenu} is null or the player is on mobile, {@link MenuButton#runnable} is invoked on click.
|
||||
* Otherwise, {@link MenuButton#submenu} is shown.
|
||||
*/
|
||||
public void addButton(MenuButton button){
|
||||
customButtons.add(button);
|
||||
}
|
||||
|
||||
/** Represents a menu button definition. */
|
||||
public static class MenuButton{
|
||||
public final Drawable icon;
|
||||
public final String text;
|
||||
/** Runnable ran when the button is clicked. Ignored on desktop if {@link #submenu} is not null. */
|
||||
public final Runnable runnable;
|
||||
/** Submenu shown when this button is clicked. Used instead of {@link #runnable} on desktop. */
|
||||
public final @Nullable Seq<MenuButton> submenu;
|
||||
|
||||
/** Constructs a simple menu button, which behaves the same way on desktop and mobile. */
|
||||
public MenuButton(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){
|
||||
/** Constructs a button that runs the runnable when clicked on mobile or shows the submenu on desktop. */
|
||||
public MenuButton(String text, Drawable icon, Runnable runnable, MenuButton... submenu){
|
||||
this.icon = icon;
|
||||
this.text = text;
|
||||
this.runnable = runnable;
|
||||
this.submenu = submenu != null ? Seq.with(submenu) : null;
|
||||
}
|
||||
|
||||
/** Comstructs a desktop-only button; used internally. */
|
||||
MenuButton(String text, Drawable icon, MenuButton... submenu){
|
||||
this.icon = icon;
|
||||
this.text = text;
|
||||
this.runnable = () -> {};
|
||||
this.submenu = buttons;
|
||||
this.submenu = submenu != null ? Seq.with(submenu) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,33 @@ 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.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MinimapFragment extends Fragment{
|
||||
public class MinimapFragment{
|
||||
private boolean shown;
|
||||
float panx, pany, zoom = 1f, lastZoom = -1;
|
||||
private float baseSize = Scl.scl(5f);
|
||||
public Element elem;
|
||||
|
||||
@Override
|
||||
protected Rect getRectBounds(){
|
||||
float
|
||||
w = Core.graphics.getWidth(),
|
||||
h = Core.graphics.getHeight(),
|
||||
ratio = renderer.minimap.getTexture() == null ? 1f : (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width,
|
||||
size = baseSize * zoom * world.width();
|
||||
|
||||
return Tmp.r1.set(w/2f + panx*zoom - size/2f, h/2f + pany*zoom - size/2f * ratio, size, size * ratio);
|
||||
}
|
||||
|
||||
public void build(Group parent){
|
||||
elem = parent.fill((x, y, w, h) -> {
|
||||
w = Core.graphics.getWidth();
|
||||
@@ -35,7 +46,9 @@ public class MinimapFragment extends Fragment{
|
||||
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
|
||||
TextureRegion reg = Draw.wrap(renderer.minimap.getTexture());
|
||||
Draw.rect(reg, w/2f + panx*zoom, h/2f + pany*zoom, size, size * ratio);
|
||||
renderer.minimap.drawEntities(w/2f + panx*zoom - size/2f, h/2f + pany*zoom - size/2f * ratio, size, size * ratio, zoom, true);
|
||||
|
||||
Rect bounds = getRectBounds();
|
||||
renderer.minimap.drawEntities(bounds.x, bounds.y, bounds.width, bounds.height, zoom, true);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
@@ -69,13 +82,20 @@ public class MinimapFragment extends Fragment{
|
||||
|
||||
@Override
|
||||
public void pan(InputEvent event, float x, float y, float deltaX, float deltaY){
|
||||
panx += deltaX / zoom;
|
||||
pany += deltaY / zoom;
|
||||
if(event.keyCode != KeyCode.mouseRight){
|
||||
panx += deltaX / zoom;
|
||||
pany += deltaY / zoom;
|
||||
}else{
|
||||
panTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
super.touchDown(event, x, y, pointer, button);
|
||||
if(button == KeyCode.mouseRight){
|
||||
panTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,6 +126,12 @@ public class MinimapFragment extends Fragment{
|
||||
});
|
||||
}
|
||||
|
||||
public void panTo(float relativeX, float relativeY){
|
||||
Rect r = getRectBounds();
|
||||
Tmp.v1.set(relativeX, relativeY).sub(r.x, r.y).scl(1f / r.width, 1f / r.height).scl(world.unitWidth(), world.unitHeight());
|
||||
control.input.panCamera(Tmp.v1.clamp(-tilesize/2f, -tilesize/2f, world.unitWidth() + tilesize/2f, world.unitHeight() + tilesize/2f));
|
||||
}
|
||||
|
||||
public boolean shown(){
|
||||
return shown;
|
||||
}
|
||||
@@ -115,11 +141,14 @@ public class MinimapFragment extends Fragment{
|
||||
}
|
||||
|
||||
public void toggle(){
|
||||
float size = baseSize * zoom * world.width();
|
||||
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
|
||||
float px = player.dead() ? Core.camera.position.x : player.x, py = player.dead() ? Core.camera.position.y : player.y;
|
||||
panx = (size/2f - px / (world.width() * tilesize) * size) / zoom;
|
||||
pany = (size*ratio/2f - py / (world.height() * tilesize) * size*ratio) / zoom;
|
||||
if(renderer.minimap.getTexture() != null){
|
||||
float size = baseSize * zoom * world.width();
|
||||
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
|
||||
float px = player.dead() ? Core.camera.position.x : player.x, py = player.dead() ? Core.camera.position.y : player.y;
|
||||
panx = (size/2f - px / (world.width() * tilesize) * size) / zoom;
|
||||
pany = (size*ratio/2f - py / (world.height() * tilesize) * size*ratio) / zoom;
|
||||
}
|
||||
|
||||
shown = !shown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -8,25 +8,29 @@ import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.Tooltip.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class PlacementFragment extends Fragment{
|
||||
public class PlacementFragment{
|
||||
final int rowWidth = 4;
|
||||
|
||||
public Category currentCategory = Category.distribution;
|
||||
@@ -36,13 +40,17 @@ public class PlacementFragment extends Fragment{
|
||||
boolean[] categoryEmpty = new boolean[Category.all.length];
|
||||
ObjectMap<Category,Block> selectedBlocks = new ObjectMap<>();
|
||||
ObjectFloatMap<Category> scrollPositions = new ObjectFloatMap<>();
|
||||
Block menuHoverBlock;
|
||||
Displayable hover;
|
||||
Object lastDisplayState;
|
||||
@Nullable Block menuHoverBlock;
|
||||
@Nullable Displayable hover;
|
||||
@Nullable Building lastFlowBuild, nextFlowBuild;
|
||||
@Nullable Object lastDisplayState;
|
||||
@Nullable Team lastTeam;
|
||||
boolean wasHovered;
|
||||
Table blockTable, toggler, topTable;
|
||||
Table blockTable, toggler, topTable, blockCatTable, commandTable;
|
||||
Stack mainStack;
|
||||
ScrollPane blockPane;
|
||||
boolean blockSelectEnd;
|
||||
Runnable rebuildCommand;
|
||||
boolean blockSelectEnd, wasCommandMode;
|
||||
int blockSelectSeq;
|
||||
long blockSelectSeqMillis;
|
||||
Binding[] blockSelect = {
|
||||
@@ -65,11 +73,18 @@ public class PlacementFragment extends Fragment{
|
||||
public PlacementFragment(){
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
Core.app.post(() -> {
|
||||
currentCategory = Category.distribution;
|
||||
control.input.block = null;
|
||||
rebuild();
|
||||
});
|
||||
});
|
||||
|
||||
Events.run(Trigger.unitCommandChange, () -> {
|
||||
if(rebuildCommand != null){
|
||||
rebuildCommand.run();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(UnlockEvent.class, event -> {
|
||||
if(event.content instanceof Block){
|
||||
rebuild();
|
||||
@@ -79,14 +94,29 @@ public class PlacementFragment extends Fragment{
|
||||
Events.on(ResetEvent.class, event -> {
|
||||
selectedBlocks.clear();
|
||||
});
|
||||
|
||||
Events.run(Trigger.update, () -> {
|
||||
//disable flow updating on previous building so it doesn't waste CPU
|
||||
if(lastFlowBuild != null && lastFlowBuild != nextFlowBuild){
|
||||
if(lastFlowBuild.flowItems() != null) lastFlowBuild.flowItems().stopFlow();
|
||||
if(lastFlowBuild.liquids != null) lastFlowBuild.liquids.stopFlow();
|
||||
}
|
||||
|
||||
lastFlowBuild = nextFlowBuild;
|
||||
|
||||
if(nextFlowBuild != null){
|
||||
if(nextFlowBuild.flowItems() != null) nextFlowBuild.flowItems().updateFlow();
|
||||
if(nextFlowBuild.liquids != null) nextFlowBuild.liquids.updateFlow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Displayable hover(){
|
||||
return hover;
|
||||
}
|
||||
|
||||
void rebuild(){
|
||||
currentCategory = Category.turret;
|
||||
public void rebuild(){
|
||||
//category does not change on rebuild anymore, only on new world load
|
||||
Group group = toggler.parent;
|
||||
int index = toggler.getZIndex();
|
||||
toggler.remove();
|
||||
@@ -94,13 +124,17 @@ public class PlacementFragment extends Fragment{
|
||||
toggler.setZIndex(index);
|
||||
}
|
||||
|
||||
boolean gridUpdate(InputHandler input){
|
||||
scrollPositions.put(currentCategory, blockPane.getScrollY());
|
||||
boolean updatePick(InputHandler input){
|
||||
if(Core.input.keyTap(Binding.pick) && player.isBuilder() && !Core.scene.hasDialog()){ //mouse eyedropper select
|
||||
var build = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
|
||||
if(Core.input.keyTap(Binding.pick) && player.isBuilder()){ //mouse eyedropper select
|
||||
Building tile = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
Block tryRecipe = tile == null ? null : tile.block instanceof ConstructBlock ? ((ConstructBuild)tile).cblock : tile.block;
|
||||
Object tryConfig = tile == null ? null : tile.config();
|
||||
//can't middle click buildings in fog
|
||||
if(build != null && build.inFogTo(player.team())){
|
||||
build = null;
|
||||
}
|
||||
|
||||
Block tryRecipe = build == null ? null : build instanceof ConstructBuild c ? c.current : build.block;
|
||||
Object tryConfig = build == null || !build.block.copyConfig ? null : build.config();
|
||||
|
||||
for(BuildPlan req : player.unit().plans()){
|
||||
if(!req.breaking && req.block.bounds(req.x, req.y, Tmp.r1).contains(Core.input.mouseWorld())){
|
||||
@@ -110,15 +144,36 @@ public class PlacementFragment extends Fragment{
|
||||
}
|
||||
}
|
||||
|
||||
if(tryRecipe != null && tryRecipe.isVisible() && unlocked(tryRecipe)){
|
||||
if(tryRecipe == null && state.rules.editor){
|
||||
var tile = world.tileWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());
|
||||
if(tile != null){
|
||||
tryRecipe =
|
||||
tile.block() != Blocks.air ? tile.block() :
|
||||
tile.overlay() != Blocks.air ? tile.overlay() :
|
||||
tile.floor() != Blocks.air ? tile.floor() : null;
|
||||
}
|
||||
}
|
||||
|
||||
if(tryRecipe != null && ((tryRecipe.isVisible() && unlocked(tryRecipe)) || state.rules.editor)){
|
||||
input.block = tryRecipe;
|
||||
tryRecipe.lastConfig = tryConfig;
|
||||
currentCategory = input.block.category;
|
||||
if(tryRecipe.isVisible()){
|
||||
currentCategory = input.block.category;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ui.chatfrag.shown() || Core.scene.hasKeyboard()) return false;
|
||||
boolean gridUpdate(InputHandler input){
|
||||
scrollPositions.put(currentCategory, blockPane.getScrollY());
|
||||
|
||||
if(updatePick(input)){
|
||||
return true;
|
||||
}
|
||||
|
||||
if(ui.chatfrag.shown() || ui.consolefrag.shown() || Core.scene.hasKeyboard()) return false;
|
||||
|
||||
for(int i = 0; i < blockSelect.length; i++){
|
||||
if(Core.input.keyTap(blockSelect[i])){
|
||||
@@ -175,25 +230,38 @@ public class PlacementFragment extends Fragment{
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.category_prev)){
|
||||
int i = 0;
|
||||
do{
|
||||
currentCategory = currentCategory.prev();
|
||||
}while(categoryEmpty[currentCategory.ordinal()]);
|
||||
i ++;
|
||||
}while(categoryEmpty[currentCategory.ordinal()] && i < categoryEmpty.length);
|
||||
input.block = getSelectedBlock(currentCategory);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.category_next)){
|
||||
int i = 0;
|
||||
do{
|
||||
currentCategory = currentCategory.next();
|
||||
}while(categoryEmpty[currentCategory.ordinal()]);
|
||||
i ++;
|
||||
}while(categoryEmpty[currentCategory.ordinal()] && i < categoryEmpty.length);
|
||||
input.block = getSelectedBlock(currentCategory);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.block_info)){
|
||||
var build = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
Block hovering = build == null ? null : build instanceof ConstructBuild c ? c.current : build.block;
|
||||
Block displayBlock = menuHoverBlock != null ? menuHoverBlock : input.block != null ? input.block : hovering;
|
||||
if(displayBlock != null && displayBlock.unlockedNow()){
|
||||
ui.content.show(displayBlock);
|
||||
Events.fire(new BlockInfoEvent());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build(Group parent){
|
||||
parent.fill(full -> {
|
||||
toggler = full;
|
||||
@@ -217,9 +285,9 @@ public class PlacementFragment extends Fragment{
|
||||
blockTable.row();
|
||||
}
|
||||
|
||||
ImageButton button = blockTable.button(new TextureRegionDrawable(block.icon(Cicon.medium)), Styles.selecti, () -> {
|
||||
ImageButton button = blockTable.button(new TextureRegionDrawable(block.uiIcon), Styles.selecti, () -> {
|
||||
if(unlocked(block)){
|
||||
if(Core.input.keyDown(KeyCode.shiftLeft) && Fonts.getUnicode(block.name) != 0){
|
||||
if((Core.input.keyDown(KeyCode.shiftLeft) || Core.input.keyDown(KeyCode.controlLeft)) && Fonts.getUnicode(block.name) != 0){
|
||||
Core.app.setClipboardText((char)Fonts.getUnicode(block.name) + "");
|
||||
ui.showInfoFade("@copied");
|
||||
}else{
|
||||
@@ -228,7 +296,7 @@ public class PlacementFragment extends Fragment{
|
||||
}
|
||||
}
|
||||
}).size(46f).group(group).name("block-" + block.name).get();
|
||||
button.resizeImage(Cicon.medium.size);
|
||||
button.resizeImage(iconMed);
|
||||
|
||||
button.update(() -> { //color unplacable things gray
|
||||
Building core = player.core();
|
||||
@@ -276,13 +344,14 @@ public class PlacementFragment extends Fragment{
|
||||
|
||||
//don't refresh unnecessarily
|
||||
//refresh only when the hover state changes, or the displayed block changes
|
||||
if(wasHovered == isHovered && lastDisplayState == displayState) return;
|
||||
if(wasHovered == isHovered && lastDisplayState == displayState && lastTeam == player.team()) return;
|
||||
|
||||
topTable.clear();
|
||||
topTable.top().left().margin(5);
|
||||
|
||||
lastDisplayState = displayState;
|
||||
wasHovered = isHovered;
|
||||
lastTeam = player.team();
|
||||
|
||||
//show details of selected block, with costs
|
||||
if(displayBlock != null){
|
||||
@@ -302,12 +371,12 @@ public class PlacementFragment extends Fragment{
|
||||
}
|
||||
final String keyComboFinal = keyCombo;
|
||||
header.left();
|
||||
header.add(new Image(displayBlock.icon(Cicon.medium))).size(8 * 4);
|
||||
header.add(new Image(displayBlock.uiIcon)).size(8 * 4);
|
||||
header.labelWrap(() -> !unlocked(displayBlock) ? Core.bundle.get("block.unknown") : displayBlock.localizedName + keyComboFinal)
|
||||
.left().width(190f).padLeft(5);
|
||||
header.add().growX();
|
||||
if(unlocked(displayBlock)){
|
||||
header.button("?", Styles.clearPartialt, () -> {
|
||||
header.button("?", Styles.flatBordert, () -> {
|
||||
ui.content.show(displayBlock);
|
||||
Events.fire(new BlockInfoEvent());
|
||||
}).size(8 * 5).padTop(-5).padRight(-5).right().grow().name("blockinfo");
|
||||
@@ -321,15 +390,15 @@ public class PlacementFragment extends Fragment{
|
||||
for(ItemStack stack : displayBlock.requirements){
|
||||
req.table(line -> {
|
||||
line.left();
|
||||
line.image(stack.item.icon(Cicon.small)).size(8 * 2);
|
||||
line.image(stack.item.uiIcon).size(8 * 2);
|
||||
line.add(stack.item.localizedName).maxWidth(140f).fillX().color(Color.lightGray).padLeft(2).left().get().setEllipsis(true);
|
||||
line.labelWrap(() -> {
|
||||
Building core = player.core();
|
||||
if(core == null || state.rules.infiniteResources) return "*/*";
|
||||
int stackamount = Math.round(stack.amount * state.rules.buildCostMultiplier);
|
||||
if(core == null || state.rules.infiniteResources) return "*/" + stackamount;
|
||||
|
||||
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]");
|
||||
String color = (amount < stackamount / 2f ? "[scarlet]" : amount < stackamount ? "[accent]" : "[white]");
|
||||
|
||||
return color + UI.formatAmount(amount) + "[white]/" + stackamount;
|
||||
}).padLeft(5);
|
||||
@@ -338,11 +407,11 @@ public class PlacementFragment extends Fragment{
|
||||
}
|
||||
}).growX().left().margin(3);
|
||||
|
||||
if(!displayBlock.isPlaceable() || !player.isBuilder()){
|
||||
if((!displayBlock.isPlaceable() || !player.isBuilder()) && !state.rules.editor){
|
||||
topTable.row();
|
||||
topTable.table(b -> {
|
||||
b.image(Icon.cancel).padRight(2).color(Color.scarlet);
|
||||
b.add(!player.isBuilder() ? "@unit.nobuild" : "@banned").width(190f).wrap();
|
||||
b.add(!player.isBuilder() ? "@unit.nobuild" : !displayBlock.supportsEnv(state.rules.env) ? "@unsupported.environment" : "@banned").width(190f).wrap();
|
||||
b.left();
|
||||
}).padTop(2).left();
|
||||
}
|
||||
@@ -352,65 +421,295 @@ public class PlacementFragment extends Fragment{
|
||||
hovered.display(topTable);
|
||||
}
|
||||
});
|
||||
}).colspan(3).fillX().visible(this::hasInfoBox).touchable(Touchable.enabled);
|
||||
frame.row();
|
||||
frame.image().color(Pal.gray).colspan(3).height(4).growX();
|
||||
frame.row();
|
||||
frame.table(Tex.pane2, blocksSelect -> {
|
||||
blocksSelect.margin(4).marginTop(0);
|
||||
blockPane = 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();
|
||||
blockPane.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);
|
||||
}).colspan(3).fillX().visible(this::hasInfoBox).touchable(Touchable.enabled).row();
|
||||
|
||||
ButtonGroup<ImageButton> group = new ButtonGroup<>();
|
||||
frame.image().color(Pal.gray).colspan(3).height(4).growX().row();
|
||||
|
||||
//update category empty values
|
||||
for(Category cat : Category.all){
|
||||
Seq<Block> blocks = getUnlockedByCategory(cat);
|
||||
categoryEmpty[cat.ordinal()] = blocks.isEmpty();
|
||||
blockCatTable = new Table();
|
||||
commandTable = new Table(Tex.pane2);
|
||||
mainStack = new Stack();
|
||||
|
||||
mainStack.update(() -> {
|
||||
if(control.input.commandMode != wasCommandMode){
|
||||
mainStack.clearChildren();
|
||||
mainStack.addChild(control.input.commandMode ? commandTable : blockCatTable);
|
||||
|
||||
//hacky, but forces command table to be same width as blocks
|
||||
if(control.input.commandMode){
|
||||
commandTable.getCells().peek().width(blockCatTable.getWidth() / Scl.scl(1f));
|
||||
}
|
||||
|
||||
wasCommandMode = control.input.commandMode;
|
||||
}
|
||||
});
|
||||
|
||||
int f = 0;
|
||||
for(Category cat : getCategories()){
|
||||
if(f++ % 2 == 0) categories.row();
|
||||
frame.add(mainStack).colspan(3).fill();
|
||||
|
||||
if(categoryEmpty[cat.ordinal()]){
|
||||
categories.image(Styles.black6);
|
||||
continue;
|
||||
frame.row();
|
||||
|
||||
//for better inset visuals at the bottom
|
||||
frame.rect((x, y, w, h) -> {
|
||||
if(Core.scene.marginBottom > 0){
|
||||
Tex.paneLeft.draw(x, 0, w, y);
|
||||
}
|
||||
}).colspan(3).fillX().row();
|
||||
|
||||
//commandTable: commanded units
|
||||
{
|
||||
commandTable.touchable = Touchable.enabled;
|
||||
commandTable.add(Core.bundle.get("commandmode.name")).fill().center().labelAlign(Align.center).row();
|
||||
commandTable.image().color(Pal.accent).growX().pad(20f).padTop(0f).padBottom(4f).row();
|
||||
commandTable.table(u -> {
|
||||
u.left();
|
||||
int[] curCount = {0};
|
||||
UnitCommand[] currentCommand = {null};
|
||||
var commands = new Seq<UnitCommand>();
|
||||
|
||||
UnitStance[] currentStance = {null};
|
||||
var stances = new Seq<UnitStance>();
|
||||
|
||||
rebuildCommand = () -> {
|
||||
u.clearChildren();
|
||||
var units = control.input.selectedUnits;
|
||||
if(units.size > 0){
|
||||
int[] counts = new int[content.units().size];
|
||||
for(var unit : units){
|
||||
counts[unit.type.id] ++;
|
||||
}
|
||||
commands.clear();
|
||||
stances.clear();
|
||||
boolean firstCommand = false, firstStance = false;
|
||||
Table unitlist = u.table().growX().left().get();
|
||||
unitlist.left();
|
||||
|
||||
int col = 0;
|
||||
for(int i = 0; i < counts.length; i++){
|
||||
if(counts[i] > 0){
|
||||
var type = content.unit(i);
|
||||
unitlist.add(StatValues.stack(type, counts[i])).pad(4).with(b -> {
|
||||
b.clearListeners();
|
||||
b.addListener(Tooltips.getInstance().create(type.localizedName, false));
|
||||
|
||||
var listener = new ClickListener();
|
||||
|
||||
//left click -> select
|
||||
b.clicked(KeyCode.mouseLeft, () -> {
|
||||
control.input.selectedUnits.removeAll(unit -> unit.type != type);
|
||||
Events.fire(Trigger.unitCommandChange);
|
||||
});
|
||||
//right click -> remove
|
||||
b.clicked(KeyCode.mouseRight, () -> {
|
||||
control.input.selectedUnits.removeAll(unit -> unit.type == type);
|
||||
Events.fire(Trigger.unitCommandChange);
|
||||
});
|
||||
|
||||
b.addListener(listener);
|
||||
b.addListener(new HandCursorListener());
|
||||
//gray on hover
|
||||
b.update(() -> ((Group)b.getChildren().first()).getChildren().first().setColor(listener.isOver() ? Color.lightGray : Color.white));
|
||||
});
|
||||
|
||||
if(++col % 7 == 0){
|
||||
unitlist.row();
|
||||
}
|
||||
|
||||
if(!firstCommand){
|
||||
commands.add(type.commands);
|
||||
firstCommand = true;
|
||||
}else{
|
||||
//remove commands that this next unit type doesn't have
|
||||
commands.removeAll(com -> !type.commands.contains(com));
|
||||
}
|
||||
|
||||
if(!firstStance){
|
||||
stances.add(type.stances);
|
||||
firstStance = true;
|
||||
}else{
|
||||
//remove commands that this next unit type doesn't have
|
||||
stances.removeAll(st -> !type.stances.contains(st));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//list commands
|
||||
if(commands.size > 1){
|
||||
u.row();
|
||||
|
||||
u.table(coms -> {
|
||||
coms.left();
|
||||
int scol = 0;
|
||||
for(var command : commands){
|
||||
coms.button(Icon.icons.get(command.icon, Icon.cancel), Styles.clearNoneTogglei, () -> {
|
||||
Call.setUnitCommand(player, units.mapInt(un -> un.id).toArray(), command);
|
||||
}).checked(i -> currentCommand[0] == command).size(50f).tooltip(command.localized(), true);
|
||||
|
||||
if(++scol % 6 == 0) coms.row();
|
||||
}
|
||||
|
||||
}).fillX().padTop(4f).left();
|
||||
}
|
||||
|
||||
//list stances
|
||||
if(stances.size > 1){
|
||||
u.row();
|
||||
|
||||
if(commands.size > 1){
|
||||
u.add(new Image(Tex.whiteui)).height(3f).color(Pal.gray).pad(7f).growX().row();
|
||||
}
|
||||
|
||||
u.table(coms -> {
|
||||
coms.left();
|
||||
int scol = 0;
|
||||
for(var stance : stances){
|
||||
|
||||
coms.button(Icon.icons.get(stance.icon, Icon.cancel), Styles.clearNoneTogglei, () -> {
|
||||
Call.setUnitStance(player, units.mapInt(un -> un.id).toArray(), stance);
|
||||
}).checked(i -> currentStance[0] == stance).size(50f).tooltip(stance.localized(), true);
|
||||
|
||||
if(++scol % 6 == 0) coms.row();
|
||||
}
|
||||
}).fillX().padTop(4f).left();
|
||||
}
|
||||
}else{
|
||||
u.add(Core.bundle.get("commandmode.nounits")).color(Color.lightGray).growX().center().labelAlign(Align.center).pad(6);
|
||||
}
|
||||
};
|
||||
|
||||
u.update(() -> {
|
||||
{
|
||||
boolean hadCommand = false, hadStance = false;
|
||||
UnitCommand shareCommand = null;
|
||||
UnitStance shareStance = null;
|
||||
|
||||
//find the command that all units have, or null if they do not share one
|
||||
for(var unit : control.input.selectedUnits){
|
||||
if(unit.isCommandable()){
|
||||
var nextCommand = unit.command().command;
|
||||
|
||||
if(hadCommand){
|
||||
if(shareCommand != nextCommand){
|
||||
shareCommand = null;
|
||||
}
|
||||
}else{
|
||||
shareCommand = nextCommand;
|
||||
hadCommand = true;
|
||||
}
|
||||
|
||||
var nextStance = unit.command().stance;
|
||||
|
||||
if(hadStance){
|
||||
if(shareStance != nextStance){
|
||||
shareStance = null;
|
||||
}
|
||||
}else{
|
||||
shareStance = nextStance;
|
||||
hadStance = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentCommand[0] = shareCommand;
|
||||
currentStance[0] = shareStance;
|
||||
|
||||
int size = control.input.selectedUnits.size;
|
||||
if(curCount[0] != size){
|
||||
curCount[0] = size;
|
||||
rebuildCommand.run();
|
||||
}
|
||||
|
||||
//not a huge fan of running input logic here, but it's convenient as the stance arrays are all here...
|
||||
for(UnitStance stance : stances){
|
||||
//first stance must always be the stop stance
|
||||
if(stance.keybind != null && Core.input.keyTap(stance.keybind)){
|
||||
Call.setUnitStance(player, control.input.selectedUnits.mapInt(un -> un.id).toArray(), stance);
|
||||
}
|
||||
}
|
||||
|
||||
for(UnitCommand command : commands){
|
||||
//first stance must always be the stop stance
|
||||
if(command.keybind != null && Core.input.keyTap(command.keybind)){
|
||||
Call.setUnitCommand(player, control.input.selectedUnits.mapInt(un -> un.id).toArray(), command);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
rebuildCommand.run();
|
||||
}).grow();
|
||||
}
|
||||
|
||||
//blockCatTable: all blocks | all categories
|
||||
{
|
||||
blockCatTable.table(Tex.pane2, blocksSelect -> {
|
||||
blocksSelect.margin(4).marginTop(0);
|
||||
blockPane = blocksSelect.pane(blocks -> blockTable = blocks).height(194f).update(pane -> {
|
||||
if(pane.hasScroll()){
|
||||
Element result = Core.scene.getHoverElement();
|
||||
if(result == null || !result.isDescendantOf(pane)){
|
||||
Core.scene.setScrollFocus(null);
|
||||
}
|
||||
}
|
||||
}).grow().get();
|
||||
blockPane.setStyle(Styles.smallPane);
|
||||
blocksSelect.row();
|
||||
blocksSelect.table(t -> {
|
||||
t.image().color(Pal.gray).height(4f).colspan(4).growX();
|
||||
t.row();
|
||||
control.input.buildPlacementUI(t);
|
||||
}).name("inputTable").growX();
|
||||
}).fillY().bottom().touchable(Touchable.enabled);
|
||||
blockCatTable.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){
|
||||
Seq<Block> blocks = getUnlockedByCategory(cat);
|
||||
categoryEmpty[cat.ordinal()] = blocks.isEmpty();
|
||||
}
|
||||
|
||||
categories.button(ui.getIcon(cat.name()), Styles.clearToggleTransi, () -> {
|
||||
currentCategory = cat;
|
||||
if(control.input.block != null){
|
||||
control.input.block = getSelectedBlock(currentCategory);
|
||||
boolean needsAssign = categoryEmpty[currentCategory.ordinal()];
|
||||
|
||||
int f = 0;
|
||||
for(Category cat : getCategories()){
|
||||
if(f++ % 2 == 0) categories.row();
|
||||
|
||||
if(categoryEmpty[cat.ordinal()]){
|
||||
categories.image(Styles.black6);
|
||||
continue;
|
||||
}
|
||||
rebuildCategory.run();
|
||||
}).group(group).update(i -> i.setChecked(currentCategory == cat)).name("category-" + cat.name());
|
||||
}
|
||||
}).fillY().bottom().touchable(Touchable.enabled);
|
||||
|
||||
if(needsAssign){
|
||||
currentCategory = cat;
|
||||
needsAssign = false;
|
||||
}
|
||||
|
||||
categories.button(ui.getIcon(cat.name()), Styles.clearTogglei, () -> {
|
||||
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);
|
||||
}
|
||||
|
||||
mainStack.add(blockCatTable);
|
||||
|
||||
rebuildCategory.run();
|
||||
frame.update(() -> {
|
||||
if(gridUpdate(control.input)) rebuildCategory.run();
|
||||
if(!control.input.commandMode && gridUpdate(control.input)){
|
||||
rebuildCategory.run();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -421,7 +720,7 @@ public class PlacementFragment extends Fragment{
|
||||
}
|
||||
|
||||
Seq<Block> getByCategory(Category cat){
|
||||
return returnArray.selectFrom(content.blocks(), block -> block.category == cat && block.isVisible());
|
||||
return returnArray.selectFrom(content.blocks(), block -> block.category == cat && block.isVisible() && block.environmentBuildable());
|
||||
}
|
||||
|
||||
Seq<Block> getUnlockedByCategory(Category cat){
|
||||
@@ -433,7 +732,8 @@ public class PlacementFragment extends Fragment{
|
||||
}
|
||||
|
||||
boolean unlocked(Block block){
|
||||
return block.unlockedNow();
|
||||
return block.unlockedNowHost() && block.placeablePlayer && block.environmentBuildable() &&
|
||||
block.supportsEnv(state.rules.env);
|
||||
}
|
||||
|
||||
boolean hasInfoBox(){
|
||||
@@ -441,16 +741,15 @@ public class PlacementFragment extends Fragment{
|
||||
return control.input.block != null || menuHoverBlock != null || hover != null;
|
||||
}
|
||||
|
||||
/** Returns the thing being hovered over. */
|
||||
@Nullable
|
||||
Displayable hovered(){
|
||||
/** @return the thing being hovered over. */
|
||||
public @Nullable Displayable hovered(){
|
||||
Vec2 v = topTable.stageToLocalCoordinates(Core.input.mouse());
|
||||
|
||||
//if the mouse intersects the table or the UI has the mouse, no hovering can occur
|
||||
if(Core.scene.hasMouse() || topTable.hit(v.x, v.y, false) != null) return null;
|
||||
if(Core.scene.hasMouse(Core.input.mouseX(), Core.input.mouseY()) || topTable.hit(v.x, v.y, false) != null) return null;
|
||||
|
||||
//check for a unit
|
||||
Unit unit = Units.closestOverlap(player.team(), Core.input.mouseWorldX(), Core.input.mouseWorldY(), 5f, u -> !u.isLocal());
|
||||
Unit unit = Units.closestOverlap(player.team(), Core.input.mouseWorldX(), Core.input.mouseWorldY(), 5f, u -> !u.isLocal() && u.displayable());
|
||||
//if cursor has a unit, display it
|
||||
if(unit != null) return unit;
|
||||
|
||||
@@ -458,13 +757,12 @@ public class PlacementFragment extends Fragment{
|
||||
Tile hoverTile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
if(hoverTile != null){
|
||||
//if the tile has a building, display it
|
||||
if(hoverTile.build != null){
|
||||
hoverTile.build.updateFlow = true;
|
||||
return hoverTile.build;
|
||||
if(hoverTile.build != null && hoverTile.build.displayable() && !hoverTile.build.inFogTo(player.team())){
|
||||
return nextFlowBuild = hoverTile.build;
|
||||
}
|
||||
|
||||
//if the tile has a drop, display the drop
|
||||
if(hoverTile.drop() != null){
|
||||
if((hoverTile.drop() != null && hoverTile.block() == Blocks.air) || hoverTile.wallDrop() != null || hoverTile.floor().liquidDrop != null){
|
||||
return hoverTile;
|
||||
}
|
||||
}
|
||||
|
||||
83
core/src/mindustry/ui/fragments/PlanConfigFragment.java
Normal file
83
core/src/mindustry/ui/fragments/PlanConfigFragment.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package mindustry.ui.fragments;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.actions.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/**
|
||||
* Displays the configuration UI for build plans before they have been placed.
|
||||
* Shamelessly stolen from Foo's Client.
|
||||
*/
|
||||
public class PlanConfigFragment{
|
||||
Table table = new Table();
|
||||
BuildPlan selected;
|
||||
|
||||
public void build(Group parent){
|
||||
table.visible = false;
|
||||
parent.addChild(table);
|
||||
|
||||
Events.on(EventType.ResetEvent.class, e -> forceHide());
|
||||
}
|
||||
|
||||
public void showConfig(BuildPlan plan){
|
||||
if(this.selected == plan || plan.block == null){
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
Block block = plan.block;
|
||||
if(!block.configurable) return;
|
||||
selected = plan;
|
||||
table.clear();
|
||||
|
||||
var options = new Seq<UnlockableContent>();
|
||||
block.getPlanConfigs(options);
|
||||
|
||||
if(options.isEmpty()) return;
|
||||
|
||||
ItemSelection.buildTable(
|
||||
table, options,
|
||||
() -> selected != null ? (selected.config instanceof UnlockableContent c ? c : null) : null,
|
||||
content -> {
|
||||
selected.config = content;
|
||||
hide();
|
||||
},
|
||||
block.selectionRows, block.selectionColumns
|
||||
);
|
||||
table.pack();
|
||||
table.setTransform(true);
|
||||
table.visible = true;
|
||||
table.actions(Actions.scaleTo(0f, 1f), Actions.visible(true),
|
||||
Actions.scaleTo(1f, 1f, 0.07f, Interp.pow3Out));
|
||||
table.update(() -> {
|
||||
table.setOrigin(Align.center);
|
||||
if(plan.isDone() || !(control.input.selectPlans.contains(plan) || player.dead() || player.unit().plans.contains(plan))){
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
Vec2 pos = Core.input.mouseScreen(plan.drawx(), plan.drawy() - block.size * tilesize / 2.0F - 1);
|
||||
table.setPosition(pos.x, pos.y, Align.top);
|
||||
});
|
||||
}
|
||||
|
||||
public void forceHide(){
|
||||
table.visible = false;
|
||||
selected = null;
|
||||
}
|
||||
|
||||
public void hide(){
|
||||
selected = null;
|
||||
table.actions(Actions.scaleTo(0f, 1f, 0.06f, Interp.pow3Out), Actions.visible(false));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,32 @@
|
||||
package mindustry.ui.fragments;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.ImageButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.net.Packets.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class PlayerListFragment extends Fragment{
|
||||
public class PlayerListFragment{
|
||||
public Table content = new Table().marginRight(13f).marginLeft(13f);
|
||||
private boolean visible = false;
|
||||
private Interval timer = new Interval();
|
||||
private TextField sField;
|
||||
private TextField search;
|
||||
private Seq<Player> players = new Seq<>();
|
||||
|
||||
@Override
|
||||
public void build(Group parent){
|
||||
content.name = "players";
|
||||
parent.fill(cont -> {
|
||||
@@ -47,15 +50,12 @@ public class PlayerListFragment extends Fragment{
|
||||
cont.table(Tex.buttonTrans, pane -> {
|
||||
pane.label(() -> Core.bundle.format(Groups.player.size() == 1 ? "players.single" : "players", Groups.player.size()));
|
||||
pane.row();
|
||||
sField = pane.field(null, text -> {
|
||||
rebuild();
|
||||
}).grow().pad(8).get();
|
||||
sField.name = "search";
|
||||
sField.setMaxLength(maxNameLength);
|
||||
sField.setMessageText(Core.bundle.format("players.search"));
|
||||
|
||||
search = pane.field(null, text -> rebuild()).grow().pad(8).name("search").maxTextLength(maxNameLength).get();
|
||||
search.setMessageText(Core.bundle.get("players.search"));
|
||||
|
||||
pane.row();
|
||||
pane.pane(content).grow().get().setScrollingDisabled(true, false);
|
||||
pane.pane(content).grow().scrollX(false);
|
||||
pane.row();
|
||||
|
||||
pane.table(menu -> {
|
||||
@@ -67,102 +67,191 @@ public class PlayerListFragment extends Fragment{
|
||||
menu.button("@close", this::toggle);
|
||||
}).margin(0f).pad(10f).growX();
|
||||
|
||||
}).touchable(Touchable.enabled).margin(14f);
|
||||
}).touchable(Touchable.enabled).margin(14f).minWidth(360f);
|
||||
});
|
||||
|
||||
rebuild();
|
||||
}
|
||||
|
||||
public void rebuild(){
|
||||
boolean allowTeamSwitch = !state.isCampaign() && (state.rules.pvp || state.rules.infiniteResources);
|
||||
|
||||
content.clear();
|
||||
|
||||
float h = 74f;
|
||||
float h = 50f;
|
||||
boolean found = false;
|
||||
|
||||
players.clear();
|
||||
Groups.player.copy(players);
|
||||
|
||||
players.sort(Structs.comps(Structs.comparing(Player::team), Structs.comparingBool(p -> !p.admin)));
|
||||
if(search.getText().length() > 0){
|
||||
players.retainAll(p -> Strings.stripColors(p.name().toLowerCase()).contains(search.getText().toLowerCase()));
|
||||
}
|
||||
|
||||
for(var user : players){
|
||||
found = true;
|
||||
NetConnection connection = user.con;
|
||||
|
||||
if(connection == null && net.server() && !user.isLocal()) return;
|
||||
if(sField.getText().length() > 0 && !user.name().toLowerCase().contains(sField.getText().toLowerCase()) && !Strings.stripColors(user.name().toLowerCase()).contains(sField.getText().toLowerCase())) return;
|
||||
|
||||
Table button = new Table();
|
||||
button.left();
|
||||
button.margin(5).marginBottom(10);
|
||||
|
||||
Table table = new Table(){
|
||||
ClickListener listener = new ClickListener();
|
||||
|
||||
Table iconTable = new Table(){
|
||||
@Override
|
||||
public void draw(){
|
||||
super.draw();
|
||||
Draw.color(Pal.gray);
|
||||
Draw.colorMul(user.team().color, listener.isOver() ? 1.3f : 1f);
|
||||
Draw.alpha(parentAlpha);
|
||||
Lines.stroke(Scl.scl(4f));
|
||||
Lines.rect(x, y, width, height);
|
||||
Draw.reset();
|
||||
}
|
||||
};
|
||||
table.margin(8);
|
||||
table.add(new Image(user.icon()).setScaling(Scaling.bounded)).grow();
|
||||
table.name = user.name();
|
||||
|
||||
button.add(table).size(h);
|
||||
button.labelWrap("[#" + user.color().toString().toUpperCase() + "]" + user.name()).width(170f).pad(10);
|
||||
boolean clickable = !(state.rules.fog && state.rules.pvp && user.team() != player.team());
|
||||
|
||||
if(clickable){
|
||||
iconTable.addListener(listener);
|
||||
iconTable.addListener(new HandCursorListener());
|
||||
}
|
||||
iconTable.margin(8);
|
||||
iconTable.add(new Image(user.icon()).setScaling(Scaling.bounded)).grow();
|
||||
iconTable.name = user.name();
|
||||
iconTable.touchable = Touchable.enabled;
|
||||
|
||||
iconTable.tapped(() -> {
|
||||
if(!user.dead() && clickable){
|
||||
control.input.spectate(user.unit());
|
||||
ui.showInfoFade(Core.bundle.format("viewplayer", user.name), 1f);
|
||||
}
|
||||
});
|
||||
|
||||
button.add(iconTable).size(h);
|
||||
button.labelWrap("[#" + user.color().toString().toUpperCase() + "]" + user.name()).style(Styles.outlineLabel).width(170f).pad(10);
|
||||
button.add().grow();
|
||||
|
||||
button.background(Tex.underline);
|
||||
|
||||
button.image(Icon.admin).visible(() -> user.admin && !(!user.isLocal() && net.server())).padRight(5).get().updateVisibility();
|
||||
|
||||
if((net.server() || player.admin) && !user.isLocal() && (!user.admin || net.server())){
|
||||
var ustyle = new ImageButtonStyle(){{
|
||||
down = Styles.none;
|
||||
up = Styles.none;
|
||||
imageDownColor = Pal.accent;
|
||||
imageUpColor = Color.white;
|
||||
imageOverColor = Color.lightGray;
|
||||
}};
|
||||
|
||||
if(net.server() || (player.admin && (!user.admin || user == player))){
|
||||
button.add().growY();
|
||||
|
||||
float bs = (h) / 2f;
|
||||
if(allowTeamSwitch || user != player){
|
||||
button.button(Icon.menu, ustyle, () -> {
|
||||
var dialog = new BaseDialog(user.coloredName());
|
||||
|
||||
button.table(t -> {
|
||||
t.defaults().size(bs);
|
||||
dialog.title.setColor(Color.white);
|
||||
dialog.titleTable.remove();
|
||||
|
||||
t.button(Icon.hammer, Styles.clearPartiali,
|
||||
() -> ui.showConfirm("@confirm", Core.bundle.format("confirmban", user.name()), () -> Call.adminRequest(user, AdminAction.ban)));
|
||||
t.button(Icon.cancel, Styles.clearPartiali,
|
||||
() -> ui.showConfirm("@confirm", Core.bundle.format("confirmkick", user.name()), () -> Call.adminRequest(user, AdminAction.kick)));
|
||||
dialog.closeOnBack();
|
||||
|
||||
t.row();
|
||||
var bstyle = Styles.defaultt;
|
||||
|
||||
t.button(Icon.admin, Styles.clearTogglePartiali, () -> {
|
||||
if(net.client()) return;
|
||||
dialog.cont.add(user.coloredName()).row();
|
||||
dialog.cont.image(Tex.whiteui, Pal.accent).fillX().height(3f).pad(4f).row();
|
||||
|
||||
String id = user.uuid();
|
||||
dialog.cont.pane(t -> {
|
||||
t.defaults().size(220f, 55f).pad(3f);
|
||||
|
||||
if(netServer.admins.isAdmin(id, connection.address)){
|
||||
ui.showConfirm("@confirm", Core.bundle.format("confirmunadmin", user.name()), () -> netServer.admins.unAdminPlayer(id));
|
||||
}else{
|
||||
ui.showConfirm("@confirm", Core.bundle.format("confirmadmin", user.name()), () -> netServer.admins.adminPlayer(id, user.usid()));
|
||||
}
|
||||
}).update(b -> b.setChecked(user.admin))
|
||||
.disabled(b -> net.client())
|
||||
.touchable(() -> net.client() ? Touchable.disabled : Touchable.enabled)
|
||||
.checked(user.admin);
|
||||
if(user != player){
|
||||
t.button("@player.ban", Icon.hammer, bstyle, () -> {
|
||||
ui.showConfirm("@confirm", Core.bundle.format("confirmban", user.name()), () -> Call.adminRequest(user, AdminAction.ban, null));
|
||||
dialog.hide();
|
||||
}).row();
|
||||
|
||||
t.button(Icon.zoom, Styles.clearPartiali, () -> Call.adminRequest(user, AdminAction.trace));
|
||||
t.button("@player.kick", Icon.cancel, bstyle, () -> {
|
||||
ui.showConfirm("@confirm", Core.bundle.format("confirmkick", user.name()), () -> Call.adminRequest(user, AdminAction.kick, null));
|
||||
dialog.hide();
|
||||
}).row();
|
||||
|
||||
}).padRight(12).size(bs + 10f, bs);
|
||||
t.button("@player.trace", Icon.zoom, bstyle, () -> {
|
||||
Call.adminRequest(user, AdminAction.trace, null);
|
||||
dialog.hide();
|
||||
}).row();
|
||||
}
|
||||
|
||||
//there's generally no reason to team switch outside PvP or sandbox, and it's basically an easy way to cheat
|
||||
if(allowTeamSwitch){
|
||||
t.button("@player.team", Icon.redo, bstyle, () -> {
|
||||
var teamSelect = new BaseDialog(Core.bundle.get("player.team") + ": " + user.name);
|
||||
teamSelect.setFillParent(false);
|
||||
|
||||
var group = new ButtonGroup<>();
|
||||
|
||||
int i = 0;
|
||||
|
||||
for(Team team : Team.baseTeams){
|
||||
var b = new ImageButton(Tex.whiteui, Styles.clearNoneTogglei);
|
||||
b.margin(4f);
|
||||
b.getImageCell().grow();
|
||||
b.getStyle().imageUpColor = team.color;
|
||||
b.clicked(() -> {
|
||||
Call.adminRequest(user, AdminAction.switchTeam, team);
|
||||
teamSelect.hide();
|
||||
});
|
||||
teamSelect.cont.add(b).size(50f).checked(a -> user.team() == team).group(group);
|
||||
|
||||
if(i++ % 3 == 2) teamSelect.cont.row();
|
||||
}
|
||||
|
||||
teamSelect.addCloseButton();
|
||||
teamSelect.show();
|
||||
|
||||
dialog.hide();
|
||||
}).row();
|
||||
}
|
||||
|
||||
if(!net.client() && !user.isLocal()){
|
||||
t.button("@player.admin", Icon.admin, Styles.togglet, () -> {
|
||||
dialog.hide();
|
||||
String id = user.uuid();
|
||||
|
||||
if(user.admin){
|
||||
ui.showConfirm("@confirm", Core.bundle.format("confirmunadmin", user.name()), () -> {
|
||||
netServer.admins.unAdminPlayer(id);
|
||||
user.admin = false;
|
||||
});
|
||||
}else{
|
||||
ui.showConfirm("@confirm", Core.bundle.format("confirmadmin", user.name()), () -> {
|
||||
netServer.admins.adminPlayer(id, user.usid());
|
||||
user.admin = true;
|
||||
});
|
||||
}
|
||||
}).checked(b -> user.admin).row();
|
||||
}
|
||||
}).row();
|
||||
|
||||
dialog.cont.button("@back", Icon.left, dialog::hide).padTop(-1f).size(220f, 55f);
|
||||
|
||||
dialog.show();
|
||||
|
||||
|
||||
}).size(h);
|
||||
}
|
||||
}else if(!user.isLocal() && !user.admin && net.client() && Groups.player.size() >= 3 && player.team() == user.team()){ //votekick
|
||||
button.add().growY();
|
||||
|
||||
button.button(Icon.hammer, Styles.clearPartiali,
|
||||
() -> {
|
||||
ui.showConfirm("@confirm", Core.bundle.format("confirmvotekick", user.name()), () -> {
|
||||
Call.sendChatMessage("/votekick " + user.name());
|
||||
});
|
||||
}).size(h);
|
||||
button.button(Icon.hammer, ustyle,
|
||||
() -> ui.showTextInput("@votekick.reason", Core.bundle.format("votekick.reason.message", user.name()), "",
|
||||
reason -> Call.sendChatMessage("/votekick #" + user.id + " " + reason)))
|
||||
.size(h);
|
||||
}
|
||||
|
||||
content.add(button).padBottom(-6).width(350f).maxHeight(h + 14);
|
||||
content.row();
|
||||
content.image().height(4f).color(state.rules.pvp ? user.team().color : Pal.gray).growX();
|
||||
content.add(button).width(350f).height(h + 14);
|
||||
content.row();
|
||||
}
|
||||
|
||||
@@ -179,7 +268,7 @@ public class PlayerListFragment extends Fragment{
|
||||
rebuild();
|
||||
}else{
|
||||
Core.scene.setKeyboardFocus(null);
|
||||
sField.clearText();
|
||||
search.clearText();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public class BranchTreeLayout implements TreeLayout{
|
||||
}
|
||||
|
||||
private float getWidthOrHeightOfNode(TreeNode treeNode, boolean returnWidth){
|
||||
return returnWidth ? treeNode.width : treeNode.height;
|
||||
return returnWidth ? treeNode.calcWidth() : treeNode.height;
|
||||
}
|
||||
|
||||
private float getNodeThickness(TreeNode treeNode){
|
||||
|
||||
@@ -11,12 +11,23 @@ public interface TreeLayout{
|
||||
public T parent;
|
||||
|
||||
//internal stuff
|
||||
public float mode, prelim, change, shift;
|
||||
public float mode, prelim, change, shift, cachedWidth = -1f;
|
||||
public int number = -1, leaves;
|
||||
public TreeNode thread, ancestor;
|
||||
|
||||
public boolean isLeaf(){
|
||||
return children == null || children.length == 0;
|
||||
}
|
||||
|
||||
public float calcWidth(){
|
||||
if(children == null) return width;
|
||||
if(cachedWidth > 0) return cachedWidth;
|
||||
|
||||
float cWidth = 0;
|
||||
for(T node : children){
|
||||
cWidth += node.calcWidth();
|
||||
}
|
||||
return cachedWidth = Math.max(width, cWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user