Dynamic layout for unlocking blocks

This commit is contained in:
Anuken
2018-05-30 22:41:52 -04:00
parent e9e7d2e289
commit fae7bfa8c5
6 changed files with 306 additions and 171 deletions

View File

@@ -9,6 +9,7 @@ import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.mindustry.content.Mechs;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.game.ContentDatabase;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.input.AndroidInput;
@@ -19,6 +20,7 @@ import io.anuke.mindustry.io.Map;
import io.anuke.mindustry.io.Saves;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.Recipe;
import io.anuke.ucore.core.*;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.input.InputProxy;
@@ -271,6 +273,22 @@ public class Control extends Module{
return hiscore;
}
private void checkUnlockableBlocks(){
TileEntity entity = players[0].getClosestCore();
if(entity == null) return;
for(int i = 0 ; i < Recipe.all().size; i ++){
Recipe recipe = Recipe.all().get(i);
if(entity.items.hasItems(recipe.requirements)){
if(control.database().unlockContent(recipe)){
ui.hudfrag.showUnlock(recipe);
}
}
}
}
@Override
public void dispose(){
Platform.instance.onGameExit();
@@ -322,6 +340,10 @@ public class Control extends Module{
input.update();
}
if(Timers.get("timerCheckUnlock", 60)){
checkUnlockableBlocks();
}
if(Inputs.keyTap("pause") && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
state.set(state.is(State.playing) ? State.paused : State.playing);
}

View File

@@ -6,17 +6,11 @@ import com.badlogic.gdx.utils.ObjectMap.Entry;
import com.badlogic.gdx.utils.ObjectSet;
import io.anuke.ucore.core.Settings;
import static io.anuke.mindustry.Vars.debug;
public class ContentDatabase {
private ObjectMap<String, ObjectSet<String>> unlocked = new ObjectMap<>();
/**Returns whether or not this piece of content is unlocked yet.*/
public boolean isUnlocked(Content content){
if(debug){
return true;
}
if(!unlocked.containsKey(content.getContentTypeName())){
unlocked.put(content.getContentTypeName(), new ObjectSet<>());
}
@@ -28,13 +22,14 @@ public class ContentDatabase {
/**Makes this piece of content 'unlocked'.
* If this piece of content is already unlocked, nothing changes.
* Results are not saved until you call {@link #save()}.*/
public void unlockContent(Content content){
* Results are not saved until you call {@link #save()}.
* @return whether or not this content was newly unlocked.*/
public boolean unlockContent(Content content){
if(!unlocked.containsKey(content.getContentTypeName())){
unlocked.put(content.getContentTypeName(), new ObjectSet<>());
}
unlocked.get(content.getContentTypeName()).add(content.getContentName());
return unlocked.get(content.getContentTypeName()).add(content.getContentName());
}
private void load(){

View File

@@ -229,4 +229,5 @@ public abstract class InputHandler extends InputAdapter{
Tile tile = world.tile(x, y).target();
player.addBuildRequest(new BuildRequest(tile.x, tile.y));
}
}

View File

@@ -5,7 +5,6 @@ import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.input.InputHandler;
import io.anuke.mindustry.type.Category;
import io.anuke.mindustry.type.ItemStack;
@@ -28,13 +27,19 @@ import static io.anuke.mindustry.Vars.*;
public class BlocksFragment implements Fragment{
/**Table containing description that is shown on top.*/
private Table desctable;
private Table descTable;
/**Main table containing the whole menu.*/
private Table blocks;
private Table mainTable;
/**Table for all section buttons and blocks.*/
private Table selectTable;
/**Whether the whole thing is shown or hidden by the popup button.*/
private boolean shown = true;
/**Recipe currently hovering over.*/
private Recipe hoverRecipe;
/**Last category selected.*/
private Category lastCategory;
/**Last block pane scroll Y position.*/
private float lastScroll;
/**Temporary recipe array for storage*/
private Array<Recipe> recipes = new Array<>();
@@ -59,157 +64,37 @@ public class BlocksFragment implements Fragment{
visible(() -> !state.is(State.menu) && shown);
//create the main blocks table
blocks = new table(){{
mainTable = new table(){{
//add top description table
desctable = new Table("button");
desctable.setVisible(() -> hoverRecipe != null || input.recipe != null); //make sure it's visible when necessary
desctable.update(() -> {
descTable = new Table("button");
descTable.setVisible(() -> hoverRecipe != null || input.recipe != null); //make sure it's visible when necessary
descTable.update(() -> {
// note: This is required because there is no direct connection between
// input.recipe and the description ui. If input.recipe gets set to null
// a proper cleanup of the ui elements is required.
boolean anyRecipeShown = input.recipe != null || hoverRecipe != null;
boolean descriptionTableClean = desctable.getChildren().size == 0;
boolean descriptionTableClean = descTable.getChildren().size == 0;
boolean cleanupRequired = !anyRecipeShown && !descriptionTableClean;
if(cleanupRequired){
desctable.clear();
descTable.clear();
}
});
add(desctable).fillX().uniformX();
add(descTable).fillX().uniformX();
row();
//now add the block selection menu
new table("pane") {{
selectTable = new table("pane") {{
touchable(Touchable.enabled);
get().setRound(true);
Stack stack = new Stack();
ButtonGroup<ImageButton> group = new ButtonGroup<>();
//add categories
for (Category cat : Category.values()) {
//get recipes out by category
Recipe.getUnlockedByCategory(cat, recipes);
//empty section, nothing to see here
if(recipes.size == 0){
continue;
}
//table where actual recipes go
Table recipeTable = new Table();
recipeTable.margin(4).top().left().marginRight(15);
//add category button
ImageButton catb = get().addImageButton( "icon-" + cat.name(), "toggle", 40, () -> {
if (!recipeTable.isVisible() && input.recipe != null) {
input.recipe = null;
}
}).growX().height(54).padTop(cat.ordinal() <= secrows-1 ? -10 : -5).group(group)
.name("sectionbutton" + cat.name()).get();
//scrollpane for recipes
ScrollPane pane = new ScrollPane(recipeTable, "clear-black");
pane.setOverscroll(false, false);
pane.setVisible(catb::isChecked);
pane.update(() -> {
Element e = Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true);
if(e != null && e.isDescendantOf(pane)){
Core.scene.setScrollFocus(pane);
}
});
stack.add(pane);
//add a new row here when needed
if (cat.ordinal() % secrows == secrows-1) {
row();
}
int i = 0;
//add actual recipes
for (Recipe r : recipes) {
ImageButton image = new ImageButton(new TextureRegion(), "select");
TextureRegion[] regions = r.result.getCompactIcon();
Stack istack = new Stack();
for(TextureRegion region : regions){
istack.add(new Image(region));
}
image.getImageCell().setActor(istack).size(size);
image.addChild(istack);
image.setTouchable(Touchable.enabled);
image.getImage().remove();
image.addListener(new ClickListener(){
@Override
public void enter(InputEvent event, float x, float y, int pointer, Element fromActor) {
super.enter(event, x, y, pointer, fromActor);
if (hoverRecipe != r) {
hoverRecipe = r;
updateRecipe(r);
}
}
@Override
public void exit(InputEvent event, float x, float y, int pointer, Element toActor) {
super.exit(event, x, y, pointer, toActor);
hoverRecipe = null;
updateRecipe(input.recipe);
}
});
image.clicked(() -> {
// note: input.recipe only gets set here during a click.
// during a hover only the visual description will be updated.
InputHandler handler = mobile ? input : control.input(0);
boolean nothingSelectedYet = handler.recipe == null;
boolean selectedSomethingElse = !nothingSelectedYet && handler.recipe != r;
boolean shouldMakeSelection = nothingSelectedYet || selectedSomethingElse;
if (shouldMakeSelection) {
handler.recipe = r;
hoverRecipe = r;
updateRecipe(r);
} else {
handler.recipe = null;
hoverRecipe = null;
updateRecipe(null);
}
});
recipeTable.add(image).size(size + 8);
image.update(() -> {
for(Player player : players){
if(control.input(player.playerIndex).recipe == r){
image.setChecked(true);
return;
}
}
image.setChecked(false);
});
if (i % rows == rows - 1) {
recipeTable.row();
}
i++;
}
}
row();
add(stack).colspan(Category.values().length).padBottom(-5).height((size + 12)*maxrow);
margin(10f);
marginLeft(0f);
marginRight(0f);
marginTop(-5);
end();
}}.right().bottom();
}}.right().bottom().end().get();
visible(() -> !state.is(State.menu) && shown);
@@ -217,33 +102,178 @@ public class BlocksFragment implements Fragment{
}}.end();
}
public void toggle(boolean show, float t, Interpolation ip){
void rebuild(){
selectTable.clear();
InputHandler input = control.input(0);
Stack stack = new Stack();
ButtonGroup<ImageButton> group = new ButtonGroup<>();
Table catTable = selectTable;
int cati = 0;
int checkedi = 0;
//add categories
for (Category cat : Category.values()) {
//get recipes out by category
Recipe.getUnlockedByCategory(cat, recipes);
//empty section, nothing to see here
if(recipes.size == 0){
continue;
}
//table where actual recipes go
Table recipeTable = new Table();
recipeTable.margin(4).top().left().marginRight(15);
//add a new row here when needed
if (cati == secrows) {
catTable = new Table();
selectTable.row();
selectTable.add(catTable).colspan(secrows).padTop(-5).growX();
}
//add category button
ImageButton catb = catTable.addImageButton( "icon-" + cat.name(), "toggle", 40, () -> {
if (!recipeTable.isVisible() && input.recipe != null) {
input.recipe = null;
}
lastCategory = cat;
}).growX().height(54).group(group)
.name("sectionbutton" + cat.name()).get();
if(lastCategory == cat || lastCategory == null){
checkedi = cati;
lastCategory = cat;
}
//scrollpane for recipes
ScrollPane pane = new ScrollPane(recipeTable, "clear-black");
pane.setOverscroll(false, false);
pane.setVisible(catb::isChecked);
pane.setScrollYForce(lastScroll);
pane.update(() -> {
Element e = Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true);
if(e != null && e.isDescendantOf(pane)){
Core.scene.setScrollFocus(pane);
}
if(lastCategory == cat){
lastScroll = pane.getVisualScrollY();
}
});
stack.add(pane);
int i = 0;
//add actual recipes
for (Recipe r : recipes) {
ImageButton image = new ImageButton(new TextureRegion(), "select");
TextureRegion[] regions = r.result.getCompactIcon();
Stack istack = new Stack();
for(TextureRegion region : regions){
istack.add(new Image(region));
}
image.getImageCell().setActor(istack).size(size);
image.addChild(istack);
image.setTouchable(Touchable.enabled);
image.getImage().remove();
image.addListener(new ClickListener(){
@Override
public void enter(InputEvent event, float x, float y, int pointer, Element fromActor) {
super.enter(event, x, y, pointer, fromActor);
if (hoverRecipe != r) {
hoverRecipe = r;
updateRecipe(r);
}
}
@Override
public void exit(InputEvent event, float x, float y, int pointer, Element toActor) {
super.exit(event, x, y, pointer, toActor);
hoverRecipe = null;
updateRecipe(input.recipe);
}
});
image.clicked(() -> {
// note: input.recipe only gets set here during a click.
// during a hover only the visual description will be updated.
InputHandler handler = mobile ? input : control.input(0);
boolean nothingSelectedYet = handler.recipe == null;
boolean selectedSomethingElse = !nothingSelectedYet && handler.recipe != r;
boolean shouldMakeSelection = nothingSelectedYet || selectedSomethingElse;
if (shouldMakeSelection) {
handler.recipe = r;
hoverRecipe = r;
updateRecipe(r);
} else {
handler.recipe = null;
hoverRecipe = null;
updateRecipe(null);
}
});
recipeTable.add(image).size(size + 8);
image.update(() -> {
for(Player player : players){
if(control.input(player.playerIndex).recipe == r){
image.setChecked(true);
return;
}
}
image.setChecked(false);
});
if (i % rows == rows - 1) {
recipeTable.row();
}
i++;
}
cati ++;
}
group.getButtons().get(checkedi).setChecked(true);
selectTable.row();
selectTable.add(stack).colspan(Category.values().length).padBottom(-5).height((size + 12)*maxrow);
}
void toggle(boolean show, float t, Interpolation ip){
if(!show){
blocks.actions(Actions.translateBy(0, -blocks.getHeight() - desctable.getHeight(), t, ip), Actions.call(() -> shown = false));
mainTable.actions(Actions.translateBy(0, -mainTable.getHeight() - descTable.getHeight(), t, ip), Actions.call(() -> shown = false));
}else{
shown = true;
blocks.actions(Actions.translateBy(0, -blocks.getTranslation().y, t, ip));
mainTable.actions(Actions.translateBy(0, -mainTable.getTranslation().y, t, ip));
}
}
void updateRecipe(Recipe recipe){
private void updateRecipe(Recipe recipe){
if (recipe == null) {
desctable.clear();
descTable.clear();
return;
}
desctable.clear();
desctable.setTouchable(Touchable.enabled);
descTable.clear();
descTable.setTouchable(Touchable.enabled);
desctable.defaults().left();
desctable.left();
desctable.margin(12);
descTable.defaults().left();
descTable.left();
descTable.margin(12);
Table header = new Table();
desctable.add(header).left();
descTable.add(header).left();
desctable.row();
descTable.row();
TextureRegion[] regions = recipe.result.getCompactIcon();
@@ -256,14 +286,14 @@ public class BlocksFragment implements Fragment{
nameLabel.setWrap(true);
header.add(nameLabel).padLeft(2).width(120f);
desctable.add().pad(2);
descTable.add().pad(2);
Table requirements = new Table();
desctable.row();
descTable.row();
desctable.add(requirements);
desctable.left();
descTable.add(requirements);
descTable.left();
for(ItemStack stack : recipe.requirements){
requirements.addImage(stack.item.region).size(8*3);
@@ -280,18 +310,6 @@ public class BlocksFragment implements Fragment{
requirements.row();
}
desctable.row();
}
private void checkUnlockableBlocks(){
TileEntity entity = players[0].getClosestCore();
if(entity == null) return;
for(Recipe recipe : Recipe.all()){
if(entity.items.hasAtLeastOneOfItems(recipe.requirements)){
control.database().unlockContent(recipe);
}
}
descTable.row();
}
}

View File

@@ -5,12 +5,16 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Scaling;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.Recipe;
import io.anuke.ucore.core.Core;
import io.anuke.ucore.core.Inputs;
import io.anuke.ucore.core.Settings;
import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.scene.Element;
import io.anuke.ucore.scene.Group;
import io.anuke.ucore.scene.actions.Actions;
import io.anuke.ucore.scene.builders.imagebutton;
@@ -21,6 +25,7 @@ import io.anuke.ucore.scene.style.TextureRegionDrawable;
import io.anuke.ucore.scene.ui.Image;
import io.anuke.ucore.scene.ui.ImageButton;
import io.anuke.ucore.scene.ui.Label;
import io.anuke.ucore.scene.ui.layout.Stack;
import io.anuke.ucore.scene.ui.layout.Table;
import io.anuke.ucore.util.Bundles;
@@ -33,6 +38,8 @@ public class HudFragment implements Fragment{
private Table respawntable;
private Table wavetable;
private Label infolabel;
private Table lastUnlockTable;
private Table lastUnlockLayout;
private boolean shown = true;
private float dsize = 58;
private float isize = 40;
@@ -179,6 +186,96 @@ public class HudFragment implements Fragment{
blockfrag.build(Core.scene.getRoot());
}
/**Show unlock notification for a new recipe.*/
public void showUnlock(Recipe recipe){
blockfrag.rebuild();
//if there's currently no unlock notification...
if(lastUnlockTable == null) {
Table table = new Table("button");
table.margin(12);
Table in = new Table();
//create texture stack for displaying
Stack stack = new Stack();
for (TextureRegion region : recipe.result.getCompactIcon()) {
Image image = new Image(region);
image.setScaling(Scaling.fit);
stack.add(image);
}
in.add(stack).size(48f).pad(2);
//add to table
table.add(in).padRight(8);
table.add("$text.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, Interpolation.fade), Actions.delay(4f),
//nesting actions() calls is necessary so the right prefHeight() is used
Actions.run(() -> container.actions(Actions.translateBy(0, table.getPrefHeight(), 1f, Interpolation.fade), Actions.run(() ->{
lastUnlockTable = null;
lastUnlockLayout = null;
}), Actions.removeActor())));
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
Array<Element> elements = new Array<>(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);
//correct plurals if needed
if(esize == 1){
((Label)lastUnlockLayout.getParent().find(e -> e instanceof Label)).setText("$text.unlocked.plural");
}
lastUnlockLayout.clearChildren();
lastUnlockLayout.defaults().size(size).pad(2);
for(int i = 0; i < esize && i <= cap; i ++){
lastUnlockLayout.add(elements.get(i));
if(i % col == col - 1){
lastUnlockLayout.row();
}
}
//if there's space, add it
if(esize < cap) {
Stack stack = new Stack();
for (TextureRegion region : recipe.result.getCompactIcon()) {
Image image = new Image(region);
image.setScaling(Scaling.fit);
stack.add(image);
}
lastUnlockLayout.add(stack);
}else{ //else, add a specific icon to denote no more space
lastUnlockLayout.addImage("icon-add");
}
lastUnlockLayout.pack();
}
}
private void toggleMenus(){
if (wavetable.getActions().size != 0) return;