Files
Mindustry/core/src/mindustry/ui/fragments/HudFragment.java
2020-07-01 11:50:33 -04:00

660 lines
24 KiB
Java

package mindustry.ui.fragments;
import arc.*;
import arc.graphics.*;
import arc.input.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.ImageButton.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.net.Packets.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class HudFragment extends Fragment{
public final PlacementFragment blockfrag = new PlacementFragment();
private ImageButton flip;
private Table lastUnlockTable;
private Table lastUnlockLayout;
private boolean shown = true;
private float dsize = 47.2f;
//TODO implement
private CoreItemsDisplay coreItems = new CoreItemsDisplay();
private String hudText = "";
private boolean showHudText;
private long lastToast;
@Override
public void build(Group parent){
//TODO details and stuff
Events.on(SectorCaptureEvent.class, e ->{
showToast("Sector[accent] captured[]!");
});
//TODO full implementation
Events.on(ResetEvent.class, e -> {
coreItems.resetUsed();
});
//TODO tear this all down
//menu at top left
parent.fill(cont -> {
cont.setName("overlaymarker");
cont.top().left();
if(mobile){
cont.table(select -> {
select.left();
select.defaults().size(dsize).left();
ImageButtonStyle style = Styles.clearTransi;
select.button(Icon.menu, style, ui.paused::show);
flip = select.button(Icon.upOpen, style, this::toggleMenus).get();
select.button(Icon.paste, style, ui.schematics::show);
select.button(Icon.pause, style, () -> {
if(net.active()){
ui.listfrag.toggle();
}else{
state.set(state.is(State.paused) ? State.playing : State.paused);
}
}).name("pause").update(i -> {
if(net.active()){
i.getStyle().imageUp = Icon.players;
}else{
i.setDisabled(false);
i.getStyle().imageUp = state.is(State.paused) ? Icon.play : Icon.pause;
}
});
select.button(Icon.chat, style,() -> {
if(net.active() && mobile){
if(ui.chatfrag.shown()){
ui.chatfrag.hide();
}else{
ui.chatfrag.toggle();
}
}else if(state.isCampaign()){
ui.tech.show();
}else{
ui.database.show();
}
}).update(i -> {
if(net.active() && mobile){
i.getStyle().imageUp = Icon.chat;
}else{
i.getStyle().imageUp = Icon.book;
}
});
select.image().color(Pal.gray).width(4f).fillY();
});
cont.row();
cont.image().height(4f).color(Pal.gray).fillX();
cont.row();
}
//TODO BUTTONS FOR VIEWING EXPORTS/IMPORTS/RESEARCH/MAP/ETC
/*
cont.table(t -> {
});
cont.row();*/
cont.update(() -> {
if(Core.input.keyTap(Binding.toggle_menus) && !ui.chatfrag.shown() && !Core.scene.hasDialog() && !(Core.scene.getKeyboardFocus() instanceof TextField)){
toggleMenus();
}
});
Table wavesMain, editorMain;
cont.stack(wavesMain = new Table(), editorMain = new Table()).height(wavesMain.getPrefHeight());
{
wavesMain.visible(() -> shown && !state.isEditor());
wavesMain.top().left();
Stack stack = new Stack();
Button waves = new Button(Styles.waveb);
Table btable = new Table().margin(0);
stack.add(waves);
stack.add(btable);
addWaveTable(waves);
addPlayButton(btable);
wavesMain.add(stack).width(dsize * 5 + 4f);
wavesMain.row();
wavesMain.table(Tex.button, t -> t.margin(10f).add(new Bar("boss.health", Pal.health, () -> state.boss() == null ? 0f : state.boss().healthf()).blink(Color.white))
.grow()).fillX().visible(() -> state.rules.waves && state.boss() != null).height(60f).get();
wavesMain.row();
}
{
editorMain.table(Tex.buttonEdge4, t -> {
//t.margin(0f);
t.add("$editor.teams").growX().left();
t.row();
t.table(teams -> {
teams.left();
int i = 0;
for(Team team : Team.baseTeams){
ImageButton button = teams.button(Tex.whiteui, Styles.clearTogglePartiali, 40f, () -> Call.setPlayerTeamEditor(player, team))
.size(50f).margin(6f).get();
button.getImageCell().grow();
button.getStyle().imageUpColor = team.color;
button.update(() -> button.setChecked(player.team() == team));
if(++i % 3 == 0){
teams.row();
}
}
}).left();
}).width(dsize * 5 + 4f);
editorMain.visible(() -> shown && state.isEditor());
}
//fps display
cont.table(info -> {
info.touchable(Touchable.disabled);
info.top().left().margin(4).visible(() -> Core.settings.getBool("fps") && shown);
info.update(() -> info.setTranslation(state.rules.waves || state.isEditor() ? 0f : -Scl.scl(dsize * 4 + 3), 0));
IntFormat fps = new IntFormat("fps");
IntFormat ping = new IntFormat("ping");
info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left().style(Styles.outlineLabel);
info.row();
info.label(() -> ping.get(netClient.getPing())).visible(net::client).left().style(Styles.outlineLabel);
}).top().left();
});
parent.fill(t -> {
t.visible(() -> Core.settings.getBool("minimap") && !state.rules.tutorial && shown);
//minimap
t.add(new Minimap());
t.row();
//position
t.label(() -> player.tileX() + "," + player.tileY())
.visible(() -> Core.settings.getBool("position") && !state.rules.tutorial)
.touchable(Touchable.disabled);
t.top().right();
});
//spawner warning
parent.fill(t -> {
t.touchable(Touchable.disabled);
t.table(Styles.black, c -> c.add("$nearpoint")
.update(l -> l.setColor(Tmp.c1.set(Color.white).lerp(Color.scarlet, Mathf.absin(Time.time(), 10f, 1f))))
.get().setAlignment(Align.center, Align.center))
.margin(6).update(u -> u.color.a = Mathf.lerpDelta(u.color.a, Mathf.num(spawner.playerNear()), 0.1f)).get().color.a = 0f;
});
parent.fill(t -> {
t.visible(() -> netServer.isWaitingForPlayers());
t.table(Tex.button, c -> c.add("$waiting.players"));
});
//'core is under attack' table
parent.fill(t -> {
t.touchable(Touchable.disabled);
float notifDuration = 240f;
float[] coreAttackTime = {0};
float[] coreAttackOpacity = {0};
Events.on(Trigger.teamCoreDamage, () -> {
coreAttackTime[0] = notifDuration;
});
t.top().visible(() -> {
if(state.isMenu() || !state.teams.get(player.team()).hasCore()){
coreAttackTime[0] = 0f;
return false;
}
t.getColor().a = coreAttackOpacity[0];
if(coreAttackTime[0] > 0){
coreAttackOpacity[0] = Mathf.lerpDelta(coreAttackOpacity[0], 1f, 0.1f);
}else{
coreAttackOpacity[0] = Mathf.lerpDelta(coreAttackOpacity[0], 0f, 0.1f);
}
coreAttackTime[0] -= Time.delta();
return coreAttackOpacity[0] > 0;
});
t.table(Tex.button, top -> top.add("$coreattack").pad(2)
.update(label -> label.getColor().set(Color.orange).lerp(Color.scarlet, Mathf.absin(Time.time(), 2f, 1f)))).touchable(Touchable.disabled);
});
//tutorial text
parent.fill(t -> {
Runnable resize = () -> {
t.clearChildren();
t.top().right().visible(() -> state.rules.tutorial);
t.stack(new Button(){{
marginLeft(48f);
labelWrap(() -> control.tutorial.stage.text() + (control.tutorial.canNext() ? "\n\n" + Core.bundle.get("tutorial.next") : "")).width(!Core.graphics.isPortrait() ? 400f : 160f).pad(2f);
clicked(() -> control.tutorial.nextSentence());
setDisabled(() -> !control.tutorial.canNext());
}},
new Table(f -> {
f.left().button(Icon.left, Styles.emptyi, () -> {
control.tutorial.prevSentence();
}).width(44f).growY().visible(() -> control.tutorial.canPrev());
}));
};
resize.run();
Events.on(ResizeEvent.class, e -> resize.run());
});
//paused table
parent.fill(t -> {
t.top().visible(() -> state.isPaused()).touchable(Touchable.disabled);
t.table(Tex.buttonTrans, top -> top.add("$paused").pad(5f));
});
//'saving' indicator
parent.fill(t -> {
t.bottom().visible(() -> control.saves.isSaving());
t.add("$saving").style(Styles.outlineLabel);
});
parent.fill(p -> {
p.top().table(Styles.black3, t -> t.margin(4).label(() -> hudText)
.style(Styles.outlineLabel)).padTop(10).visible(p.color.a >= 0.001f);
p.update(() -> {
p.color.a = Mathf.lerpDelta(p.color.a, Mathf.num(showHudText), 0.2f);
if(state.isMenu()){
p.color.a = 0f;
showHudText = false;
}
});
p.touchable(Touchable.disabled);
});
//TODO DEBUG: rate table
parent.fill(t -> {
t.bottom().left();
t.table(Styles.black6, c -> {
Bits used = new Bits(content.items().size);
Runnable rebuild = () -> {
c.clearChildren();
for(Item item : content.items()){
if(state.secinfo.getExport(item) >= 1){
c.image(item.icon(Cicon.small));
c.label(() -> (int)state.secinfo.getExport(item) + " /s").color(Color.lightGray);
c.row();
}
}
};
c.update(() -> {
boolean wrong = false;
for(Item item : content.items()){
boolean has = state.secinfo.getExport(item) >= 1;
if(used.get(item.id) != has){
used.set(item.id, has);
wrong = true;
}
}
if(wrong){
rebuild.run();
}
});
}).visible(() -> state.isCampaign() && content.items().contains(i -> state.secinfo.getExport(i) > 0));
});
//TODO move, select loadout, consume resources
parent.fill(t -> {
t.bottom().visible(() -> state.isCampaign() && player.team().core() != null);
t.button("test launch", Icon.warning, () -> ui.planet.show(state.getSector(), player.team().core()))
.width(150f).disabled(b -> player.team().core() == null || !player.team().core().items.has(player.team().core().block.requirements)); //disable core when missing resources for launch
});
blockfrag.build(parent);
}
@Remote(targets = Loc.both, forward = true, called = Loc.both)
public static void setPlayerTeamEditor(Player player, Team team){
if(state.isEditor() && player != null){
player.team(team);
}
}
public void setHudText(String text){
showHudText = true;
hudText = text;
}
public void toggleHudText(boolean shown){
showHudText = shown;
}
private void scheduleToast(Runnable run){
long duration = (int)(3.5 * 1000);
long since = Time.timeSinceMillis(lastToast);
if(since > duration){
lastToast = Time.millis();
run.run();
}else{
Time.runTask((duration - since) / 1000f * 60f, run);
lastToast += duration;
}
}
public void showToast(String text){
if(state.isMenu()) return;
scheduleToast(() -> {
Sounds.message.play();
Table table = new Table(Tex.button);
table.update(() -> {
if(state.isMenu()){
table.remove();
}
});
table.margin(12);
table.image(Icon.ok).pad(3);
table.add(text).wrap().width(280f).get().setAlignment(Align.center, Align.center);
table.pack();
//create container table which will align and move
Table container = Core.scene.table();
container.top().add(table);
container.setTranslation(0, table.getPrefHeight());
container.actions(Actions.translateBy(0, -table.getPrefHeight(), 1f, Interp.fade), Actions.delay(2.5f),
//nesting actions() calls is necessary so the right prefHeight() is used
Actions.run(() -> container.actions(Actions.translateBy(0, table.getPrefHeight(), 1f, Interp.fade), Actions.remove())));
});
}
public boolean shown(){
return shown;
}
/** Show unlock notification for a new recipe. */
public void showUnlock(UnlockableContent content){
//some content may not have icons... yet
//also don't play in the tutorial to prevent confusion
if(state.isMenu() || state.rules.tutorial) return;
Sounds.message.play();
//if there's currently no unlock notification...
if(lastUnlockTable == null){
scheduleToast(() -> {
Table table = new Table(Tex.button);
table.update(() -> {
if(state.isMenu()){
table.remove();
lastUnlockLayout = null;
lastUnlockTable = null;
}
});
table.margin(12);
Table in = new Table();
//create texture stack for displaying
Image image = new Image(content.icon(Cicon.xlarge));
image.setScaling(Scaling.fit);
in.add(image).size(8 * 6).pad(2);
//add to table
table.add(in).padRight(8);
table.add("$unlocked");
table.pack();
//create container table which will align and move
Table container = Core.scene.table();
container.top().add(table);
container.setTranslation(0, table.getPrefHeight());
container.actions(Actions.translateBy(0, -table.getPrefHeight(), 1f, Interp.fade), Actions.delay(2.5f),
//nesting actions() calls is necessary so the right prefHeight() is used
Actions.run(() -> container.actions(Actions.translateBy(0, table.getPrefHeight(), 1f, Interp.fade), Actions.run(() -> {
lastUnlockTable = null;
lastUnlockLayout = null;
}), Actions.remove())));
lastUnlockTable = container;
lastUnlockLayout = in;
});
}else{
//max column size
int col = 3;
//max amount of elements minus extra 'plus'
int cap = col * col - 1;
//get old elements
Seq<Element> elements = new Seq<>(lastUnlockLayout.getChildren());
int esize = elements.size;
//...if it's already reached the cap, ignore everything
if(esize > cap) return;
//get size of each element
float size = 48f / Math.min(elements.size + 1, col);
lastUnlockLayout.clearChildren();
lastUnlockLayout.defaults().size(size).pad(2);
for(int i = 0; i < esize; i++){
lastUnlockLayout.add(elements.get(i));
if(i % col == col - 1){
lastUnlockLayout.row();
}
}
//if there's space, add it
if(esize < cap){
Image image = new Image(content.icon(Cicon.medium));
image.setScaling(Scaling.fit);
lastUnlockLayout.add(image);
}else{ //else, add a specific icon to denote no more space
lastUnlockLayout.image(Icon.add);
}
lastUnlockLayout.pack();
}
}
public void showLaunchDirect(){
Image image = new Image();
image.getColor().a = 0f;
image.setFillParent(true);
image.actions(Actions.fadeIn(launchDuration / 60f, Interp.pow2In), Actions.delay(8f / 60f), Actions.remove());
Core.scene.add(image);
}
public void showLaunch(){
Image image = new Image();
image.getColor().a = 0f;
image.setFillParent(true);
image.actions(Actions.fadeIn(40f / 60f));
image.update(() -> {
if(state.isMenu()){
image.remove();
}
});
Core.scene.add(image);
}
public void showLand(){
Image image = new Image();
image.getColor().a = 1f;
image.touchable(Touchable.disabled);
image.setFillParent(true);
image.actions(Actions.fadeOut(0.8f), Actions.remove());
image.update(() -> {
image.toFront();
if(state.isMenu()){
image.remove();
}
});
Core.scene.add(image);
}
private void showLaunchConfirm(){
BaseDialog dialog = new BaseDialog("$launch");
dialog.update(() -> {
if(!inLaunchWave()){
dialog.hide();
}
});
dialog.cont.add("$launch.confirm").width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.button("$cancel", dialog::hide);
dialog.buttons.button("$ok", () -> {
dialog.hide();
Call.launchZone();
});
dialog.keyDown(KeyCode.escape, dialog::hide);
dialog.keyDown(KeyCode.back, dialog::hide);
dialog.show();
}
private boolean inLaunchWave(){
return state.hasSector() &&
state.getSector().metCondition() &&
!net.client() &&
state.wave % state.getSector().launchPeriod == 0 && !spawner.isSpawning();
}
private boolean canLaunch(){
return inLaunchWave() && state.enemies <= 0;
}
private void toggleMenus(){
if(flip != null){
flip.getStyle().imageUp = shown ? Icon.downOpen : Icon.upOpen;
}
shown = !shown;
}
private void addWaveTable(Button table){
StringBuilder ibuild = new StringBuilder();
IntFormat wavef = new IntFormat("wave");
IntFormat enemyf = new IntFormat("wave.enemy");
IntFormat enemiesf = new IntFormat("wave.enemies");
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
ibuild.setLength(0);
int m = i/60;
int s = i % 60;
if(m > 0){
ibuild.append(m);
ibuild.append(":");
if(s < 10){
ibuild.append("0");
}
}
ibuild.append(s);
return ibuild.toString();
});
table.clearChildren();
table.touchable(Touchable.enabled);
StringBuilder builder = new StringBuilder();
table.setName("waves");
table.labelWrap(() -> {
builder.setLength(0);
builder.append(wavef.get(state.wave));
builder.append("\n");
if(inLaunchWave()){
builder.append("[#");
Tmp.c1.set(Color.white).lerp(state.enemies > 0 ? Color.white : Color.scarlet, Mathf.absin(Time.time(), 2f, 1f)).toString(builder);
builder.append("]");
if(!canLaunch()){
builder.append(Core.bundle.get("launch.unable2"));
}else{
builder.append(Core.bundle.get("launch"));
builder.append("\n");
builder.append(Core.bundle.format("launch.next", state.wave + state.getSector().launchPeriod));
builder.append("\n");
}
builder.append("[]\n");
}
if(state.enemies > 0){
if(state.enemies == 1){
builder.append(enemyf.get(state.enemies));
}else{
builder.append(enemiesf.get(state.enemies));
}
builder.append("\n");
}
if(state.rules.waveTimer){
builder.append((logic.isWaitingWave() ? Core.bundle.get("wave.waveInProgress") : ( waitingf.get((int)(state.wavetime/60)))));
}else if(state.enemies == 0){
builder.append(Core.bundle.get("waiting"));
}
return builder;
}).growX().pad(8f);
table.setDisabled(() -> !canLaunch());
table.visible(() -> state.rules.waves);
table.clicked(() -> {
if(canLaunch()){
showLaunchConfirm();
}
});
}
private boolean canSkipWave(){
return state.rules.waves && ((net.server() || player.admin) || !net.active()) && state.enemies == 0 && !spawner.isSpawning() && !state.rules.tutorial;
}
private void addPlayButton(Table table){
table.right().button(Icon.play, Styles.righti, 30f, () -> {
if(net.client() && player.admin){
Call.onAdminRequest(player, AdminAction.wave);
}else if(inLaunchWave()){
ui.showConfirm("$confirm", "$launch.skip.confirm", () -> !canSkipWave(), () -> state.wavetime = 0f);
}else{
state.wavetime = 0f;
}
}).growY().fillX().right().width(40f)
.visible(this::canSkipWave);
}
}