package games.strategy.triplea.delegate;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Resource;
import games.strategy.engine.data.changefactory.ChangeFactory;
import games.strategy.engine.random.IRandomStats.DiceType;
import games.strategy.sound.SoundPath;
import games.strategy.triplea.Constants;
import games.strategy.triplea.MapSupport;
import games.strategy.triplea.attachments.AbstractConditionsAttachment;
import games.strategy.triplea.attachments.ICondition;
import games.strategy.triplea.attachments.UserActionAttachment;
import games.strategy.triplea.delegate.remote.IUserActionDelegate;
import games.strategy.triplea.formatter.MyFormatter;
import games.strategy.triplea.ui.UserActionText;
@MapSupport
public class UserActionDelegate extends BaseTripleADelegate implements IUserActionDelegate {
public UserActionDelegate() {}
/**
* Called before the delegate will run.
*/
@Override
public void start() {
super.start();
}
@Override
public void end() {
super.end();
resetAttempts();
}
@Override
public Serializable saveState() {
final UserActionExtendedDelegateState state = new UserActionExtendedDelegateState();
state.superState = super.saveState();
// state.m_testedConditions = m_testedConditions;
// add other variables to state here:
return state;
}
@Override
public void loadState(final Serializable state) {
final UserActionExtendedDelegateState s = (UserActionExtendedDelegateState) state;
super.loadState(s.superState);
}
@Override
public boolean delegateCurrentlyRequiresUserInput() {
return !getValidActions().isEmpty();
}
public HashMap<ICondition, Boolean> getTestedConditions() {
final HashSet<ICondition> allConditionsNeeded = AbstractConditionsAttachment.getAllConditionsRecursive(
new HashSet<>(UserActionAttachment.getUserActionAttachments(m_player)), null);
return AbstractConditionsAttachment.testAllConditionsRecursive(allConditionsNeeded, null, m_bridge);
}
@Override
public Collection<UserActionAttachment> getValidActions() {
final GameData data = m_bridge.getData();
final HashMap<ICondition, Boolean> testedConditions;
data.acquireReadLock();
try {
testedConditions = getTestedConditions();
} finally {
data.releaseReadLock();
}
return UserActionAttachment.getValidActions(m_player, testedConditions, data);
}
@Override
public void attemptAction(final UserActionAttachment actionChoice) {
if (actionChoice.canPerform(getTestedConditions())) {
if (checkEnoughMoney(actionChoice)) { // See if the player has got enough money to pay for the action
// Charge for attempting the action
chargeForAction(actionChoice);
// take one of the uses this round
actionChoice.useAttempt(getBridge());
if (actionRollSucceeds(actionChoice)) { // See if the action is successful
if (actionIsAccepted(actionChoice)) {
// activate the triggers
activateTriggers(actionChoice);
// notify the players
notifySuccess(actionChoice);
} else {
// notify the players of the failed attempt
notifyFailure(actionChoice);
}
} else {
// notify the players of the failed attempt
notifyFailure(actionChoice);
}
} else {
// notify the player he hasn't got enough money;
notifyMoney(actionChoice);
}
} else {
// notify the player the action isn't valid anymore (shouldn't happen)
notifyNoValidAction(actionChoice);
}
}
/**
* @param uaa
* The UserActionAttachment the player should be charged for.
* @return false if the player can't afford the action
*/
private boolean checkEnoughMoney(final UserActionAttachment uaa) {
final Resource PUs = getData().getResourceList().getResource(Constants.PUS);
final int cost = uaa.getCostPU();
final int has = m_bridge.getPlayerID().getResources().getQuantity(PUs);
return has >= cost;
}
/**
* Subtract money from the players wallet
*
* @param uaa
* the UserActionAttachment this the money is charged for.
*/
private void chargeForAction(final UserActionAttachment uaa) {
final Resource PUs = getData().getResourceList().getResource(Constants.PUS);
final int cost = uaa.getCostPU();
if (cost > 0) {
// don't notify user of spending money anymore
// notifyMoney(uaa, true);
final String transcriptText = m_bridge.getPlayerID().getName() + " spend " + cost + " PU on User Action: "
+ MyFormatter.attachmentNameToText(uaa.getName());
m_bridge.getHistoryWriter().startEvent(transcriptText);
final Change charge = ChangeFactory.changeResourcesChange(m_bridge.getPlayerID(), PUs, -cost);
m_bridge.addChange(charge);
} else {
final String transcriptText =
m_bridge.getPlayerID().getName() + " takes action: " + MyFormatter.attachmentNameToText(uaa.getName());
// we must start an event anyway
m_bridge.getHistoryWriter().startEvent(transcriptText);
}
}
/**
* @param uaa
* the action to check if it succeeds
* @return true if the action succeeds, usually because the die-roll succeeded.
*/
private boolean actionRollSucceeds(final UserActionAttachment uaa) {
final int hitTarget = uaa.getChanceToHit();
final int diceSides = uaa.getChanceDiceSides();
if (diceSides <= 0 || hitTarget >= diceSides) {
uaa.changeChanceDecrementOrIncrementOnSuccessOrFailure(m_bridge, true, true);
return true;
} else if (hitTarget <= 0) {
uaa.changeChanceDecrementOrIncrementOnSuccessOrFailure(m_bridge, false, true);
return false;
}
final int rollResult = m_bridge.getRandom(diceSides, m_player, DiceType.NONCOMBAT,
"Attempting the User Action: " + MyFormatter.attachmentNameToText(uaa.getName())) + 1;
final boolean success = rollResult <= hitTarget;
final String notificationMessage = "rolling (" + hitTarget + " out of " + diceSides + ") result: " + rollResult
+ " = " + (success ? "Success!" : "Failure!");
m_bridge.getHistoryWriter()
.addChildToEvent(MyFormatter.attachmentNameToText(uaa.getName()) + " : " + notificationMessage);
uaa.changeChanceDecrementOrIncrementOnSuccessOrFailure(m_bridge, success, true);
sendNotification(notificationMessage);
return success;
}
/**
* Get a list of players that should accept this action and then ask each
* player if it accepts this action.
*
* @param uaa
* the UserActionAttachment that should be accepted
*/
private boolean actionIsAccepted(final UserActionAttachment uaa) {
for (final PlayerID player : uaa.getActionAccept()) {
if (!(getRemotePlayer(player)).acceptAction(m_player,
UserActionText.getInstance().getAcceptanceQuestion(uaa.getText()), false)) {
return false;
}
}
return true;
}
/**
* Fire triggers.
*
* @param uaa
* the UserActionAttachment to activate triggers for
*/
private void activateTriggers(final UserActionAttachment uaa) {
UserActionAttachment.fireTriggers(uaa, getTestedConditions(), m_bridge);
}
/**
* Let all players involved in this action know the action was successful
*
* @param uaa
* the UserActionAttachment that just succeeded.
*/
private void notifySuccess(final UserActionAttachment uaa) {
// play a sound
getSoundChannel().playSoundForAll(SoundPath.CLIP_USER_ACTION_SUCCESSFUL, m_player);
sendNotification(UserActionText.getInstance().getNotificationSucccess(uaa.getText()));
notifyOtherPlayers(uaa, UserActionText.getInstance().getNotificationSuccessOthers(uaa.getText()));
}
/**
* Send a notification to the current player.
*
* @param text
* if NONE don't send a notification
*/
private void sendNotification(final String text) {
if (!"NONE".equals(text)) {
// "To " + m_player.getName() + ": " +
this.getRemotePlayer().reportMessage(text, text);
}
}
/**
* Send a notification to the other players involved in this action (all
* players except the player starting the action).
*/
private void notifyOtherPlayers(final UserActionAttachment uaa, final String notification) {
if (!"NONE".equals(notification)) {
// we can send it to just uaa.getOtherPlayers(), or we can send it to all players. both are good options.
final Collection<PlayerID> currentPlayer = new ArrayList<>();
currentPlayer.add(m_player);
final Collection<PlayerID> otherPlayers = getData().getPlayerList().getPlayers();
otherPlayers.removeAll(currentPlayer);
this.getDisplay().reportMessageToPlayers(otherPlayers, currentPlayer, notification, notification);
}
}
/**
* Let all players involved in this action know the action has failed.
*
* @param uaa
* the UserActionAttachment that just failed.
*/
private void notifyFailure(final UserActionAttachment uaa) {
// play a sound
getSoundChannel().playSoundForAll(SoundPath.CLIP_USER_ACTION_FAILURE, m_player);
final String transcriptText =
m_bridge.getPlayerID().getName() + " fails on action: " + MyFormatter.attachmentNameToText(uaa.getName());
m_bridge.getHistoryWriter().addChildToEvent(transcriptText);
sendNotification(UserActionText.getInstance().getNotificationFailure(uaa.getText()));
notifyOtherPlayers(uaa, UserActionText.getInstance().getNotificationFailureOthers(uaa.getText()));
}
/**
* Let the player know he is being charged for money or that he hasn't got
* enough money.
*
* @param uaa
* the UserActionAttachment the player is notified about
*
*/
private void notifyMoney(final UserActionAttachment uaa) {
sendNotification("You don't have enough money, you need " + uaa.getCostPU() + " PU's to perform this action");
}
/**
* Let the player know this action isn't valid anymore, this shouldn't
* happen as the player shouldn't get an option to push the button on
* non-valid actions.
*/
private void notifyNoValidAction(final UserActionAttachment uaa) {
sendNotification("This action isn't available anymore (this shouldn't happen!?!)");
}
/**
* Reset the attempts-counter for this action, so next round the player can
* try again for a number of attempts.
*/
private void resetAttempts() {
for (final UserActionAttachment uaa : UserActionAttachment.getUserActionAttachments(m_player)) {
uaa.resetAttempts(getBridge());
}
}
@Override
public Class<IUserActionDelegate> getRemoteType() {
return IUserActionDelegate.class;
}
}
class UserActionExtendedDelegateState implements Serializable {
private static final long serialVersionUID = -7521031770074984272L;
Serializable superState;
// add other variables here:
// public HashMap<ICondition, Boolean> m_testedConditions = null;
}