Merge branch 'master' into port-field
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user