New hint tutorial system (unfinished!)
This commit is contained in:
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -2059,6 +2059,7 @@ public class Blocks implements ContentList{
|
||||
size = 7;
|
||||
hasPower = true;
|
||||
consumes.power(10f);
|
||||
buildCostMultiplier = 0.5f;
|
||||
}};
|
||||
|
||||
//endregion campaign
|
||||
|
||||
@@ -1250,6 +1250,7 @@ public class UnitTypes implements ContentList{
|
||||
defaultController = RepairAI::new;
|
||||
|
||||
mineTier = 3;
|
||||
mineSpeed = 4f;
|
||||
health = 500;
|
||||
armor = 5f;
|
||||
speed = 2.5f;
|
||||
|
||||
@@ -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(() -> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
259
core/src/mindustry/ui/fragments/HintsFragment.java
Normal file
259
core/src/mindustry/ui/fragments/HintsFragment.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user