package com.drakulo.games.ais.ui.state;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.newdawn.slick.Color;
import org.newdawn.slick.Font;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import com.drakulo.games.ais.AloneInSpace;
import com.drakulo.games.ais.core.GameData;
import com.drakulo.games.ais.core.GameEngine;
import com.drakulo.games.ais.core.NumberHelper;
import com.drakulo.games.ais.core.Resource;
import com.drakulo.games.ais.core.Settings;
import com.drakulo.games.ais.core.delayed.ResearchAction;
import com.drakulo.games.ais.core.tech.Technology;
import com.drakulo.games.ais.core.tech.TechnologyHelper;
import com.drakulo.games.ais.ui.FontHelper;
import com.drakulo.games.ais.ui.I18n;
import com.drakulo.games.ais.ui.ImageManager;
import com.drakulo.games.ais.ui.UIHelper;
import com.drakulo.games.ais.ui.component.ActionHandler;
import com.drakulo.games.ais.ui.component.MultilineLabel;
import com.drakulo.games.ais.ui.component.ProgressBar;
import com.drakulo.games.ais.ui.component.UITechnology;
import com.drakulo.games.ais.ui.component.button.Button;
import com.drakulo.games.ais.ui.component.button.ImageButton;
import com.drakulo.games.ais.ui.component.button.TextButton;
import de.matthiasmann.twl.Label;
/**
* The research state
*
* @author Drakulo
*
*/
public class ResearchState extends GameState {
private static final int TOPBAR_HEIGHT = 100;
private static final int DATA_ZONE_HEIGHT = 150;
private static final int BUTTON_WIDTH = 170;
/** The image background */
private Image background;
/** A map of Progress bar, one for each resource */
private Map<Resource, ProgressBar> resourceBars;
/** Back to the game button */
private Button backToSectorButton;
/** Start research button */
private Button startResearchButton;
/** Cancel research button */
private Button cancelResearchButton;
/** Home button to go back to the root */
private Button homeButton;
/** The technology displayed */
private UITechnology uiTechnology;
/** The parent technologies */
private List<UITechnology> parents;
/** The child technologies */
private List<UITechnology> children;
/** The root technologies */
private List<UITechnology> roots;
/** The research progress bar */
private ProgressBar researchProgress;
/** Flag indicating the return to the root technologies */
private boolean backToRoot;
/** The resource labels */
private Map<Resource, Label> storeLabels;
private Map<Resource, Label> costLabels;
private MultilineLabel technologyDescription;
public ResearchState(int id) {
super(id);
this.roots = new ArrayList<UITechnology>();
this.parents = new ArrayList<UITechnology>();
this.children = new ArrayList<UITechnology>();
this.researchProgress = new ProgressBar();
this.researchProgress.setWidth(202);
this.researchProgress.setHeight(10);
this.researchProgress.setEmptyColor(Color.darkGray);
this.researchProgress.setFilledColor(Color.magenta);
this.researchProgress.setX(10);
this.researchProgress.setY(80);
storeLabels = new HashMap<Resource, Label>();
costLabels = new HashMap<Resource, Label>();
for (Resource r : Resource.values()) {
Label ls = new Label();
ls.setTheme("resourceLabel");
ls.setSize(80, 15);
add(ls);
storeLabels.put(r, ls);
Label lc = new Label();
lc.setTheme("resourceLabel");
lc.setSize(80, 15);
add(lc);
costLabels.put(r, lc);
}
technologyDescription = new MultilineLabel("");
technologyDescription.setSize(250, 50);
add(technologyDescription);
}
@Override
public void initState() throws SlickException {
List<Technology> rootTechs = TechnologyHelper.getRootTechnologies();
for (Technology t : rootTechs) {
UITechnology uit = new UITechnology(t);
this.roots.add(uit);
add(uit);
}
this.background = ImageManager.getGfx("space_background");
this.resourceBars = new HashMap<Resource, ProgressBar>();
Resource[] resources = Resource.values();
for (Resource r : resources) {
this.resourceBars.put(r,
UIHelper.createBar(r.getI18n(), r.getColor(), false));
}
final int bh = 26;
final int padding = 5;
final int width = BUTTON_WIDTH * 2 + 15;
int x = Settings.WIDTH / 2 - width / 2;
int y = 10;
this.backToSectorButton = new TextButton(
I18n.get("research.back_to_sector"));
this.backToSectorButton.setSize(BUTTON_WIDTH, bh);
this.backToSectorButton.setPosition(x, y);
this.backToSectorButton.setActionHandler(new ActionHandler() {
@Override
public void run() {
GameData.getGame().enterState(AloneInSpace.SECTOR_STATE);
}
});
add(backToSectorButton);
x = this.backToSectorButton.getOX()
+ this.backToSectorButton.getWidth() + padding;
this.homeButton = new TextButton(I18n.get("research.back_to_root"));
this.homeButton.setSize(BUTTON_WIDTH, bh);
this.homeButton.setPosition(x, y);
this.homeButton.setActionHandler(new ActionHandler() {
@Override
public void run() {
backToRoot = true;
GameData.setLastViewedTechnology(null);
}
});
add(homeButton);
x = 600;
y = Settings.HEIGHT - padding - ImageButton.IB_DEFAULT_SIZE;
this.startResearchButton = new TextButton(I18n.get("research.start"));
startResearchButton.setSize(190, 25);
startResearchButton.hide();
researchProgress.setMaxValue(BigDecimal.valueOf(100));
this.startResearchButton.setActionHandler(new ActionHandler() {
@Override
public void run() {
// First, verify the amount of resources needed
Map<Resource, BigDecimal> cost = ResearchState.this.uiTechnology
.getTechnology().getCost();
Resource[] resources = Resource.values();
for (Resource r : resources) {
if (NumberHelper.inf(GameData.getSelectedColony()
.getResource(r), cost.get(r))) {
// Not enough resources
// TODO Play an error sound
return;
}
}
// The research can start
// Resoures are removed from the store
for (Resource r : resources) {
GameData.getSelectedColony().updateResource(r,
cost.get(r).negate());
}
ResearchState.this.startResearchButton.disable();
ResearchState.this.cancelResearchButton.enable();
// TODO externalize research duration in technologies file
ResearchAction action = new ResearchAction(
ResearchState.this.uiTechnology.getTechnology(), 5);
GameData.getSelectedColony().setResearch(action);
}
});
add(startResearchButton);
x += ImageButton.IB_DEFAULT_SIZE + padding;
this.cancelResearchButton = new ImageButton("delete"); // TODO I18N
this.cancelResearchButton.setPosition(x, y);
this.cancelResearchButton.disable();
this.cancelResearchButton.setActionHandler(new ActionHandler() {
@Override
public void run() {
GameData.getSelectedColony().setResearch(null);
ResearchState.this.startResearchButton.enable();
ResearchState.this.cancelResearchButton.disable();
}
});
}
@Override
public void renderState(Graphics g) throws SlickException {
// The rendering is splitted. Only parents and children of the current
// technology are shown. The player can navigate to parents and children
// by clicking on them. On the top of the screen, a minimap of the total
// technology tree shows where the view is on the global tree. The
// player may also go to a specific part of the research tree by
// clicking on a technology on the minimap
// DEBUG
// g.setColor(Color.red);
// final int avs = Settings.HEIGHT - TOPBAR_HEIGHT - DATA_ZONE_HEIGHT;
// g.drawLine(0, TOPBAR_HEIGHT + avs / 2, Settings.WIDTH, TOPBAR_HEIGHT
// + avs / 2);
// Render the background
g.drawImage(this.background, 0, 0);
Technology currentTech = GameData.getLastViewedTechnology();
if (currentTech != null && this.uiTechnology != null) {
// A technology is selected
renderTechnology(g);
} else {
for (UITechnology uit : roots)
add(uit);
// No technology is selected, all base technologies are shown
renderCategories(g);
}
// Render data zone
renderDataZone(g);
}
@Override
public void updateState(Input input, int time) throws SlickException {
if (backToRoot) {
backToRoot = false;
selectTechnology(null);
}
for (UITechnology uit : this.roots) {
uit.update(input);
}
// Display update
Technology lastViewed = GameData.getLastViewedTechnology();
// current = null & last != null
boolean c1 = this.uiTechnology == null && lastViewed != null;
// current != null & last = null
boolean c2 = this.uiTechnology != null && lastViewed == null;
boolean c3 = false;
if (this.uiTechnology != null) {
// Different technology
c3 = !this.uiTechnology.getTechnology().equals(lastViewed);
}
if (c1 || c2 || c3) {
selectTechnology(lastViewed);
}
if (this.uiTechnology != null) {
this.uiTechnology.update(input);
// Parents
for (UITechnology uit : this.parents) {
uit.update(input);
}
// Children
for (UITechnology uit : this.children) {
uit.update(input);
}
}
// Resource progress bars updates
Resource[] resources = Resource.values();
for (Resource r : resources) {
ProgressBar pb = this.resourceBars.get(r);
pb.update(input);
}
// Handling research buttons
ResearchAction action = GameData.getSelectedColony().getResearch();
if (action == null) {
// No research for now : authorize start research is research is
// possible
if (this.uiTechnology != null
&& this.uiTechnology.getTechnology().isResearchable()) {
// The technology is researchable
this.startResearchButton.enable();
} else {
// Action not possible
this.startResearchButton.disable();
}
this.cancelResearchButton.disable();
this.researchProgress.setValue(BigDecimal.ZERO);
} else {
// Research in progress
Technology t = action.getTechnology();
if (this.uiTechnology == null) {
// The player is showing the root : disable all
this.startResearchButton.disable();
this.cancelResearchButton.disable();
} else if (t.equals(this.uiTechnology.getTechnology())) {
// The player is showing the research in pregress : authorize
// stop button
this.startResearchButton.disable();
this.cancelResearchButton.enable();
} else {
// The player is showing a random technology : block research
// actions
this.startResearchButton.disable();
this.cancelResearchButton.disable();
}
this.researchProgress.setValue(BigDecimal.valueOf(action
.getProgression()));
}
// buttons
this.startResearchButton.update(input);
this.cancelResearchButton.update(input);
this.researchProgress.update(input);
// Game logic update
GameEngine.run(time);
}
/**
* Render the top zone
*
* @param g
* the graphic canvas
* @throws SlickException
*/
protected void to_do(Graphics g) throws SlickException {
// Info BG
UIHelper.drawBox(g, 0, 0, 222, 100);
// The buttons
this.backToSectorButton.render(g);
this.homeButton.render(g);
Font font = FontHelper.getDefaultFont();
String label;
// If a technology is currently beeing researched, progress is rendered
ResearchAction action = GameData.getSelectedColony().getResearch();
if (action == null) {
label = I18n.getFirstToUpper("research.no_research");
} else {
label = action.getTechnology().getName();
this.researchProgress.setMaxValue(BigDecimal.valueOf(100));
this.researchProgress.setValue(BigDecimal.valueOf(action
.getProgression()));
this.researchProgress.render(g);
}
font.drawString(10, 60, label);
// TODO the minimap
}
/**
* Render the selected technology with it's parents and children
*
* @param g
* The graphic canvas
* @throws SlickException
*/
private void renderTechnology(Graphics g) throws SlickException {
// DEBUG
// final int avs = Settings.HEIGHT - TOPBAR_HEIGHT - DATA_ZONE_HEIGHT;
// final int zones = this.children.size() + 1;
// final int zoneHeight = avs / zones;
// int y = TOPBAR_HEIGHT + zoneHeight;
// g.setColor(Color.red);
// for (UITechnology uit : this.children) {
// g.drawLine(0, y, Settings.WIDTH, y);
// y += zoneHeight;
// }
// Render the arrows
renderArrows(g);
// The technology is rendered in the center of the zone
this.uiTechnology.render(g);
// Parents are rendered vertically to the left of the current tech
for (UITechnology uit : this.parents) {
uit.render(g);
}
// Children are rendered vertically to the right of the current tech
for (UITechnology uit : this.children) {
uit.render(g);
}
}
/**
* Render the arrows showing hierarchy between the technology, it's parents
* and it's children
*
* @param g
* The graphic canvas
*/
private void renderArrows(Graphics g) {
int zoneWidth = Settings.WIDTH / 3;
int x;
int y;
int vy = 0;
// Parents arrows starts
for (UITechnology uit : this.parents) {
x = uit.getOX() + UITechnology.SIZE;
y = uit.getOY() + UITechnology.SIZE / 2;
UIHelper.drawHArrowLine(g, x, y, zoneWidth - x);
if (this.parents.size() > 1) {
if (vy == 0) {
// The start was not initialized yet
vy = y;
}
if (uit.equals(this.parents.get(this.parents.size() - 1))) {
// It is the last element, we can draw the vertical arrow
if (vy != 0) {
// There was mode than 1 element so there is a
// vertical line to render
UIHelper.drawVArrowLine(g, zoneWidth, vy,
(uit.getOY() + UITechnology.SIZE / 2) - vy);
}
}
}
}
// Left arrow line from the current technology's parents
y = this.uiTechnology.getOY() + UITechnology.SIZE / 2;
final int width = this.uiTechnology.getOX() - zoneWidth;
x = zoneWidth;
UIHelper.drawLeftToRightArrow(g, x, y, width);
if (!this.children.isEmpty()) {
// Right arrow start line to current technology's children
x = this.uiTechnology.getOX() + UITechnology.SIZE;
UIHelper.drawHArrowLine(g, x, y, width);
}
// Children
// Parents arrows starts
x = zoneWidth * 2;
vy = 0;
for (UITechnology uit : this.children) {
y = uit.getOY() + UITechnology.SIZE / 2;
UIHelper.drawLeftToRightArrow(g, x, y, uit.getOX() - x);
if (this.children.size() > 1) {
if (vy == 0) {
// The start was not initialized yet
vy = y;
}
if (uit.equals(this.children.get(this.children.size() - 1))) {
// It is the last element, we can draw the vertical arrow
if (vy != 0) {
// There was mode than 1 element so there is a
// vertical line to render
UIHelper.drawVArrowLine(g, x, vy,
(uit.getOY() + UITechnology.SIZE / 2) - vy);
}
}
}
}
}
private void renderCategories(Graphics g, Technology... technologies)
throws SlickException {
List<Technology> roots = TechnologyHelper.getRootTechnologies();
final int techNumber = roots.size();
final int avs = Settings.HEIGHT - TOPBAR_HEIGHT - DATA_ZONE_HEIGHT;
final int y = avs / 2 - UITechnology.SIZE / 2 + TOPBAR_HEIGHT;
final int zonePitch = Settings.WIDTH / techNumber;
final int squareSize = 48;
final int demiSqSize = squareSize / 2;
int x = zonePitch / 2 - demiSqSize;
// UITechnology uit;
for (UITechnology uit : this.roots) {
// DEBUG
// g.setColor(Color.red);
// g.drawLine(0, y, Settings.WIDTH, y);
// g.drawLine(zoneX, 0, zoneX, Settings.HEIGHT);
// /DEBUG
uit.setOX(x);
uit.setOY(y);
uit.render(g);
x += zonePitch;
}
}
private void renderDataZone(Graphics g) throws SlickException {
final int width = 300;
final int height = 250;
int sx = Settings.WIDTH / 2 - width / 2;
int sy = Settings.HEIGHT - height;
UIHelper.drawWindow(g, sx, sy, width, height, 5);
// Render the colony resources
Resource[] resources = Resource.values();
Image img;
Map<Resource, BigDecimal> resourceMap = GameData.getSelectedColony()
.getResources();
Technology t = null;
if (uiTechnology != null) {
t = uiTechnology.getTechnology();
}
Map<Resource, BigDecimal> costMap = null;
if (t != null) {
costMap = t.getCost();
}
int y = sy + height - 100;
int x = sx + 45;
final int rsx = x;
for (Resource r : resources) {
UIHelper.drawDarkBox(g, x, y += 3, 24, 20);
img = ImageManager.getGfx(r.getGfxBase() + "-small");
img.draw(x + 3, y, r.getColor());
String value = resourceMap.get(r).toString();
Label lbl = this.storeLabels.get(r);
final int zoneHeight = 15;
UIHelper.drawDarkBox(g, x + 24, y, lbl.getWidth() + 4, zoneHeight);
lbl.setPosition(x + 24, y);
lbl.setText(value);
lbl.setVisible(true);
x += lbl.getWidth() + 15;
String costValue = "";
if (costMap != null) {
costValue = costMap.get(r).toString();
}
Label costLbl = this.costLabels.get(r);
UIHelper.drawDarkBox(g, x + 24, y, costLbl.getWidth() + 4,
zoneHeight);
costLbl.setPosition(x + 24, y);
costLbl.setText(costValue);
costLbl.setVisible(true);
y += 20;
x = rsx;
}
if (t != null) {
Font font = FontHelper.getFont(FontHelper.HEMI_HEAD, Color.white,
25);
font.drawString(sx + 10, sy + 10, t.getName());
technologyDescription.setText(t.getDescription());
technologyDescription.setPosition(sx + 10, sy += 35);
} else {
technologyDescription.setText("");
}
x = Settings.WIDTH / 2 - startResearchButton.getWidth() / 2;
y = sy + technologyDescription.getHeight();
startResearchButton.setPosition(x, y);
startResearchButton.render(g);
// int y = Settings.HEIGHT - DATA_ZONE_HEIGHT - 1;
// UIHelper.drawWindow(g, 0, y, Settings.WIDTH - 1, DATA_ZONE_HEIGHT,
// 5);
//
// // Render resources
// final int pitch = 37;
// StringBuilder builder = new StringBuilder();
// BigDecimal inStore;
// BigDecimal max;
// ProgressBar pb;
// Map<Resource, BigDecimal> modifiers = GameData.getSelectedColony()
// .getLastModifiers();
// BigDecimal modifier;
// y = Settings.HEIGHT - DATA_ZONE_HEIGHT + 5;
// Font font = FontHelper.getDefaultFont();
// Resource[] resources = Resource.values();
// for (Resource r : resources) {
// // Retreiving data
// inStore = GameData.getSelectedColony().getResource(r);
// max = GameData.getSelectedColony().getStorSpace(r);
//
// // Creating and rendering the progress bar
// pb = this.resourceBars.get(r);
// pb.setX(10);
// pb.setY(y);
// pb.setMaxValue(max);
// pb.setValue(inStore);
// pb.render(g);
//
// builder.setLength(0);
// builder.append(inStore.setScale(0, BigDecimal.ROUND_FLOOR))
// .append("/").append(max);
// builder.append(" (");
// modifier = modifiers.get(r);
// if (modifier.signum() == -1) {
// // Negative value
// builder.append("-");
// } else if (modifier.signum() == 1) {
// // Positive value
// builder.append("+");
// }
// builder.append(modifier);
// builder.append(")");
// font.drawString(pb.getOX() + pb.getWidth() + 10, y + 1,
// builder.toString());
//
// y += pitch;
// }
//
// // Current technology data
// y = Settings.HEIGHT - DATA_ZONE_HEIGHT - 1;
// Font titleFont = FontHelper.getFont(FontHelper.DROID,
// java.awt.Color.black, 20);
// if (this.uiTechnology != null) {
// int x = Settings.WIDTH / 2;
// Technology t = this.uiTechnology.getTechnology();
// titleFont.drawString(x, y + 2, t.getName());
//
// y += 32;
// font.drawString(x, y, t.getDescription());
//
// // Render resources needed
// final int resPitch = 20;
// Map<Resource, BigDecimal> cost = t.getCost();
// y += 30;
// for (Resource r : resources) {
// font.drawString(x, y, r.toString());
// font.drawString(x + 100, y, cost.get(r).toString());
// y += resPitch;
// }
//
// this.startResearchButton.render(g);
// this.cancelResearchButton.render(g);
// }
}
/**
* Selects the technology for display
*
* @param t
* The technology to display
*/
private void selectTechnology(Technology t) {
remove(this.uiTechnology);
removeAll(this.children);
removeAll(this.parents);
if (t == null) {
// Show roots technologies
for (UITechnology uit : roots) {
add(uit);
}
this.uiTechnology = null;
startResearchButton.hide();
return;
} else {
for (UITechnology uit : roots) {
remove(uit);
}
}
startResearchButton.show();
final int pitch = Settings.WIDTH / 3 - UITechnology.SIZE / 2;
final int x = Settings.WIDTH / 2 - UITechnology.SIZE / 2;
final int avs = Settings.HEIGHT - TOPBAR_HEIGHT - DATA_ZONE_HEIGHT;
final int zoneHeight = avs / 2;
int y = TOPBAR_HEIGHT + zoneHeight - UITechnology.SIZE / 2;
this.uiTechnology = new UITechnology(t);
this.uiTechnology.setPosition(x, y);
this.uiTechnology.setSelected(true);
this.children.clear();
this.children.addAll(createUITechnologies(t.getChildren(), x + pitch));
this.parents.clear();
this.parents.addAll(createUITechnologies(t.getParents(), x - pitch));
if (this.parents.isEmpty()) {
// There is no parent so it is a root technology. We add a link to
// show all roots technologies
UITechnology root = new UITechnology("share");
root.setPosition(x - pitch, y);
this.parents.add(root);
}
}
/**
* Create a list of UITechnology from a list of thechnologies and the X
* coordinates. Y coordinates will be calculated
*
* @param technologies
* The technology list
* @param x
* the X coordinate
* @return the list of UITechnology
*/
private List<UITechnology> createUITechnologies(
List<Technology> technologies, int x) {
List<UITechnology> result = new ArrayList<UITechnology>();
final int number = technologies.size();
if (number == 0) {
return result;
}
final int avs = Settings.HEIGHT - TOPBAR_HEIGHT - DATA_ZONE_HEIGHT;
final int zones = technologies.size() + 1;
final int zoneHeight = avs / zones;
int y = TOPBAR_HEIGHT + zoneHeight - UITechnology.SIZE / 2;
for (Technology t : technologies) {
UITechnology uit = new UITechnology(t);
uit.setPosition(x, y);
result.add(uit);
add(uit);
y += zoneHeight;
}
add(uiTechnology);
return result;
}
@Override
protected void renderTopZone(Graphics g) throws SlickException {
final int w = BUTTON_WIDTH * 2 + 25;
final int h = 46 + 50;
final int x = Settings.WIDTH / 2 - w / 2 - 5;
UIHelper.drawWindow(g, x, 0, w, h, 5);
// Render the colony name
UIHelper.drawWindow(g, Settings.WIDTH - 140, 0, 140, 46, 5);
Font font = FontHelper.getFont(FontHelper.HEMI_HEAD, Color.white, 20);
font.drawString(Settings.WIDTH - 130, 15, GameData.getSelectedColony()
.getName());
researchProgress.setOX(x + 10);
researchProgress.setOY(41);
researchProgress.setWidth(BUTTON_WIDTH * 2 + 5);
String label = "";
ResearchAction action = GameData.getSelectedColony().getResearch();
if (action == null) {
label = I18n.getFirstToUpper("research.no_research");
} else {
label = action.getTechnology().getName();
this.researchProgress.setMaxValue(BigDecimal.valueOf(100));
this.researchProgress.setValue(BigDecimal.valueOf(action
.getProgression()));
}
researchProgress.render(g);
font.drawString(x + 10, 60, label);
}
}