/*
* Created on 03.01.2004
*/
package net.sf.colossus.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import net.sf.colossus.game.Creature;
import net.sf.colossus.game.RecruitGraph;
import net.sf.colossus.guiutil.KDialog;
import net.sf.colossus.server.BattleStrikeServerSide;
import net.sf.colossus.server.LegionServerSide;
import net.sf.colossus.server.VariantSupport;
import net.sf.colossus.util.HTMLColor;
import net.sf.colossus.variant.BattleHex;
import net.sf.colossus.variant.CreatureType;
import net.sf.colossus.variant.HazardHexside;
import net.sf.colossus.variant.HazardTerrain;
import net.sf.colossus.variant.Hazards;
import net.sf.colossus.variant.IVariant;
import net.sf.colossus.variant.MasterBoardTerrain;
import net.sf.colossus.variant.Variant;
/**
* A dialog frame that displays lots of (almost static) information
* about one specific creature type. i.e the power/skill, the musterings
* and the abilities in the different hazards.
*
* Non-static information that might be shown is the number of creatures
* left in the caretaker's stack.
*
* The dialog is thought to be popped up and closed if needed, displaying
* information about one selected creature type. The info in the dialog is
* updated if needed.
*
* Implementation details:
* The dialog mainly contains one JTextEdit control which itself
* display HTML text! It is thinkable, that the HTML text comes from
* an URL (easy to implement). Currently the whole HTML is built
* line by line like a servlet would do it.
*
* To figure out some of the more difficult properties of the creature
* I "simulate" a tiny battlefield, where the creature in question
* engages other creatures in different hazard terrains. This is
* of course highly dependent on the battle implementation. It can easily
* break. I tried to be very generic -- things that might break, should
* break on compilation time, and not display wrong information.
*
*
* TODO this dialog should have a SaveWindow attached to it.
*
* TODO hexside Hazards
* Clemens: I started adding the hexside hazards, but that is not completed;
* for one, the simulatedXXX setup cannot easily be extended calculate that
* right, and there it is dependent on "atop XXX" or "below XXX" .
* So, I leave the extended table creation there, but do not add the hexside
* hazards into the hazards Collection so that it just shows same as before.
* There is a lot of things that need improvement, see
* 2136671 Show creature detail window...
*
* @author Towi, copied from ShowRecruitTree
*/
public final class ShowCreatureDetails extends KDialog
{
// Client acting as placeholder for Variant
// TODO ivariant can be removed when Variant itself
// is able to provide that information
private final IVariant ivariant;
private final Collection<Hazards> hazards;
private final BattleStrikeServerSide battleStrikeSS;
/** pops up the non-modal dialog. info can be updated if needed.
* @param parentFrame parent frame, i.e. the master board
* @param creature creature to show detailed info for.
* @param point coordinate on screen to display windows, or null.
* @param pane if 'point' is not null it is relative to this.
* @param variant the current Variant
* @param clientGui for now, the Client acting as deputy to answer Variant
* questions variant cannot answer yet, and we get iVariant from clientGui
*/
public ShowCreatureDetails(final JFrame parentFrame,
final CreatureType creature, final Point point,
final JScrollPane pane, Variant variant, ClientGUI clientGui)
{
super(parentFrame, "Creature Info: " + creature.getName(), false);
this.ivariant = clientGui.getClient();
this.battleStrikeSS = new BattleStrikeServerSide(clientGui.getClient()
.getGame());
Collection<HazardTerrain> terrainHazards = HazardTerrain
.getAllHazardTerrains();
// Collection<HazardHexside> hexsideHazards = HazardHexside
// .getAllHazardHexsides();
this.hazards = new ArrayList<Hazards>();
hazards.addAll(terrainHazards);
// Not fully implemented
// hazards.addAll(hexsideHazards);
setBackground(Color.lightGray);
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
dispose();
}
});
Container cnt = getContentPane();
showCreatureDetails(cnt, creature, variant);
pack();
addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
dispose();
}
});
if (point != null)
{
placeRelative(parentFrame, point, pane);
}
setVisible(true);
repaint();
}
/**
* @param cnt the awt container where the info wil be shown in.
* it will be emptied.
* @param creature the creature that details you want to show
*/
public void showCreatureDetails(final Container cnt,
final CreatureType creature, Variant variant)
{
// clear all the elements
cnt.removeAll();
cnt.setLayout(new BorderLayout());
// prepare main pane
JEditorPane pane = new JEditorPane();
pane.setEditable(false);
// define the content
StringBuilder s = new StringBuilder();
_head(s, creature);
//
// general
//
_section(s, "General Characteristics");
_trSpan(s, "Name",
creature.getName() + " <i>(" + creature.getPluralName() + ")</i>");
_trSpan(s, "Power..Skill",
creature.getPower() + ".." + creature.getSkill());
_trSpan(s, "Total count", "" + creature.getMaxCount());
_trSpan(s, "Rangestrike", (creature.isRangestriker() ? "yes" : "no")
+ (creature.useMagicMissile() ? " <b>(magic missiles)</b>" : ""));
_trSpan(s, "Flier", creature.isFlier() ? "yes" : "no");
if (creature.isPoison())
_trSpan(s, "Poison", "yes: " + creature.getPoison());
else
_trSpan(s, "Poison", "no");
_trSpan(s, "Summonable", creature.isSummonable() ? "yes" : "no");
// TODO Instead show full list of "where and for each multiple of X
_trSpan(s, "Acquirable", variant.isAcquirable(creature) ? "yes" : "no");
_trSpan(
s,
_low("Lord"),
creature.isLordOrDemiLord() ? (creature.isLord() ? "<u><b>Lord</b></u>"
: "<b>Demi-Lord</b>")
: _low("no"));
StringBuilder buf = new StringBuilder();
String sp = " ";
String separator = "";
for (HazardTerrain terrain : HazardTerrain.getAllHazardTerrains())
{
if (creature.isNativeIn(terrain))
{
buf.append(separator);
separator = ", ";
buf.append(terrain.getName());
}
}
for (int idx = 0; idx < HEXSIDES.length; idx++)
{
if (creature.isNativeAt(HEXSIDES[idx]))
{
buf.append(separator);
separator = ", ";
buf.append(HEXSIDE_NAMES[idx]);
}
}
_trSpan(s, "Native Hazards", buf.toString());
//
// recruit
//
_section(s, "Recruit");
// in
for (MasterBoardTerrain terrain : variant.getTerrains())
{
buf = new StringBuilder();
// TODO use variant instead?
List<CreatureType> recruiters = VariantSupport.getCurrentVariant()
.getCreatureTypesAsList();
separator = "";
for (int ri = 0; ri < recruiters.size(); ri++)
{
final CreatureType recruiter = recruiters.get(ri);
int num = ivariant.numberOfRecruiterNeeded(recruiter,
creature, terrain, null);
if (num == 1 && creature.getMaxCount() == 1
&& recruiter.getName().equals(creature.getName()))
{
// skip self-recruiting if there is only one of them
// TODO skip already during load
}
else if (num == 1)
{
buf.append(separator + "by" + sp + "1" + sp
+ recruiter.getName());
separator = ", ";
}
// TODO skip the > getMaxCount() already during Variant load
else if ((num > 0) && num < recruiter.getMaxCount()
&& num < RecruitGraph.BIGNUM
&& !recruiter.getName().equals("Titan"))
{
buf.append(separator + "by" + sp + num + sp
+ recruiter.getPluralName());
separator = ", ";
}
}
if (buf.length() > 0)
{
Color color = terrain.getColor().brighter();
s.append(MessageFormat.format(
"<tr><td bgcolor={0}>in {1}</td>"
+ "<td colspan={2}><font color=blue>{3}</font></td>"
+ "</tr>", new Object[] {
HTMLColor.colorToCode(color), terrain.getId(),
"" + (hazards.size() + 1), buf.toString(), }));
}
}
// out
for (MasterBoardTerrain terrain : variant.getTerrains())
{
buf = new StringBuilder();
// TODO use variant instead?
List<CreatureType> recruits = VariantSupport.getCurrentVariant()
.getCreatureTypesAsList();
separator = "";
for (int ri = 0; ri < recruits.size(); ri++)
{
final CreatureType recruit = recruits.get(ri);
int num = ivariant.numberOfRecruiterNeeded(creature, recruit,
terrain, null);
if (num == 1 && creature.getMaxCount() == 1
&& recruit.getName().equals(creature.getName()))
{
// skip self-recruiting if there is only one of them
// TODO skip already during load
}
// TODO skip the > getMaxCount() already during Variant load
else if (num > 0 && num < creature.getMaxCount()
&& num < RecruitGraph.BIGNUM)
{
buf.append(separator);
separator = ", ";
buf.append(num + sp + "recruit" + sp + "a" + sp
+ recruit.getName());
}
}
if (buf.length() > 0)
{
Color color = terrain.getColor().brighter();
s.append(MessageFormat.format(
"<tr><td bgcolor={0}>in {1}</td>"
+ "<td colspan={2}><font color=green>{3}</font></td>"
+ "</tr>", new Object[] {
HTMLColor.colorToCode(color), terrain.getId(),
"" + (hazards.size() + 1), buf.toString(), }));
}
}
//
// Battle
//
_section(s, "Battle");
// subtable title
String explanation = "For a target in Plains (with same skill as "
+ "attacking " + creature.getName() + "), the <br> table below "
+ "shows nr of dice to roll and which rolled numbers are hits.";
s.append(MessageFormat.format(
"<tr><td bgcolor=#dddddd colspan={0}>{1}</td></tr>", new Object[] {
"" + (hazards.size() + 2), explanation, }));
SimulatedCritter critter = new SimulatedCritter(creature,
HazardTerrain.getDefaultTerrain());
SimulatedCritter other = new SimulatedCritter(creature,
HazardTerrain.getDefaultTerrain());
// =============================================================
// hazards row 1
s.append("<tr><td ROWSPAN=2 align=right>" + creature.getName()
+ " in</td><td></td>");
for (Iterator<Hazards> iterator = hazards.iterator(); iterator
.hasNext();)
{
iterator.next(); // skip one
if (!iterator.hasNext())
{
break;
}
Hazards hazard = iterator.next();
Color color = Color.lightGray;
String hazardName = hazard.getName();
if (hazard instanceof HazardTerrain)
{
critter.setNewHazardHex((HazardTerrain)hazard);
color = critter.getHazardColor().brighter();
}
else
{
other.setHexsideHazard((HazardHexside)hazard);
color = other.getHexsideColor().brighter();
// TODO fix Tower/Wall in data files
if (hazard == HazardHexside.TOWER)
{
hazardName = "Wall";
}
}
String colspan = "2";
s.append(MessageFormat.format(
"<td bgcolor={0} colspan={2}>{1}</td>", new Object[] {
HTMLColor.colorToCode(color), hazardName, colspan }));
}
s.append("</tr>");
// =============================================================
// hazards row 2
s.append("<tr>");
for (Iterator<Hazards> iterator = hazards.iterator(); iterator
.hasNext();)
{
Hazards hazard = iterator.next();
Color color = Color.lightGray;
String hazardName = hazard.getName();
if (hazard instanceof HazardTerrain)
{
critter.setNewHazardHex((HazardTerrain)hazard);
color = critter.getHazardColor().brighter();
}
else
{
other.setHexsideHazard((HazardHexside)hazard);
color = other.getHexsideColor().brighter();
// TODO fix Tower/Wall in data files
if (hazard == HazardHexside.TOWER)
{
hazardName = "Wall";
}
}
String colspan = "2";
s.append(MessageFormat.format(
"<td bgcolor={0} colspan={2}>{1}</td>", new Object[] {
HTMLColor.colorToCode(color), hazardName, colspan, }));
if (iterator.hasNext())
{
iterator.next(); // skip one
}
}
s.append("</tr>");
// =============================================================
// the info: the table content
// ... how many dice to roll
s.append("<tr><th nowrap>Dice count</th>");
for (Hazards hazard : hazards)
{
Color color;
String text;
if (hazard instanceof HazardTerrain)
{
HazardTerrain terrain = (HazardTerrain)hazard;
critter.setNewHazardHex(terrain);
color = critter.getHazardColor().brighter();
text = "" + critter.getSimulatedDiceCount(other);
}
else
{
other.setHexsideHazard((HazardHexside)hazard);
color = other.getHexsideColor().brighter();
text = "?";
}
s.append(MessageFormat.format("<td bgcolor={0}>{1}</td>",
new Object[] { HTMLColor.colorToCode(color), text, }));
}
s.append("<td bgcolor=#dddddd></td></tr>");
// =============================================================
// ... hit treshold
s.append("<tr><th nowrap>Hit if >=</th>");
for (Hazards hazard : hazards)
{
Color color;
String text;
if (hazard instanceof HazardTerrain)
{
HazardTerrain terrain = (HazardTerrain)hazard;
critter.setNewHazardHex(terrain);
color = critter.getHazardColor().brighter();
text = "" + critter.getSimulatedStrikeNr(other);
}
else
{
other.setHexsideHazard((HazardHexside)hazard);
color = other.getHexsideColor().brighter();
text = "?";
}
s.append(MessageFormat.format("<td bgcolor={0}>{1}</td>",
new Object[] { HTMLColor.colorToCode(color), text, }));
}
s.append("<td bgcolor=#dddddd></td></tr>");
// XCV ...work in progress...
// towi ...work in progress...
//
// add stuff here if you like
//
// close
s.append("</table>");
s.append("</body></html>");
// put in the content
pane.setContentType("text/html");
pane.setText(s.toString());
// layout everything
JScrollPane scrollPane = new JScrollPane(pane);
scrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setPreferredSize(new Dimension(500, 800));
scrollPane.setMinimumSize(new Dimension(200, 400));
cnt.add(scrollPane, BorderLayout.CENTER);
}
//
// helpers
//
/** easy access to hex side identifiers. */
private static final char[] HEXSIDES = { ' ', 'd', 'c', 's', 'w', 'r' };
/** define hex side names for table column headers. */
private static final String[] HEXSIDE_NAMES = { "nothing", "dune",
"cliff", "slope", "wall", "river" };
/** html header and start of page. */
private static void _head(StringBuilder s, final CreatureType cr)
{
s.append("<html><head></head><body bgcolor="
+ HTMLColor.colorToCode(Color.LIGHT_GRAY) + ">");
s.append("<h2>Creature Details: <b>" + cr.getName() + "</b></h2>");
s.append("<table width=100%>");
}
/** start of a named section.
* @param s in/out
*/
private void _section(StringBuilder s, final String name)
{
s.append("<tr bgcolor=gray><td colspan=" + (hazards.size() + 1) + ">");
s.append("<b>" + name + "</b>");
s.append("</td></tr>");
}
/** a headered table row, the data column spans.
* @param s in/out
*/
private void _trSpan(StringBuilder s, final String name, final String value)
{
s.append(MessageFormat.format(
"<tr><th>{0}</th><td colspan={1}>{2}</td></tr>", new Object[] {
name, // 0
"" + (hazards.size() + 1), // 1
value, // 2
}));
}
/** wrap HTML code around s to make it dark, or gray. */
private static String _low(final String s)
{
return "<font color=gray>" + s + "</font>";
}
//
// simulate a battle
//
/** helper class that catches some calls for the simulated critter. */
final class SimulatedBattleHex extends BattleHex
{
SimulatedBattleHex(final HazardTerrain hazard)
{
super(4, 4); // 4,4: something in the middle
setTerrain(hazard);
}
/** fake, return ' ' nor now. TODO. */
@Override
public char getOppositeHexside(final int i)
{
return ' '; // plain hex side for now
}
}
/** helper class to simulate a battle of the creature in question against
* an other creature. especially distance and hazard must be simulated.
* very fragile class, i suppose. but it might be worth it.
*
* TODO this gets harder and harder to maintain the more typesafe the model gets.
* Figure out what it is really good for and solve the actual problem. Currently
* it even causes assertion errors since it passes nulls where nulls aren't allowed.
*
* @author Towi
*/
final class SimulatedCritter extends Creature
{
/** catch calls to "underlying" battle hex and proxy it to this. */
private SimulatedBattleHex hex;
/** @param creature to create a critter for
* @param hazard that stands in this hazard */
SimulatedCritter(final CreatureType creature,
final HazardTerrain hazard)
{
super(creature, new LegionServerSide("dummy", null, null, null,
null, null));
setNewHazardHex(hazard);
}
/** in hazard Plains. */
SimulatedCritter(final CreatureType creature)
{
this(creature, HazardTerrain.getDefaultTerrain());
}
/** create the simulated hex. */
public void setNewHazardHex(final HazardTerrain hazard)
{
hex = new SimulatedBattleHex(hazard);
}
// typically called on *OTHER*
public void setHexsideHazard(final HazardHexside hexside)
{
hex.setHexsideHazard(0, hexside);
}
/** power of this creature hitting target. */
public int getSimulatedDiceCount(final Creature target)
{
return battleStrikeSS.getDice(this, target, false);
}
/** skill of this creature hitting target. */
public int getSimulatedStrikeNr(final Creature target)
{
return battleStrikeSS.getStrikeNumber(this, target, false);
}
/** color of hex i stand on. */
public Color getHazardColor()
{
return hex.getTerrainColor();
}
public Color getHexsideColor()
{
Color color = Color.gray;
// See BattleHext.getTerrainColor()
HazardHexside hexsideHazard = hex.getHexsideHazard(0);
if (hexsideHazard == HazardHexside.RIVER)
{
// River is water like a lake
color = HTMLColor.skyBlue;
}
else if (hexsideHazard == HazardHexside.DUNE)
{
// Dunes are in the desert
color = Color.orange;
}
else if (hexsideHazard == HazardHexside.SLOPE)
{
// Slope are in the hills
color = HTMLColor.brown;
}
else if (hexsideHazard == HazardHexside.TOWER)
{
// Slope are in the hills
color = HTMLColor.dimGray;
}
else if (hexsideHazard == HazardHexside.CLIFF)
{
// Slope are in the hills
color = HTMLColor.darkRed;
}
return color;
}
//
// to help simulating
//
/** prox to simulated hex. */
@Override
public BattleHex getCurrentHex()
{
return hex;
}
}
}