New hint tutorial system (unfinished!)

This commit is contained in:
Anuken
2020-11-21 14:05:58 -05:00
parent 522a7f4728
commit 6fba84959c
23 changed files with 343 additions and 364 deletions

View File

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

View File

@@ -26,7 +26,7 @@ public class Styles{
//TODO all these names are inconsistent and not descriptive
public static Drawable black, black9, black8, black6, black3, black5, none, flatDown, flatOver;
public static ButtonStyle defaultb, waveb;
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, nonet, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, logici, geni, colori, accenti, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali;
public static ScrollPaneStyle defaultPane, horizontalPane, smallPane;
public static KeybindDialogStyle defaultKeybindDialog;
@@ -86,6 +86,13 @@ public class Styles{
up = buttonOver;
over = buttonDown;
}};
nonet = new TextButtonStyle(){{
font = Fonts.outline;
fontColor = Color.lightGray;
overFontColor = Pal.accent;
disabledFontColor = Color.gray;
up = none;
}};
cleart = new TextButtonStyle(){{
over = flatOver;
font = Fonts.def;

View File

@@ -116,7 +116,7 @@ public class LaunchLoadoutDialog extends BaseDialog{
selected = s;
update.run();
rebuildItems.run();
}).group(group).pad(4).disabled(!sitems.has(s.requirements())).checked(s == selected).size(200f);
}).group(group).pad(4).checked(s == selected).size(200f);
if(++i % cols == 0){
t.row();

View File

@@ -134,6 +134,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
/** show with no limitations, just as a map. */
@Override
public Dialog show(){
if(net.client()){
ui.showInfo("@map.multiplayer");
return this;
}
mode = look;
selected = hovered = launchSector = null;
launching = false;

View File

@@ -169,7 +169,7 @@ public class ResearchDialog extends BaseDialog{
public Dialog show(){
if(net.client()){
ui.showInfo("@research.multiplayer");
return null;
return this;
}
return super.show();

View File

@@ -0,0 +1,259 @@
package mindustry.ui.fragments;
import arc.*;
import arc.func.*;
import arc.input.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.input.*;
import mindustry.ui.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class HintsFragment extends Fragment{
private static final Boolp isTutorial = () -> Vars.state.rules.sector == SectorPresets.groundZero.sector;
private static final float foutTime = 0.6f;
/** All hints to be displayed in the game. */
public Seq<Hint> incomplete = Seq.with(DefaultHint.values());
@Nullable
public Hint current;
Group group = new WidgetGroup();
ObjectSet<String> events = new ObjectSet<>();
ObjectSet<Block> placedBlocks = new ObjectSet<>();
int checkIdx = 0;
Table last;
@Override
public void build(Group parent){
group.setFillParent(true);
group.touchable = Touchable.childrenOnly;
group.visibility = () -> Core.settings.getBool("hints", true);
group.update(() -> {
if(current != null){
//current got completed
if(current.complete()){
complete();
}else if(!current.show()){ //current became hidden
hide();
}
}else if(incomplete.size > 0){
//check one hint each frame to see if it should be shown.
checkIdx = (checkIdx + 1) % incomplete.size;
Hint hint = incomplete.get(checkIdx);
if(hint.show() && !hint.finished() & !hint.complete()){
display(hint);
}
}
});
parent.addChild(group);
checkNext();
Events.on(BlockBuildEndEvent.class, event -> {
if(!event.breaking && event.tile.team() == Vars.state.rules.defaultTeam){
placedBlocks.add(event.tile.block());
}
if(event.breaking){
events.add("break");
}
});
Events.on(UnitControlEvent.class, e -> {
if(e.player == player){
events.add("unitcontrol");
}
});
Events.on(WorldLoadEvent.class, e -> Core.app.post(() -> {
checkNext();
}));
}
void checkNext(){
if(current != null) return;
incomplete.removeAll(h -> h == current || !h.valid() || h.finished() || (h.show() && h.complete()));
incomplete.sort(Hint::order);
Hint first = incomplete.find(Hint::show);
if(first != null){
incomplete.remove(first);
display(first);
}
}
void display(Hint hint){
if(current != null) return;
group.fill(t -> {
last = t;
t.left();
t.table(Styles.black5, cont -> {
cont.actions(Actions.alpha(0f), Actions.alpha(1f, 1f, Interp.smooth));
cont.margin(6f).add(hint.text()).width(Vars.mobile ? 300f : 400f).left().labelAlign(Align.left).wrap();
});
t.row();
t.button("@hint.skip", Styles.nonet, () -> {
if(current != null){
complete();
}
}).size(100f, 40f).left();
});
this.current = hint;
}
/** Completes and hides the current hint. */
void complete(){
if(current == null) return;
current.finish();
incomplete.remove(current);
hide();
}
/** Hides the current hint, but does not complete it. */
void hide(){
//hide previous child if found
if(last != null){
last.actions(Actions.parallel(Actions.alpha(0f, foutTime, Interp.smooth), Actions.translateBy(0f, Scl.scl(-200f), foutTime, Interp.smooth)), Actions.remove());
}
//check for next hint to display immediately
current = null;
last = null;
checkNext();
}
public boolean shown(){
return current != null;
}
public enum DefaultHint implements Hint{
desktopMove(visibleDesktop, () -> Core.input.axis(Binding.move_x) != 0 || Core.input.axis(Binding.move_y) != 0),
zoom(visibleDesktop, () -> Core.input.axis(KeyCode.scroll) != 0),
mine(isTutorial, () -> player.unit().mining()),
placeDrill(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.mechanicalDrill)),
placeConveyor(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.conveyor)),
placeTurret(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.duo)),
breaking(isTutorial, () -> ui.hints.events.contains("break")),
desktopShoot(visibleDesktop, () -> Vars.state.enemies > 0, () -> player.shooting),
depositItems(() -> player.unit().hasItem(), () -> !player.unit().hasItem()),
desktopPause(visibleDesktop, () -> isTutorial.get() && !Vars.net.active(), () -> Core.input.keyTap(Binding.pause)),
research(isTutorial, () -> ui.research.isShown()),
unitControl(() -> state.rules.defaultTeam.data().units.size > 1 && !net.active(), () -> !player.dead() && !player.unit().spawnedByCore),
respawn(visibleMobile, () -> !player.dead() && !player.unit().spawnedByCore, () -> !player.dead() && player.unit().spawnedByCore),
launch(() -> isTutorial.get() && state.rules.sector.isCaptured(), () -> ui.planet.isShown()),
;
@Nullable
String text;
int visibility = visibleAll;
Hint[] dependencies = {};
boolean finished, cached;
Boolp complete, shown = () -> true;
DefaultHint(Boolp complete){
this.complete = complete;
}
DefaultHint(int visiblity, Boolp complete){
this(complete);
this.visibility = visiblity;
}
DefaultHint(Boolp shown, Boolp complete){
this(complete);
this.shown = shown;
}
DefaultHint(int visiblity, Boolp shown, Boolp complete){
this(complete);
this.shown = shown;
this.visibility = visiblity;
}
@Override
public boolean finished(){
if(!cached){
cached = true;
finished = Core.settings.getBool(name() + "-hint-done", false);
}
return finished;
}
@Override
public void finish(){
Core.settings.put(name() + "-hint-done", finished = true);
}
@Override
public String text(){
if(text == null){
text = Vars.mobile && Core.bundle.has("hint." + name() + ".mobile") ? Core.bundle.get("hint." + name() + ".mobile") : Core.bundle.get("hint." + name());
if(!Vars.mobile) text = text.replace("tap", "click").replace("Tap", "Click");
}
return text;
}
@Override
public boolean complete(){
return complete.get();
}
@Override
public boolean show(){
return shown.get() && (dependencies.length == 0 || !Structs.contains(dependencies, d -> !d.finished()));
}
@Override
public int order(){
return ordinal();
}
@Override
public boolean valid(){
return (Vars.mobile && (visibility & visibleMobile) != 0) || (!Vars.mobile && (visibility & visibleDesktop) != 0);
}
}
/** Hint interface for defining any sort of message appearing at the left. */
public interface Hint{
int visibleDesktop = 1, visibleMobile = 2, visibleAll = visibleDesktop | visibleMobile;
/** Hint name for preference storage. */
String name();
/** Displayed text. */
String text();
/** @return true if hint objective is complete */
boolean complete();
/** @return whether the hint is ready to be shown */
boolean show();
/** @return order integer, determines priority */
int order();
/** @return whether this hint should be processed, used for platform splits */
boolean valid();
/** finishes the hint - it should not be shown again */
default void finish(){
Core.settings.put(name() + "-hint-done", true);
}
/** @return whether the hint is finished - if true, it should not be shown again */
default boolean finished(){
return Core.settings.getBool(name() + "-hint-done", false);
}
}
}

View File

@@ -108,7 +108,8 @@ public class HudFragment extends Fragment{
t.top().right();
});
//TODO tear this all down
ui.hints.build(parent);
//menu at top left
parent.fill(cont -> {
cont.name = "overlaymarker";
@@ -319,29 +320,6 @@ public class HudFragment extends Fragment{
.update(label -> label.color.set(Color.orange).lerp(Color.scarlet, Mathf.absin(Time.time(), 2f, 1f)))).touchable(Touchable.disabled);
});
//TODO tutorial text
parent.fill(t -> {
t.name = "tutorial";
Runnable resize = () -> {
t.clearChildren();
t.top().right().visible(() -> false);
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());
});
//'saving' indicator
parent.fill(t -> {
t.name = "saving";