Functional tech tree

This commit is contained in:
Anuken
2019-01-19 22:16:28 -05:00
parent 093043750b
commit d34f228d2f
18 changed files with 1217 additions and 1079 deletions

View File

@@ -42,6 +42,7 @@ public class Recipes implements ContentList{
new Recipe(effect, Blocks.container, new ItemStack(Items.titanium, 200));
new Recipe(effect, Blocks.vault, new ItemStack(Items.titanium, 500), new ItemStack(Items.thorium, 250));
new Recipe(effect, Blocks.launchPad, new ItemStack(Items.copper, 500));
new Recipe(effect, Blocks.core, new ItemStack(Items.titanium, 2000)).setHidden(true).setAlwaysUnlocked(true);
//projectors
new Recipe(effect, Blocks.mendProjector, new ItemStack(Items.lead, 200), new ItemStack(Items.titanium, 150), new ItemStack(Items.titanium, 50), new ItemStack(Items.silicon, 180));
@@ -160,9 +161,6 @@ public class Recipes implements ContentList{
new Recipe(units, Blocks.repairPoint, new ItemStack(Items.lead, 30), new ItemStack(Items.copper, 30), new ItemStack(Items.silicon, 30));
//removed for testing MOBA-style unit production
//new Recipe(units, Blocks.commandCenter, new ItemStack(Items.lead, 100), new ItemStack(Items.densealloy, 100), new ItemStack(Items.silicon, 200));
//LIQUIDS
new Recipe(liquid, Blocks.conduit, new ItemStack(Items.lead, 1));
new Recipe(liquid, Blocks.pulseConduit, new ItemStack(Items.titanium, 1), new ItemStack(Items.lead, 1));

View File

@@ -13,7 +13,7 @@ public class TechTree implements ContentList{
@Override
public void load(){
root = node(null, with(), () -> {
root = node(Blocks.core, with(), () -> {
node(Blocks.conveyor, with(Items.copper, 100), () -> {
node(Blocks.launchPad, with(Items.copper, 100), () -> {
@@ -252,12 +252,8 @@ public class TechTree implements ContentList{
return new TechNode(block, requirements, children);
}
private TechNode node(Block block, ItemStack[] requirements){
return new TechNode(block, requirements, () -> {});
}
private TechNode node(Block block){
return new TechNode(block, with(), () -> {});
private void node(Block block, ItemStack[] requirements){
new TechNode(block, requirements, () -> {});
}
public static class TechNode{
@@ -281,5 +277,9 @@ public class TechTree implements ContentList{
children.run();
context = last;
}
public Recipe recipe(){
return Recipe.getByResult(block);
}
}
}

View File

@@ -48,6 +48,10 @@ public class GlobalData{
modified = true;
}
public boolean has(Item item, int amount){
return items.get(item, 0) >= amount;
}
public ObjectIntMap<Item> items(){
return items;
}

View File

@@ -41,6 +41,7 @@ public class Palette{
heal = Color.valueOf("98ffa9"),
bar = Color.SLATE,
accent = Color.valueOf("ffd37f"),
locked = Color.valueOf("6b6b6b"),
accentBack = Color.valueOf("d4816b"),
place = Color.valueOf("6335f8"),
remove = Color.valueOf("e55454"),

View File

@@ -8,11 +8,15 @@ import io.anuke.arc.util.Pack;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.entities.traits.SaveTrait;
import io.anuke.mindustry.entities.traits.TypeTrait;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Content;
import io.anuke.mindustry.game.MappableContent;
import io.anuke.mindustry.game.Rules;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Serialization;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.BlockPart;
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -120,7 +124,7 @@ public abstract class SaveFileVersion{
tile.entity.readConfig(stream);
tile.entity.read(stream);
if(tile.block() == Blocks.core){
if(tile.block() instanceof CoreBlock){
state.teams.get(t).cores.add(tile);
}
}else if(wallid == 0){

View File

@@ -34,7 +34,7 @@ public class DeployDialog extends FloatingDialog{
ObjectIntMap<Item> items = data.items();
for(Item item : Vars.content.items()){
if(item.type == ItemType.material && data.isUnlocked(item)){
add(items.get(item, 0) + "").left();
label(() -> items.get(item, 0) + "").left();
addImage(item.region).size(8*4).pad(4);
add("[LIGHT_GRAY]" + item.localizedName()).left();
row();

View File

@@ -1,34 +1,42 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.ObjectSet;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Lines;
import io.anuke.arc.graphics.g2d.ScissorStack;
import io.anuke.arc.input.KeyCode;
import io.anuke.arc.math.Interpolation;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.scene.Group;
import io.anuke.arc.scene.actions.Actions;
import io.anuke.arc.scene.event.InputEvent;
import io.anuke.arc.scene.event.InputListener;
import io.anuke.arc.scene.event.Touchable;
import io.anuke.arc.scene.style.TextureRegionDrawable;
import io.anuke.arc.scene.ui.ImageButton;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Align;
import io.anuke.arc.util.Log;
import io.anuke.arc.util.Structs;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.TechTree;
import io.anuke.mindustry.content.TechTree.TechNode;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.type.Recipe;
import io.anuke.mindustry.type.Recipe.RecipeVisibility;
import io.anuke.mindustry.ui.TreeLayout;
import io.anuke.mindustry.ui.TreeLayout.TreeNode;
import io.anuke.mindustry.world.Block.Icon;
import static io.anuke.mindustry.Vars.content;
import static io.anuke.mindustry.Vars.*;
public class TechTreeDialog extends FloatingDialog{
private ObjectSet<TechTreeNode> nodes = new ObjectSet<>();
private TechTreeNode root = new TechTreeNode(TechTree.root, null);
private static final float nodeSize = 60f;
private int toasts;
public TechTreeDialog(){
super("$techtree");
@@ -36,14 +44,14 @@ public class TechTreeDialog extends FloatingDialog{
TreeLayout layout = new TreeLayout();
layout.gapBetweenLevels = 60f;
layout.gapBetweenNodes = 40f;
layout.layout(new TechTreeNode(TechTree.root, null));
layout.layout(root);
cont.add(new View()).grow();
{ //debug code; TODO remove
ObjectSet<Recipe> used = new ObjectSet<Recipe>().select(t -> true);
for(TechTreeNode node : nodes){
if(node.node.block != null) used.add(Recipe.getByResult(node.node.block));
used.add(node.node.recipe());
}
Array<Recipe> recipes = content.recipes().select(r -> r.visibility == RecipeVisibility.all && !used.contains(r));
recipes.sort(Structs.comparing(r -> r.cost));
@@ -55,11 +63,44 @@ public class TechTreeDialog extends FloatingDialog{
}
}
shown(() -> checkNodes(root));
addCloseButton();
}
void checkNodes(TechTreeNode node){
boolean locked = locked(node);
if(!locked) node.visible = true;
for(TreeNode child : node.children){
TechTreeNode l = (TechTreeNode)child;
l.visible = !locked;
checkNodes(l);
}
}
void showToast(String info){
toasts ++;
int t = toasts;
Table table = new Table();
table.actions(Actions.fadeOut(7f, Interpolation.fade), Actions.run(() -> toasts --), Actions.removeActor());
table.top().add(info);
table.update(() -> {
table.toFront();
table.setPosition(Core.graphics.getWidth()/2f, Core.graphics.getHeight() - 21 - t*20f, Align.top);
});
Core.scene.add(table);
}
boolean locked(TreeNode node){
return locked(((TechTreeNode)node).node);
}
boolean locked(TechNode node){
return !data.isUnlocked(node.recipe());
}
class TechTreeNode extends TreeNode{
final TechNode node;
boolean visible = true;
public TechTreeNode(TechNode node, TreeNode parent){
this.node = node;
@@ -77,21 +118,47 @@ public class TechTreeDialog extends FloatingDialog{
class View extends Group{
float panX = 0, panY = 0;
Rectangle clip = new Rectangle();
boolean moved = false;
Rectangle clip = new Rectangle();
ImageButton hoverNode;
Table infoTable = new Table();
{
infoTable.touchable(Touchable.enabled);
for(TechTreeNode node : nodes){
ImageButton button = new ImageButton(node.node.block == null ? Blocks.core.icon(Icon.medium) : node.node.block.icon(Icon.medium), "node");
ImageButton button = new ImageButton(node.node.block.icon(Icon.medium), "node");
button.clicked(() -> {
if(moved) return;
Vars.ui.content.show(Recipe.getByResult(node.node.block == null ? Blocks.conveyor : node.node.block));
if(mobile){
hoverNode = button;
rebuild();
}else if(data.hasItems(node.node.requirements) && locked(node)){
unlock(node.node);
}
});
button.tapped(() -> {
moved = false;
button.hovered(() -> {
if(!mobile && hoverNode != button && node.visible){
hoverNode = button;
rebuild();
}
});
button.exited(() -> {
if(hoverNode == button && !infoTable.hasMouse() && !hoverNode.hasMouse()){
hoverNode = null;
rebuild();
}
});
button.touchable(() -> !node.visible ? Touchable.disabled : Touchable.enabled);
button.setUserObject(node.node);
button.tapped(() -> moved = false);
button.setSize(nodeSize, nodeSize);
button.update(() -> button.setPosition(node.x + panX + width/2f, node.y + panY + height/2f - 0.5f, Align.center));
button.update(() -> {
button.setPosition(node.x + panX + width/2f, node.y + panY + height/2f - 0.5f, Align.center);
button.getStyle().up = Core.scene.skin.getDrawable(!locked(node) ? "content-background" : "content-background-locked");
((TextureRegionDrawable)button.getStyle().imageUp)
.setRegion(node.visible ? node.node.block.icon(Icon.medium) : Core.atlas.find("icon-tree-locked"));
button.getImage().setColor(!locked(node) ? Color.WHITE : Color.GRAY);
});
addChild(button);
}
@@ -115,6 +182,75 @@ public class TechTreeDialog extends FloatingDialog{
});
}
void unlock(TechNode node){
data.unlockContent(node.recipe());
data.removeItems(node.requirements);
showToast(Core.bundle.format("researched", node.block.formalName));
checkNodes(root);
hoverNode = null;
rebuild();
}
void rebuild(){
ImageButton button = hoverNode;
infoTable.remove();
infoTable.clear();
infoTable.update(null);
if(button == null) return;
TechNode node = (TechNode)button.getUserObject();
infoTable.exited(() -> {
if(hoverNode == button && !infoTable.hasMouse() && !hoverNode.hasMouse()){
hoverNode = null;
rebuild();
}
});
infoTable.background("content-background");
infoTable.update(() -> infoTable.setPosition(button.getX() + button.getWidth(), button.getY() + button.getHeight(), Align.topLeft));
infoTable.margin(0).left().defaults().left();
infoTable.addImageButton("icon-info", "node", 14*2, () -> ui.content.show(node.recipe())).growY().width(50f);
infoTable.add().grow();
infoTable.table(desc -> {
desc.left().defaults().left();
desc.add(node.block.formalName);
desc.row();
if(locked(node)){
desc.table(t -> {
t.left();
for(ItemStack req : node.requirements){
t.table(list -> {
list.left();
list.addImage(req.item.getContentIcon()).size(8 * 3).padRight(3);
list.add(req.item.localizedName()).color(Color.LIGHT_GRAY);
list.add(" " + Math.min(data.items().get(req.item, 0), req.amount) + " / " + req.amount)
.color(data.has(req.item, req.amount) ? Color.LIGHT_GRAY : Color.SCARLET);
}).fillX().left();
t.row();
}
});
}else{
desc.add("$completed");
}
}).pad(9);
if(mobile && locked(node)){
infoTable.row();
infoTable.addImageTextButton("$research", "icon-check", "node", 16*2, () -> unlock(node))
.disabled(b -> !data.hasItems(node.requirements)).growX().height(44f).colspan(3);
}
addChild(infoTable);
infoTable.pack();
}
@Override
public void draw(){
if(!ScissorStack.pushScissors(clip.set(x, y, width, height))){
@@ -123,26 +259,16 @@ public class TechTreeDialog extends FloatingDialog{
float offsetX = panX + width/2f + x, offsetY = panY + height/2f + y;
Lines.stroke(3f, Palette.accent);
for(TreeNode node : nodes){
for(TreeNode child : node.children){
Lines.stroke(3f, locked(node) || locked(child) ? Palette.locked : Palette.accent);
Lines.line(node.x + offsetX, node.y + offsetY, child.x + offsetX, child.y + offsetY);
}
}
super.draw();
/*
Draw.color();
for(TechTreeNode node : nodes){
Draw.drawable("content-background", node.x + offsetX - nodeSize/2f, node.y + offsetY - nodeSize/2f, nodeSize, nodeSize);
TextureRegion region = node.node.block == null ? Blocks.core.icon(Icon.medium) : node.node.block.icon(Icon.medium);
Draw.rect(region, node.x + offsetX, node.y + offsetY - 0.5f, region.getWidth(), region.getHeight());
}*/
ScissorStack.popScissors();
}
}