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

@@ -88,7 +88,7 @@ public class Vars implements Loadable{
/** duration of time between turns in ticks */
public static final float turnDuration = 2 * Time.toMinutes;
/** chance of an invasion per turn, 1 = 100% */
public static final float baseInvasionChance = 1f / 30f;
public static final float baseInvasionChance = 1f / 45f;
/** how many turns have to pass before invasions start */
public static final int invasionGracePeriod = 20;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */

View File

@@ -19,10 +19,6 @@ public class BuilderAI extends AIController{
@Override
public void updateMovement(){
if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
if(target != null && shouldShoot()){
unit.lookAt(target);
}

View File

@@ -2059,6 +2059,7 @@ public class Blocks implements ContentList{
size = 7;
hasPower = true;
consumes.power(10f);
buildCostMultiplier = 0.5f;
}};
//endregion campaign

View File

@@ -1250,6 +1250,7 @@ public class UnitTypes implements ContentList{
defaultController = RepairAI::new;
mineTier = 3;
mineSpeed = 4f;
health = 500;
armor = 5f;
speed = 2.5f;

View File

@@ -44,7 +44,6 @@ import static mindustry.Vars.*;
public class Control implements ApplicationListener, Loadable{
public Saves saves;
public SoundControl sound;
public Tutorial tutorial;
public InputHandler input;
private Interval timer = new Interval(2);
@@ -53,7 +52,6 @@ public class Control implements ApplicationListener, Loadable{
public Control(){
saves = new Saves();
tutorial = new Tutorial();
sound = new SoundControl();
Events.on(StateChangeEvent.class, event -> {
@@ -87,7 +85,6 @@ public class Control implements ApplicationListener, Loadable{
Events.on(ResetEvent.class, event -> {
player.reset();
tutorial.reset();
hiscore = false;
saves.resetSave();
@@ -408,13 +405,6 @@ public class Control implements ApplicationListener, Loadable{
public void init(){
platform.updateRPC();
//just a regular reminder
if(!OS.prop("user.name").equals("anuke") && !OS.hasEnv("iknowwhatimdoing")){
app.post(() -> app.post(() -> {
ui.showStartupInfo("@indev.popup");
}));
}
//display UI scale changed dialog
if(Core.settings.getBool("uiscalechanged", false)){
Core.app.post(() -> Core.app.post(() -> {

View File

@@ -264,7 +264,7 @@ public class Logic implements ApplicationListener{
Events.fire(new SectorCaptureEvent(state.rules.sector));
//save, just in case
if(!headless){
if(!headless && !net.client()){
control.saves.saveSector(state.rules.sector);
}
}

View File

@@ -41,7 +41,7 @@ public class NetServer implements ApplicationListener{
private static final Vec2 vector = new Vec2();
private static final Rect viewport = new Rect();
/** If a player goes away of their server-side coordinates by this distance, they get teleported back. */
private static final float correctDist = 16f;
private static final float correctDist = tilesize * 8f;
public final Administration admins = new Administration();
public final CommandHandler clientCommands = new CommandHandler("/");
@@ -638,12 +638,12 @@ public class NetServer implements ApplicationListener{
Unit unit = player.unit();
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
float maxSpeed = ((player.unit().type.canBoost && player.unit().isFlying()) ? player.unit().type.boostMultiplier : 1f) * player.unit().speed();
float maxSpeed = unit.realSpeed();
if(unit.isGrounded()){
maxSpeed *= unit.floorSpeedMultiplier();
}
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.2f;
//ignore the position if the player thinks they're dead, or the unit is wrong
boolean ignorePosition = dead || unit.id != unitID;

View File

@@ -42,6 +42,7 @@ public class UI implements ApplicationListener, Loadable{
public MinimapFragment minimapfrag;
public PlayerListFragment listfrag;
public LoadingFragment loadfrag;
public HintsFragment hints;
public WidgetGroup menuGroup, hudGroup;
@@ -150,6 +151,7 @@ public class UI implements ApplicationListener, Loadable{
menufrag = new MenuFragment();
hudfrag = new HudFragment();
hints = new HintsFragment();
chatfrag = new ChatFragment();
minimapfrag = new MinimapFragment();
listfrag = new PlayerListFragment();
@@ -520,7 +522,7 @@ public class UI implements ApplicationListener, Loadable{
//TODO move?
public static String formatAmount(long number){
public static String formatAmount(int number){
if(number >= 1_000_000_000){
return Strings.fixed(number / 1_000_000_000f, 1) + "[gray]" + Core.bundle.get("unit.billions") + "[]";
}else if(number >= 1_000_000){

View File

@@ -519,7 +519,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void dumpLiquid(Liquid liquid){
int dump = this.cdump;
if(!net.client() && state.isCampaign()) liquid.unlock();
if(!net.client() && state.isCampaign() && team == state.rules.defaultTeam) liquid.unlock();
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
@@ -620,7 +620,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
*/
public void offload(Item item){
int dump = this.cdump;
if(!net.client() && state.isCampaign()) item.unlock();
if(!net.client() && state.isCampaign() && team == state.rules.defaultTeam) item.unlock();
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);

View File

@@ -1,308 +0,0 @@
package mindustry.game;
import arc.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.*;
/** Handles tutorial state. */
public class Tutorial{
private static final int mineCopper = 18;
private static final int blocksToBreak = 3, blockOffset = -6;
ObjectSet<String> events = new ObjectSet<>();
ObjectIntMap<Block> blocksPlaced = new ObjectIntMap<>();
int sentence;
public TutorialStage stage = TutorialStage.values()[0];
public Tutorial(){
Events.on(BlockBuildEndEvent.class, event -> {
if(!event.breaking){
blocksPlaced.increment(event.tile.block(), 1);
}
});
Events.on(LineConfirmEvent.class, event -> events.add("lineconfirm"));
Events.on(TurretAmmoDeliverEvent.class, event -> events.add("ammo"));
Events.on(CoreItemDeliverEvent.class, event -> events.add("coreitem"));
Events.on(BlockInfoEvent.class, event -> events.add("blockinfo"));
Events.on(DepositEvent.class, event -> events.add("deposit"));
Events.on(WithdrawEvent.class, event -> events.add("withdraw"));
Events.on(ClientLoadEvent.class, e -> {
for(TutorialStage stage : TutorialStage.values()){
stage.load();
}
});
}
/** update tutorial state, transition if needed */
public void update(){
if(stage.done.get() && !canNext()){
next();
}else{
stage.update();
}
}
/** draw UI overlay */
public void draw(){
if(!Core.scene.hasDialog()){
stage.draw();
}
}
/** Resets tutorial state. */
public void reset(){
stage = TutorialStage.values()[0];
stage.begin();
blocksPlaced.clear();
events.clear();
sentence = 0;
}
/** Goes on to the next tutorial step. */
public void next(){
stage = TutorialStage.values()[Mathf.clamp(stage.ordinal() + 1, 0, TutorialStage.values().length)];
stage.begin();
blocksPlaced.clear();
events.clear();
sentence = 0;
}
public boolean canNext(){
return sentence + 1 < stage.sentences.size;
}
public void nextSentence(){
if(canNext()){
sentence ++;
}
}
public boolean canPrev(){
return sentence > 0;
}
public void prevSentence(){
if(canPrev()){
sentence --;
}
}
public enum TutorialStage{
intro(
line -> Core.bundle.format(line, item(Items.copper), mineCopper),
() -> item(Items.copper) >= mineCopper
),
drill(() -> placed(Blocks.mechanicalDrill, 1)){
void draw(){
outline("category-production");
outline("block-mechanical-drill");
outline("confirmplace");
}
},
blockinfo(() -> event("blockinfo")){
void draw(){
outline("category-production");
outline("block-mechanical-drill");
outline("blockinfo");
}
},
conveyor(() -> placed(Blocks.conveyor, 2) && event("lineconfirm") && event("coreitem")){
void draw(){
outline("category-distribution");
outline("block-conveyor");
}
},
turret(() -> placed(Blocks.duo, 1)){
void draw(){
outline("category-turret");
outline("block-duo");
}
},
drillturret(() -> event("ammo")),
pause(() -> state.isPaused()){
void draw(){
if(mobile){
outline("pause");
}
}
},
unpause(() -> !state.isPaused()){
void draw(){
if(mobile){
outline("pause");
}
}
},
breaking(TutorialStage::blocksBroken){
void begin(){
placeBlocks();
}
void draw(){
if(mobile){
outline("breakmode");
}
}
},
withdraw(() -> event("withdraw")){
void begin(){
state.teams.playerCores().first().items.add(Items.copper, 10);
}
},
deposit(() -> event("deposit")),
waves(() -> state.wave > 2 && state.enemies <= 0 && !spawner.isSpawning()){
void begin(){
state.rules.waveTimer = true;
logic.runWave();
}
void update(){
if(state.wave > 2){
state.rules.waveTimer = false;
}
}
},
launch(() -> false){
void begin(){
state.rules.waveTimer = false;
state.wave = 5;
//end tutorial, never show it again
Events.fire(Trigger.tutorialComplete);
Core.settings.put("playedtutorial", true);
}
void draw(){
outline("waves");
}
},;
protected String line = "";
protected final Func<String, String> text;
protected Seq<String> sentences;
protected final Boolp done;
TutorialStage(Func<String, String> text, Boolp done){
this.text = text;
this.done = done;
}
TutorialStage(Boolp done){
this(line -> line, done);
}
/** displayed tutorial stage text.*/
public String text(){
if(sentences == null){
load();
}
String line = sentences.get(control.tutorial.sentence);
return line.contains("{") ? text.get(line) : line;
}
void load(){
this.line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name();
this.sentences = Seq.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
}
/** called every frame when this stage is active.*/
void update(){
}
/** called when a stage begins.*/
void begin(){
}
/** called when a stage needs to draw itself, usually over highlighted UI elements. */
void draw(){
}
//utility
static void placeBlocks(){
Building core = state.teams.playerCores().first();
for(int i = 0; i < blocksToBreak; i++){
world.tile(core.tile().x + blockOffset, core.tile().y + i).remove();
world.tile(core.tile().x + blockOffset, core.tile().y + i).setBlock(Blocks.scrapWall, state.rules.defaultTeam);
}
}
static boolean blocksBroken(){
Building core = state.teams.playerCores().first();
for(int i = 0; i < blocksToBreak; i++){
if(world.tile(core.tile.x + blockOffset, core.tile.y + i).block() == Blocks.scrapWall){
return false;
}
}
return true;
}
static boolean event(String name){
return control.tutorial.events.contains(name);
}
static boolean placed(Block block, int amount){
return placed(block) >= amount;
}
static int placed(Block block){
return control.tutorial.blocksPlaced.get(block, 0);
}
static int item(Item item){
return state.rules.defaultTeam.data().noCores() ? 0 : state.rules.defaultTeam.core().items.get(item);
}
static boolean toggled(String name){
Element element = Core.scene.findVisible(name);
if(element instanceof Button){
return ((Button)element).isChecked();
}
return false;
}
static void outline(String name){
Element element = Core.scene.findVisible(name);
if(element != null && !toggled(name)){
element.localToStageCoordinates(Tmp.v1.setZero());
float sin = Mathf.sin(11f, Scl.scl(4f));
Lines.stroke(Scl.scl(7f), Pal.place);
Lines.rect(Tmp.v1.x - sin, Tmp.v1.y - sin, element.getWidth() + sin*2, element.getHeight() + sin*2);
float size = Math.max(element.getWidth(), element.getHeight()) + Mathf.absin(11f/2f, Scl.scl(18f));
float angle = Angles.angle(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f, Tmp.v1.x + element.getWidth()/2f, Tmp.v1.y + element.getHeight()/2f);
Tmp.v2.trns(angle + 180f, size*1.4f);
float fs = Scl.scl(40f);
float fs2 = Scl.scl(56f);
Draw.color(Pal.gray);
Drawf.tri(Tmp.v1.x + element.getWidth()/2f + Tmp.v2.x, Tmp.v1.y + element.getHeight()/2f + Tmp.v2.y, fs2, fs2, angle);
Draw.color(Pal.place);
Tmp.v2.setLength(Tmp.v2.len() - Scl.scl(4));
Drawf.tri(Tmp.v1.x + element.getWidth()/2f + Tmp.v2.x, Tmp.v1.y + element.getHeight()/2f + Tmp.v2.y, fs, fs, angle);
Draw.reset();
}
}
}
}

View File

@@ -36,7 +36,7 @@ public class Universe{
//update base coverage on capture
Events.on(SectorCaptureEvent.class, e -> {
if(state.isCampaign()){
if(!net.client() && state.isCampaign()){
state.getSector().planet.updateBaseCoverage();
}
});

View File

@@ -47,6 +47,7 @@ public class DesktopInput extends InputHandler{
@Override
public void buildUI(Group group){
//respawn hints
group.fill(t -> {
t.visible(() -> Core.settings.getBool("hints") && ui.hudfrag.shown && !player.dead() && !player.unit().spawnedByCore() && !(Core.settings.getBool("hints") && lastSchematic != null && !selectRequests.isEmpty()));
t.bottom();
@@ -56,6 +57,7 @@ public class DesktopInput extends InputHandler{
}).margin(6f);
});
//building hints
group.fill(t -> {
t.bottom();
t.visible(() -> {
@@ -74,6 +76,7 @@ public class DesktopInput extends InputHandler{
}).margin(10f);
});
//schematic controls
group.fill(t -> {
t.visible(() -> ui.hudfrag.shown && lastSchematic != null && !selectRequests.isEmpty());
t.bottom();

View File

@@ -37,6 +37,7 @@ import mindustry.world.draw.*;
import mindustry.world.meta.*;
import java.lang.reflect.*;
import java.util.*;
@SuppressWarnings("unchecked")
public class ContentParser{
@@ -267,7 +268,13 @@ public class ContentParser{
UnitType unit;
if(locate(ContentType.unit, name) == null){
unit = new UnitType(mod + "-" + name);
unit.constructor = Reflect.cons(resolve(Strings.capitalize(getType(value)), "mindustry.gen"));
var typeVal = value.get("type");
if(typeVal != null && !typeVal.isString()){
throw new RuntimeException("Unit '" + name + "' has an incorrect type. Types must be strings.");
}
unit.constructor = unitType(typeVal);
}else{
unit = locate(ContentType.unit, name);
}
@@ -337,6 +344,18 @@ public class ContentParser{
//ContentType.sector, parser(ContentType.sector, SectorPreset::new)
);
private Prov<Unit> unitType(JsonValue value){
if(value == null) return UnitEntity::create;
return switch(value.asString()){
case "flying" -> UnitEntity::create;
case "mech" -> MechUnit::create;
case "legs" -> LegsUnit::create;
case "naval" -> UnitWaterMove::create;
case "payload" -> PayloadUnit::create;
default -> throw new RuntimeException("Invalid unit type: '" + value + "'. Must be 'flying/mech/legs/naval/payload'.");
};
}
private String getString(JsonValue value, String key){
if(value.has(key)){
return value.getString(key);

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";