package net.sf.colossus.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;
import net.sf.colossus.common.Constants;
import net.sf.colossus.game.Legion;
import net.sf.colossus.game.Player;
import net.sf.colossus.variant.CreatureType;
/**
* Contains info about one event that revealed some interesting information,
* stored in EventViewer.
*
* @author Clemens Katzer
*/
public class RevealEvent
{
private static final Logger LOGGER = Logger.getLogger(RevealEvent.class
.getName());
private final int turnNumber;
private final Player player;
private int eventType;
private String markerId;
private int height;
private List<RevealedCreature> knownCreatures;
private RevealedCreature readyToDie = null;
// child legion or summoner (for split or summon events)
private String markerId2;
private int height2;
private final Legion legion1;
private final Legion legion2;
// for mulligan:
private int roll1;
private int roll2;
private String mulliganTitanBaseName; // for titan in place of solid marker
private boolean undone = false;
private int scale;
private JPanel p;
private String info;
// set for losing battle events, because if Titan killed the
// marker does already belong to slayer when we ask.
private Player realPlayer;
public final static int eventSplit = 0;
public final static int eventRecruit = 1;
public final static int eventSummon = 2;
public final static int eventTeleport = 3;
public final static int eventAcquire = 4;
public final static int eventWon = 5;
public final static int eventLost = 6;
public final static int eventTurnChange = 7;
public final static int eventPlayerChange = 8;
public final static int eventMulligan = 9;
public final static int eventMoveRoll = 10;
public final static int eventReinforce = 12;
public final static int eventExtraRoll = 13;
// Battle is only a temporary state, before it becomes Won or Lost
// ( = no filter / checkbox setting for that needed, so far at least...)
public final static int eventBattle = 11;
public final static int NUMBEROFEVENTS = 14;
private final static String eventSplitText = "Split";
private final static String eventRecruitText = "Recruit";
private final static String eventSummonText = "Summon";
private final static String eventTeleportText = "Teleport";
private final static String eventAcquireText = "Acquire";
private final static String eventWonText = "Winner";
private final static String eventLostText = "Lost";
private final static String eventTurnChangeText = "TurnChange";
private final static String eventPlayerChangeText = "PlayerChange";
private final static String eventMulliganText = "Mulligan";
private final static String eventMoveRollText = "Movement roll";
private final static String eventBattleText = "Battle";
private final static String eventReinforceText = "Reinforce";
private final static String eventExtraRollText = "Extra roll";
private static String[] eventTypeToString = { eventSplitText,
eventRecruitText, eventSummonText, eventTeleportText,
eventAcquireText, eventWonText, eventLostText, eventTurnChangeText,
eventPlayerChangeText, eventMulliganText, eventMoveRollText,
eventBattleText, eventReinforceText, eventExtraRollText };
/**
* TODO replace marker/height combos with Legion objects
* --Done.
* NOTE Can replace only for input, need to store marker and height
* from here on, because Legion content will change but we
* want to record the original state!
* @param legion1 TODO
* @param legion2 TODO
*/
public RevealEvent(int turnNumber, Player player, int eventType,
Legion legion1, List<RevealedCreature> knownCreatures, Legion legion2)
{
this.turnNumber = turnNumber;
// player in whose turn this event happens.
this.player = player;
this.eventType = eventType;
// affected legion; split: parent; summon: donor
this.legion1 = legion1;
this.markerId = legion1 != null ? legion1.getLongMarkerId() : null;
this.height = legion1 != null ? legion1.getHeight() : 0;
if (this.markerId == null && eventType != eventPlayerChange
&& eventType != eventTurnChange)
{
LOGGER.log(Level.SEVERE, "null marker for event "
+ getEventTypeText(eventType));
throw new IllegalArgumentException("Missing markerId");
}
this.knownCreatures = knownCreatures;
// next 2: child legion or summoner
this.legion2 = legion2;
this.markerId2 = legion2 != null ? legion2.getLongMarkerId() : null;
this.height2 = legion2 != null ? legion2.getHeight() : 0;
makeCreaturesTitanChangeSafe(knownCreatures);
}
// Turn or Player change
public RevealEvent(int turnNumber, Player player, int eventType)
{
this(turnNumber, player, eventType, null, null, null);
}
// Mulligan or Movement roll
public RevealEvent(int turnNumber, Player player, int eventType,
int roll1, int roll2)
{
this.turnNumber = turnNumber;
this.player = player;
this.eventType = eventType;
// Roll2 is used only for mulligan
this.roll1 = roll1;
this.roll2 = roll2;
this.legion1 = null;
this.legion2 = null;
this.mulliganTitanBaseName = player.getTitanBasename();
}
/*
* if there is a Titan in this legion, then nail down it's
* titanBaseName of the time when this happened.
* Called also after battle to update to new power after
* points were rewarded.
*/
private void makeCreaturesTitanChangeSafe(List<RevealedCreature> list)
{
if (list == null || list.isEmpty())
{
return;
}
Iterator<RevealedCreature> it = list.iterator();
while (it.hasNext())
{
RevealedCreature rc = it.next();
if (rc != null && rc.getPlainName() != null
&& rc.getPlainName().equals(Constants.titan))
{
Player player = (realPlayer != null ? realPlayer : legion1
.getPlayer());
if (player == null)
{
LOGGER.log(Level.SEVERE, "For making titan base name: "
+ "player is null!");
}
else
{
String tbName = player.getTitanBasename();
rc.setTitanBaseName(tbName);
}
}
}
}
// only for Engagement events, to set it afterwards to Winner or Loser
public void setEventType(int eventType)
{
this.eventType = eventType;
// if battle winner, re-trigger the setting titanbasename(s)
// because his power has changed.
if (eventType == RevealEvent.eventWon)
{
makeCreaturesTitanChangeSafe(knownCreatures);
}
}
public void setEventInfo(String info)
{
this.info = info;
}
public void setRealPlayer(Player realPlayer)
{
assert realPlayer != null;
this.realPlayer = realPlayer;
}
public void setUndone(boolean undone)
{
this.undone = undone;
}
public boolean wasUndone()
{
return this.undone;
}
public void setAllDead()
{
Iterator<RevealedCreature> it = this.knownCreatures.iterator();
while (it.hasNext())
{
RevealedCreature rc = it.next();
rc.setDead(true);
}
height = 0;
}
public int getAliveCount()
{
int alive = 0;
Iterator<RevealedCreature> it = this.knownCreatures.iterator();
while (it.hasNext())
{
RevealedCreature rc = it.next();
if (!rc.isDead())
{
alive++;
}
}
return alive;
}
public int getDeadCount()
{
int dead = 0;
Iterator<RevealedCreature> it = this.knownCreatures.iterator();
while (it.hasNext())
{
RevealedCreature rc = it.next();
if (rc.isDead())
{
dead++;
}
}
return dead;
}
// so far I don't know any event in which Titan would be added,
// thus we don't need to make them TitanSafe here.
public void addCreature(RevealedCreature rc)
{
knownCreatures.add(rc);
height++;
}
// creatures were revealed some while after event was created.
// E.g. engagements.
public void updateKnownCreatures(List<RevealedCreature> revealedCreatures)
{
this.knownCreatures = revealedCreatures;
makeCreaturesTitanChangeSafe(knownCreatures);
this.height = knownCreatures.size();
}
public void setCreatureDied(CreatureType type, int newHeight)
{
String name = type.getName();
if (readyToDie != null && readyToDie.getName().equals(name))
{
readyToDie = null;
return;
}
Iterator<RevealedCreature> it = this.knownCreatures.iterator();
boolean done = false;
while (!done && it.hasNext())
{
RevealedCreature rc = it.next();
if (rc.matches(name) && !rc.isDead())
{
rc.setDead(true);
done = true;
}
}
if (!done)
{
if (name.startsWith("Titan-"))
{
// never mind. Whole legion is already gone.
}
else
{
LOGGER.log(Level.WARNING, "got order to kill creature " + name
+ " in legionEvent " + this.toString()
+ " but no such alive creature found!! Contains: "
+ this.knownCreatures);
}
}
// client told us new accurate count how many are still alive.
height = newHeight;
}
// undo the summoning of the angel to this battle event legion
// (when angel was left off board).
public boolean removeSummonedCreature(int turnNumber, String name)
{
if (turnNumber != this.turnNumber)
{
LOGGER.log(Level.WARNING, "undoSummon for " + this.toString()
+ " -- wrong turn.");
return false;
}
Iterator<RevealedCreature> it = this.knownCreatures.iterator();
boolean done = false;
while (!done && it.hasNext())
{
RevealedCreature rc = it.next();
if (rc.matches(name) && rc.wasSummoned())
{
rc.setDead(true);
// remember it so that RemoveDeadBattleChits does not
// strikeout one of the original creatures
readyToDie = rc;
it.remove();
height--;
done = true;
}
}
return done;
}
// undo the summoning of the angel to this battle event legion
// (when angel was left off board).
public boolean removeReinforcedCreature(int turnNumber, String name)
{
if (turnNumber != this.turnNumber)
{
LOGGER.log(Level.WARNING,
"removeReinforcement for " + this.toString()
+ " -- wrong turn.");
return false;
}
Iterator<RevealedCreature> it = this.knownCreatures.iterator();
boolean done = false;
while (!done && it.hasNext())
{
RevealedCreature rc = it.next();
if (rc.matches(name) && rc.wasReinforced())
{
rc.setDead(true);
// remember it so that RemoveDeadBattleChits does not
// strikeout one of the original creatures
readyToDie = rc;
it.remove();
height--;
done = true;
}
}
return done;
}
public int getEventType()
{
return eventType;
}
public String getEventTypeText()
{
return eventTypeToString[eventType];
}
public static String getEventTypeText(int type)
{
return eventTypeToString[type];
}
public Legion getLegion1()
{
return legion1;
}
public Legion getLegion2()
{
return legion2;
}
/**
* Note that RevealEvents use (currently?) everywhere the long marker id
* in order to be able to handle re-colored captured markers properly.
* @return The markerId of first involved legion.
*/
public String getLongMarkerId()
{
return markerId;
}
/**
* Note that RevealEvents use (currently?) everywhere the long marker id
* in order to be able to handle re-colored captured markers properly.
* @return The markerId of 2nd involved legion.
*/
public String getLongMarkerId2()
{
return markerId2;
}
public int getHeight()
{
return height;
}
public int getTurn()
{
return turnNumber;
}
public Player getPlayer()
{
return player;
}
@Override
public String toString()
{
String msg = "<unknown event?>";
if (eventType == eventSplit)
{
msg = "Revealing event: \"" + getEventTypeText() + "\" (turn "
+ turnNumber + "):\n" + "- Legion with marker: " + markerId
+ " (now height: " + height + "\n" + "- Splitoff to marker: "
+ markerId2 + " (now height: " + height2 + "\n";
}
else if (eventType == eventSummon)
{
RevealedCreature rc = this.knownCreatures.get(0);
String summoned = rc.getName();
msg = "Revealing event: \"" + getEventTypeText() + "\":\n"
+ " Summonable \"" + summoned + "\" from " + markerId + "("
+ height + ") to " + markerId2 + "(" + height2 + ")";
}
else if (eventType == eventWon)
{
msg = "Revealing event: " + markerId + " won";
}
else if (eventType == eventLost)
{
msg = "Revealing event: " + markerId + " lost";
}
else if (eventType == eventBattle)
{
msg = "Revealing event: " + markerId + " battle starts";
}
else if (eventType == eventTurnChange)
{
msg = "Revealing event: Turn change, now player "
+ getPlayer().getName() + ", Turn " + getTurn();
}
else if (eventType == eventPlayerChange)
{
msg = "Revealing event: Player change, now player "
+ getPlayer().getName() + ", Turn " + getTurn();
}
else if (eventType == eventMulligan || eventType == eventExtraRoll)
{
String what = (eventType == eventMulligan ? "mulligan"
: "extra roll");
msg = "Revealing event: Player " + getPlayer().getName()
+ ", Turn " + getTurn() + " took " + what + ";" + " old="
+ roll1 + ", new=" + roll2;
}
else if (eventType == eventMoveRoll)
{
msg = "Revealing event: Player " + getPlayer().getName()
+ ", Turn " + getTurn() + " had movement roll: " + " old="
+ roll1;
}
else
{
StringBuilder msgBuf = new StringBuilder(1000);
msgBuf.append("Revealing event: \"" + getEventTypeText()
+ "\" for marker " + markerId + "\n");
Iterator<RevealedCreature> it = knownCreatures.iterator();
int i = 0;
while (it.hasNext())
{
i++;
RevealedCreature rc = it.next();
msgBuf.append(i + ". " + rc.toString() + "\n");
}
msgBuf.append(" => legion " + markerId + " now " + height
+ " creatures.");
msg = msgBuf.toString();
}
return msg;
}
private void addLabel(String text)
{
JLabel label = new JLabel(text);
label.setAlignmentX(Component.LEFT_ALIGNMENT);
p.add(label);
}
// Todo: paint height on top of marker possible?
private void addMarker(String markerId, int height)
{
if (markerId == null)
{
LOGGER.log(Level.SEVERE, "ERROR: markerId null, event type "
+ getEventTypeText() + " turn" + getTurn());
}
try
{
// Legion might not exist any more (destroyed in engagement)
// Legion legion = client.getLegion(markerId);
Marker marker = new Marker(null, scale, markerId);
marker.setAlignmentX(Component.LEFT_ALIGNMENT);
p.add(marker);
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, "new Chit for markerId " + markerId
+ ", event type " + getEventTypeText() + " turn" + getTurn()
+ " threw exception.", e);
}
addLabel("(" + height + ")");
}
// NOTE: this assumes that this event is for the player in whose
// turn this happens:
private Chit getSolidMarker()
{
Chit solidMarker;
// I would have liked to paint a solid marker with color of that
// player, instead of the Titan picture (or any individual marker),
// because this is for the "player as such", not related to any
// single marker or the Titan creature.
// But even if I had created BrSolid.gif (or even copied
// Br01.gif to that name), did compileVariants, and the gif image
// was listed in jar tfv usage, still I got "Couldn't get image"
// error and everything was hanging.
// So, for now we go with the Titan icon.
boolean tryUseSolidChit = false;
if (tryUseSolidChit)
{
try
{
String color = player.getShortColor();
// TODO does not work, perhaps I did that overlay stuff wrong?
solidMarker = new Chit(scale, color + "Solid", null);
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, "While trying to get chit: ", e);
// if solid marker does not exist for this color,
// use as fallback the Titan chit.
solidMarker = Chit.newCreatureChit(scale,
player.getTitanBasename());
}
}
else
{
// NOTE: this assumes that this event is for the player in whose
// turn this happens:
solidMarker = Chit.newCreatureChit(scale, mulliganTitanBaseName);
solidMarker.setAlignmentX(Component.LEFT_ALIGNMENT);
}
return solidMarker;
}
private void addCreatureWithInfoToPanel(RevealedCreature rc)
{
String info = null;
if (rc.wasSummoned())
{
info = "S:";
}
else if (rc.wasAcquired())
{
info = "A:";
}
else if (rc.wasRecruited())
{
info = "R:";
}
else if (rc.wasReinforced())
{
info = "Ri:";
}
if (info != null)
{
JLabel label = new JLabel(info);
label.setAlignmentX(Component.LEFT_ALIGNMENT);
p.add(label);
}
addCreatureToPanel(rc);
}
private void addCreatureToPanel(RevealedCreature rc)
{
Chit creature = rc.toChit(scale);
if (creature == null)
{
return;
}
creature.setAlignmentX(Component.LEFT_ALIGNMENT);
p.add(creature);
}
private JPanel infoEvent(String text)
{
JPanel p = new JPanel();
p.setBorder(new TitledBorder(""));
p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
p.setAlignmentX(Component.LEFT_ALIGNMENT);
JLabel label = new JLabel(text);
label.setAlignmentX(Component.LEFT_ALIGNMENT);
p.add(label);
return p;
}
/*
* Provides a complete panel for this event
*/
public JPanel toPanel()
{
this.scale = 2 * Scale.get();
if (eventType == eventTurnChange)
{
return infoEvent("Turn " + turnNumber + " starts");
}
if (eventType == eventPlayerChange)
{
return infoEvent("Turn " + turnNumber + ", player " + getPlayer());
}
JPanel p = new JPanel();
this.p = p;
p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
p.setAlignmentX(Component.LEFT_ALIGNMENT);
if (eventType == eventMulligan || eventType == eventMoveRoll
|| eventType == eventExtraRoll)
{
Chit solidMarker = getSolidMarker();
p.add(solidMarker);
p.add(Box.createRigidArea(new Dimension(5, 0)));
addLabel(getEventTypeText() + ": ");
Chit oldDie = new MovementDie(this.scale,
MovementDie.getDieImageName(roll1));
oldDie.setAlignmentX(Component.LEFT_ALIGNMENT);
p.add(oldDie);
if (eventType == eventMulligan || eventType == eventExtraRoll)
{
addLabel(" => ");
Chit newDie = new MovementDie(this.scale,
MovementDie.getDieImageName(roll2));
newDie.setAlignmentX(Component.LEFT_ALIGNMENT);
p.add(newDie);
}
return p;
}
int showHeight = height;
if (eventType == eventSplit)
{
showHeight = height + height2;
}
addMarker(markerId, showHeight);
p.add(Box.createRigidArea(new Dimension(5, 0)));
String eventTypeText = "";
if ((eventType == eventLost) && info != null && !info.equals(""))
{
eventTypeText = info;
if (info.equals(Constants.erMethodFlee))
{
eventTypeText = "Fled: ";
}
else if (info.equals(Constants.erMethodFight))
{
eventTypeText = "Destroyed: ";
}
else if (info.equals(Constants.erMethodConcede))
{
eventTypeText = "Conceded: ";
}
else if (info.equals(Constants.erMethodTimeLoss))
{
eventTypeText = "Time loss: ";
}
else if (info.equals(Constants.erMethodNegotiate))
{
eventTypeText = "Negotiated: ";
}
}
else
{
eventTypeText = getEventTypeText() + ": ";
}
if (wasUndone())
{
// two labels below each other, first syaing the event,
// seconds telling "undone"; both in grey color instead of black;
Box twoLabels = new Box(BoxLayout.Y_AXIS);
//twoLabels.setBorder(new TitledBorder(""));
// twoLabels.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
// twoLabels.setAlignmentX(Component.LEFT_ALIGNMENT);
JLabel label1 = new JLabel(eventTypeText);
label1.setAlignmentY(Component.TOP_ALIGNMENT);
label1.setForeground(Color.darkGray);
twoLabels.add(label1);
JLabel label2 = new JLabel("(undone)");
label2.setForeground(Color.darkGray);
label2.setAlignmentY(Component.TOP_ALIGNMENT);
twoLabels.add(label2);
p.add(twoLabels);
}
else
{
addLabel(eventTypeText);
}
if (eventType == eventSplit)
{
addLabel(" => ");
addMarker(markerId, height);
p.add(Box.createRigidArea(new Dimension(5, 0)));
addMarker(markerId2, height2);
}
else if (eventType == eventRecruit || eventType == eventReinforce)
{
Iterator<RevealedCreature> it = knownCreatures.iterator();
while (it.hasNext())
{
RevealedCreature rc = it.next();
if (rc.wasRecruited() || rc.wasReinforced())
{
addLabel(" => ");
}
addCreatureToPanel(rc);
}
}
else if (eventType == eventSummon)
{
Iterator<RevealedCreature> it = knownCreatures.iterator();
while (it.hasNext())
{
RevealedCreature rc = it.next();
addCreatureToPanel(rc);
}
addLabel(" to ");
addMarker(markerId2, height2);
}
else if (eventType == eventAcquire)
{
Iterator<RevealedCreature> it = knownCreatures.iterator();
while (it.hasNext())
{
RevealedCreature rc = it.next();
addCreatureToPanel(rc);
}
}
else if (knownCreatures != null)
{
Iterator<RevealedCreature> it = knownCreatures.iterator();
while (it.hasNext())
{
RevealedCreature rc = it.next();
addCreatureWithInfoToPanel(rc);
}
if (eventType == eventWon && knownCreatures.size() < height)
{
int diff = height - knownCreatures.size();
if (knownCreatures.isEmpty())
{
addLabel(diff + " creatures (not revealed)");
}
else
{
addLabel(" + " + diff + " more (not revealed)");
}
}
}
return p;
}
}