package com.iambookmaster.client.player; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import com.google.gwt.core.client.JavaScriptObject; import com.iambookmaster.client.beans.AbstractParameter; import com.iambookmaster.client.beans.Alchemy; import com.iambookmaster.client.beans.Battle; import com.iambookmaster.client.beans.Battle.BattleRound; import com.iambookmaster.client.beans.Modificator; import com.iambookmaster.client.beans.NPC; import com.iambookmaster.client.beans.NPCParams; import com.iambookmaster.client.beans.ObjectBean; import com.iambookmaster.client.beans.Paragraph; import com.iambookmaster.client.beans.ParagraphConnection; import com.iambookmaster.client.beans.Parameter; import com.iambookmaster.client.beans.ParametersCalculation; import com.iambookmaster.client.beans.Picture; import com.iambookmaster.client.beans.Sound; import com.iambookmaster.client.common.Base64Coder; import com.iambookmaster.client.common.JSONBuilder; import com.iambookmaster.client.common.JSONParser; import com.iambookmaster.client.exceptions.JSONException; import com.iambookmaster.client.locale.AppConstants; import com.iambookmaster.client.locale.AppLocale; import com.iambookmaster.client.locale.AppMessages; import com.iambookmaster.client.model.Model; import com.iambookmaster.client.model.Model.FullParagraphDescriptonBuilder; import com.iambookmaster.client.model.ParagraphParsingHandler; public class PlayerState { private static final int EVENT_ADD_OBJECT = 0; private static final int EVENT_REMOVE_OBJECT = 1; private static final int EVENT_USE_OBJECT = 2; private static final int EVENT_RESET = 3; private static final int EVENT_FINISH = 4; private static final int EVENT_LOST_OBJECT = 5; private static final int EVENT_CHANGE_PARAMETER = 6; private static final int EVENT_CHANGE_MODIFICATOR = 7; private static final int EVENT_BATTLE = 8; private static final int EVENT_ENEMY = 9; private static final int EVENT_ENABLE = 10; private static final int EVENT_DISABLE = 11; private static final String FIELD_PARAGRAPH_ID = "a"; private static final String FIELD_OBJECT_ID = "b"; private static final String FIELD_BAG = "c"; private static final String FIELD_GAME_ID = "d"; private static final String FIELD_AUDIO = "e"; private static final String FIELD_IMAGES = "f"; private static final String FIELD_BACKGROUND = "i"; private static final String FIELD_BACKGROUND_SOUND = "k"; private static final String FIELD_PARAMETERS = "l"; private static final String FIELD_PARAMETER_ID = "m"; private static final String FIELD_PARAMETER_VALUE = "n"; private static final String FIELD_MODIFICATORS = "o"; private static final String FIELD_FIGHTERS = "p"; private static final String FIELD_ALCEMY_WAS_USED = "r"; private static final String FIELD_BATTLE_LIMITS = "s"; private static final String FIELD_OLD_STATE = "t"; private static final String FIELD_NPC = "u"; private static final String FIELD_BATTLE_ROUND = "x"; private static final String FIELD_METADATA = "j"; private LinkedHashSet<ObjectBean> bag; private FullParagraphDescriptonBuilder paragraphDescriptonBuilder; private ArrayList<PlayerStateListener> listeners; private Paragraph currentParagraph; private Model model; private boolean allowAudio; private boolean allowImages; private Picture background; private Sound backgroundSound; private LinkedHashMap<Parameter, Integer> parameters; private HashSet<Modificator> modificators; private boolean finished; private AppConstants appConstants; private AppMessages appMessages; private ArrayList<FighterData> fighters; private LinkedHashMap<Parameter, Integer> heroBatleLimits; private boolean alchemyWasUsed; private HashMap<ParagraphConnection,Boolean> vitalConnections; private HashMap<ParagraphConnection,Integer> escapeBattleConnections; private Battle currentBattle; private int currentBattleRound; private FighterData currentBattleTarget; private PlayerStateMetadata metadata; private final String[] history = new String[5]; public PlayerStateMetadata getMetadata() { if (metadata==null) { metadata = new PlayerStateMetadata(); } return metadata; } public void setMetadata(PlayerStateMetadata metadata) { this.metadata = metadata; } public PlayerState(Model mod,AppConstants constants,AppMessages messages) { model = mod; this.appConstants = constants; this.appMessages = messages; listeners = new ArrayList<PlayerStateListener>(); bag=new LinkedHashSet<ObjectBean>(); paragraphDescriptonBuilder = model.getFullParagraphDescriptonBuilder(); paragraphDescriptonBuilder.setObjects(bag); paragraphDescriptonBuilder.setPlayerMode(true); parameters = new LinkedHashMap<Parameter, Integer>(); modificators = new HashSet<Modificator>(); fighters = new ArrayList<PlayerState.FighterData>(); heroBatleLimits = new LinkedHashMap<Parameter, Integer>(); vitalConnections = new HashMap<ParagraphConnection,Boolean>(); escapeBattleConnections = new HashMap<ParagraphConnection, Integer>(); } public void reset() { for (int i = 0; i < history.length; i++) { history[i] = null; } bag.clear(); parameters.clear(); modificators.clear(); alchemyWasUsed = false; ArrayList<AbstractParameter> list = model.getParameters(); //initialize all pre-defined parameters for (AbstractParameter abstractParameter : list) { if (abstractParameter instanceof Parameter) { Parameter parameter = (Parameter) abstractParameter; if (parameter.isHeroHasInitialValue()) { //has initial value parameters.put(parameter, parameter.getHeroInitialValue().calculate()); } } } //initialize all parameters with limits for (AbstractParameter abstractParameter : list) { if (abstractParameter instanceof Parameter) { Parameter parameter = (Parameter) abstractParameter; if (parameter.getLimit() != null && parameters.containsKey(parameter)==false && parameters.containsKey(parameter.getLimit())) { //limit is initialized parameters.put(parameter, parameters.get(parameter.getLimit())); } } } //check that limits are after his parameters HashMap<Parameter,Integer> limits = null; HashSet<Parameter> used = new HashSet<Parameter>(); for (Parameter parameter : parameters.keySet()) { if (parameter.getLimit() != null && used.contains(parameter.getLimit())) { //limit before main parameter if (limits == null) { limits = new HashMap<Parameter,Integer>(); } limits.put(parameter.getLimit(),parameters.get(parameter.getLimit())); } used.add(parameter); } if (limits != null) { //reorder this parameters, remove first for (Parameter parameter : limits.keySet()) { parameters.remove(parameter); } //and add again to the tail for (Parameter parameter : limits.keySet()) { parameters.put(parameter, limits.get(parameter)); } limits.clear(); } //check that all limits are initialized for (Parameter parameter : parameters.keySet()) { if (parameter.getLimit() != null && parameters.containsKey(parameter.getLimit())==false) { //initialize limit for this parameter if (limits==null) { limits = new HashMap<Parameter, Integer>(); } limits.put(parameter.getLimit(), parameters.get(parameter)); } } if (limits != null && limits.size()>0) { for (Parameter parameter : limits.keySet()) { parameters.put(parameter, limits.get(parameter)); } } background = null; fireEvent(EVENT_RESET); currentParagraph = model.getStartParagraph(); finished = false; } public void setCurrentParagraph(Paragraph currentParagraph) { this.currentParagraph = currentParagraph; } public void apply(Paragraph paragraph) { alchemyWasUsed = false; currentParagraph = paragraph; for (ObjectBean bean : paragraph.getGotObjects()) { if (bag.contains(bean)==false) { bag.add(bean); fireEvent(EVENT_ADD_OBJECT,bean); } } for (ObjectBean bean : paragraph.getLostObjects()) { if (bag.contains(bean)) { bag.remove(bean); fireEvent(EVENT_LOST_OBJECT,bean); } } //update parameters LinkedHashMap<Parameter, ParametersCalculation> changes = paragraph.getChangeParameters(); if (changes != null && changes.size()>0) { for (Parameter parameter : changes.keySet()) { ParametersCalculation calculation = changes.get(parameter); int value = calculateNewValue(calculation,parameter); fireEvent(EVENT_CHANGE_PARAMETER, parameter, value); if (parameter.isVital() && value<=0) { //death fireEvent(EVENT_FINISH); } } } //update modificators LinkedHashMap<Modificator, Boolean> mods = paragraph.getChangeModificators(); if (mods != null && mods.size()>0) { for (Modificator modificator : mods.keySet()) { boolean value = mods.get(modificator); if (value) { modificators.add(modificator); } else { modificators.remove(modificator); } fireEvent(EVENT_CHANGE_MODIFICATOR, modificator, value ? 1:0); } } if (paragraph.isFail() || paragraph.isSuccess()) { finished = true; fireEvent(EVENT_FINISH); } //battle if (currentBattle !=null) { fighters.clear(); heroBatleLimits.clear(); vitalConnections.clear(); escapeBattleConnections.clear(); } currentBattleTarget = null; if (paragraph.getBattle() == null) { currentBattle = null; currentBattleRound=0; } else { currentBattleRound=1; currentBattle = paragraph.getBattle(); //start battle for ( NPCParams npc : paragraph.getEnemies()) { fighters.add(new FighterData(npc.getValues(), paragraph.getBattle(), npc)); } selectTarget(); updateBattleConnections(paragraph); //save limits ArrayList<AbstractParameter> listAlchemy = model.getParameters(); for (AbstractParameter parameter : listAlchemy) { if (parameter instanceof Alchemy) { Alchemy alchemy = (Alchemy) parameter; if (alchemy.getBattleLimit() == null || heroBatleLimits.containsKey(alchemy.getBattleLimit())) { continue; } Integer value = parameters.get(alchemy.getBattleLimit()); if (value != null && value>0) { heroBatleLimits.put(alchemy.getBattleLimit(),value); } } } // heroBatleLimits = new FighterData(parameters, paragraph.getBattle(), null); fireEvent(EVENT_BATTLE, paragraph.getBattle(), 1); for ( NPCParams npc : paragraph.getEnemies()) { fireEvent(EVENT_ENEMY, npc.getNpc(), 1); } } } private int calculateNewValue(ParametersCalculation calculation,Parameter parameter) { boolean hasValue = parameters.containsKey(parameter); int value = calculation.calculate(parameters); if (!parameter.isNegative() && value<0) { //cannot be negative value=0; } if (model.getSettings().isOverflowControl() ^ calculation.isOverflowControl()) { if (parameter.getLimit() != null && parameters.containsKey(parameter.getLimit())) { //control limits int max = parameters.get(parameter.getLimit()); int val = hasValue ? parameters.get(parameter) : 0; if (val>max) { if (value>val) { //it was a useless action value = val; } } else if (value>max) { value=max; } } } if (hasValue || parameter.getLimit() == null || parameters.containsKey(parameter.getLimit())==false) { parameters.put(parameter, value); } else { //initial insert, has max parameter int max = parameters.get(parameter.getLimit()); parameters.remove(parameter.getLimit()); parameters.put(parameter, value); parameters.put(parameter.getLimit(), max); } return value; } private void updateBattleConnections(Paragraph paragraph) { List<ParagraphConnection> list = model.getOutputParagraphConnections(paragraph); vitalConnections.clear(); escapeBattleConnections.clear(); for (ParagraphConnection connection : list) { if (connection.getType()==ParagraphConnection.TYPE_VITAL_LESS || connection.getType()==ParagraphConnection.TYPE_ENEMY_VITAL_LESS) { vitalConnections.put(connection,Boolean.FALSE); } if (connection.getType()==ParagraphConnection.TYPE_BATTLE_ROUND_MORE) { escapeBattleConnections.put(connection,connection.getParameterValue().getConstant()); } } } public LinkedHashMap<Parameter, Integer> getHeroBatleLimits() { return heroBatleLimits; } private void fireEvent(int event, AbstractParameter parameter,int value) { for (int i = 0; i < listeners.size(); i++) { PlayerStateListener listener = listeners.get(i); switch (event) { case EVENT_CHANGE_PARAMETER: listener.changeParameter((Parameter)parameter,value); break; case EVENT_CHANGE_MODIFICATOR: listener.changeModificator((Modificator)parameter,value==1); break; case EVENT_BATTLE: if (value==0) { //end of battle - clean target currentBattleTarget=null; } listener.battle((Battle)parameter,value==1); break; case EVENT_ENEMY: listener.enemy((NPC)parameter,value==1); break; default: throw new IllegalArgumentException("Unsupported Enent "+event); } } } private void fireEvent(int event, ParagraphConnection connection) { for (int i = 0; i < listeners.size(); i++) { PlayerStateListener listener = listeners.get(i); switch (event) { case EVENT_ENABLE: listener.enableConnection(connection); break; case EVENT_DISABLE: listener.disableConnection(connection); break; } } } private void fireEvent(int event) { finished = true; for (int i = 0; i < listeners.size(); i++) { PlayerStateListener listener = listeners.get(i); switch (event) { case EVENT_RESET: listener.reset(); break; case EVENT_FINISH: listener.finish(); break; } } } private void fireEvent(int event, ObjectBean object,boolean success) { for (int i = 0; i < listeners.size(); i++) { PlayerStateListener listener = listeners.get(i); switch (event) { case EVENT_USE_OBJECT: listener.useObject(object,success); break; default: throw new IllegalArgumentException("Unsupported Enent "+event); } } } private void fireEvent(int event, ObjectBean object) { for (int i = 0; i < listeners.size(); i++) { PlayerStateListener listener = listeners.get(i); switch (event) { case EVENT_ADD_OBJECT: listener.addObject(object); break; case EVENT_LOST_OBJECT: listener.lostObject(object); break; case EVENT_REMOVE_OBJECT: listener.removeObject(object); break; default: throw new IllegalArgumentException("Unsupported Enent "+event); } } } public String getFullParagraphDescripton(Paragraph paragraph, ArrayList<Paragraph> ids, ArrayList<String> errors, ParagraphParsingHandler parsingHandler) { paragraphDescriptonBuilder.setParagraphParsingHandler(parsingHandler); paragraphDescriptonBuilder.setParameters(parameters); paragraphDescriptonBuilder.setModificators(modificators); return paragraphDescriptonBuilder.getFullParagraphDescripton(paragraph, ids,errors,null); } public String getFullParagraphDescripton(Paragraph paragraph, ArrayList<Paragraph> ids, ParagraphParsingHandler parsingHandler, ArrayList<ParagraphConnection> connections) { paragraphDescriptonBuilder.setParagraphParsingHandler(parsingHandler); paragraphDescriptonBuilder.setParameters(parameters); paragraphDescriptonBuilder.setModificators(modificators); return paragraphDescriptonBuilder.getFullParagraphDescripton(paragraph, ids,null,null,connections); } public boolean selectObject(ObjectBean object) { if (isFinished()) { return false; } if (currentParagraph != null) { ArrayList<ParagraphConnection> connections = model.getOutputParagraphConnections(currentParagraph); for (int i = 0; i < connections.size(); i++) { ParagraphConnection connection = connections.get(i); if (connection.getObject()==object && connection.getStrictness() != ParagraphConnection.STRICTNESS_MUST_NOT) { //has output connection from here currentParagraph = connection.getTo(); fireEvent(EVENT_USE_OBJECT,object,true); return true; } } } fireEvent(EVENT_USE_OBJECT,object,false); return false; } public void addPlayerStateListener(PlayerStateListener listener) { if (listeners.contains(listener)==false) { listeners.add(listener); } } public void removePlayerStateListener(PlayerStateListener listener) { if (listeners.contains(listener)) { listeners.remove(listener); } } public Iterator<ObjectBean> getObjectIterator() { return bag.iterator(); } public boolean isBagEmpty() { return bag.isEmpty(); } /* (non-Javadoc) * @see com.iambookmaster.client.player.PlayerGameState#saveState(boolean) */ public String saveState(boolean addToHistory) { JSONBuilder json = _toJSON(); String old = null; //find the oldest state for (int i = 0; i < history.length; i++) { if (history[i] != null) { old = history[i]; break; } } if (addToHistory) { //scroll history for (int i = 1; i < history.length; i++) { history[i-1] = history[i]; } //save the latest state history[history.length-1] = json.toString(); } if (old != null) { //add the oldest state json.field(FIELD_OLD_STATE,Base64Coder.encodeString(old)); } return Base64Coder.encodeString(json.toString()); } public String toJSON() { return _toJSON().toString(); } private JSONBuilder _toJSON() { JSONBuilder builder = JSONBuilder.getStartInstance(); builder.newRow(); builder.field(FIELD_PARAGRAPH_ID, currentParagraph.getId()); //bag JSONBuilder builderBag = builder.getInstance(); Iterator<ObjectBean> iterator = bag.iterator(); while (iterator.hasNext()) { ObjectBean bean = iterator.next(); builderBag.newRow(); builderBag.field(FIELD_OBJECT_ID,bean.getId()); } builder.childArray(FIELD_BAG, builderBag); if (parameters.size()>0) { //parameters builderBag = builder.getInstance(); for (Parameter parameter : parameters.keySet()) { int value = parameters.get(parameter); builderBag.newRow(); builderBag.field(FIELD_PARAMETER_ID,parameter.getId()); builderBag.field(FIELD_PARAMETER_VALUE,value); } builder.childArray(FIELD_PARAMETERS, builderBag); } if (modificators.size()>0) { StringBuffer buffer = new StringBuffer(); for (Modificator modificator : modificators) { if (buffer.length()>0) { buffer.append(','); } buffer.append(modificator.getId()); } builder.field(FIELD_MODIFICATORS, buffer.toString()); } builder.field(FIELD_GAME_ID, model.getGameId()); builder.field(FIELD_AUDIO, allowAudio ? 1:0); builder.field(FIELD_IMAGES, allowImages ? 1:0); if (background != null) { builder.field(FIELD_BACKGROUND, background.getId()); } if (backgroundSound != null) { builder.field(FIELD_BACKGROUND_SOUND, backgroundSound.getId()); } if (currentBattle != null && !fighters.isEmpty()) { //battle in the process builderBag = builder.getInstance(); for (FighterData data : fighters) { builderBag.newRow(); JSONBuilder jsonBuilder = builder.getInstance(); data.npc.toJSON(jsonBuilder, Model.EXPORT_PLAY); builderBag.child(FIELD_NPC,jsonBuilder); if (currentBattleTarget==data) { builderBag.field(FIELD_PARAMETER_VALUE,1); } JSONBuilder builderSub = builder.getInstance(); for (Parameter parameter : data.parameters.keySet()) { int value = data.parameters.get(parameter); builderSub.newRow(); builderSub.field(FIELD_PARAMETER_ID, parameter.getId()); builderSub.field(FIELD_PARAMETER_VALUE,value); } builderBag.childArray(FIELD_PARAMETERS,builderSub); } builder.childArray(FIELD_FIGHTERS,builderBag); builder.field(FIELD_BATTLE_ROUND,currentBattleRound); if (alchemyWasUsed) { builder.field(FIELD_ALCEMY_WAS_USED,1); } if (heroBatleLimits.isEmpty()==false) { builderBag = builder.getInstance(); for (Parameter parameter : heroBatleLimits.keySet()) { int value = heroBatleLimits.get(parameter); builderBag.newRow(); builderBag.field(FIELD_PARAMETER_ID, parameter.getId()); builderBag.field(FIELD_PARAMETER_VALUE,value); } builderBag.childArray(FIELD_BATTLE_LIMITS,builderBag); } } if (metadata != null) { builderBag = builder.getInstance(); metadata.toJSON(builderBag); builder.child(FIELD_METADATA,builderBag); } return builder; } public void fromJS(JavaScriptObject data) { JSONParser parser = JSONParser.getInstance(); String gameId = parser.propertyString(data, FIELD_GAME_ID); if (model.getGameId().equals(gameId)==false) { throw new IllegalArgumentException(AppLocale.getAppConstants().playerDifferentVersionsOfGame()); } String id = parser.propertyString(data, FIELD_PARAGRAPH_ID); Paragraph paragraph = model.getParagrapByID(id); if (paragraph==null) { throw new IllegalArgumentException("Error code 1"); } //bag Object bg = parser.property(data, FIELD_BAG); int len = parser.length(bg); LinkedHashSet<ObjectBean> bag = new LinkedHashSet<ObjectBean>(); for (int i = 0; i < len; i++) { id = parser.propertyString(parser.getRow(bg, i),FIELD_OBJECT_ID); ObjectBean bean = model.getObjectById(id); if (bean==null) { throw new IllegalArgumentException("Error code 2"); } bag.add(bean); } LinkedHashMap<Parameter, Integer> parameters = new LinkedHashMap<Parameter, Integer>(); HashSet<Modificator> modificators = new HashSet<Modificator>(); HashMap<String,AbstractParameter> params = new HashMap<String, AbstractParameter>(model.getParameters().size()); if (model.getParameters().size()>0) { ArrayList<AbstractParameter> list = model.getParameters(); for (AbstractParameter abstractParameter : list) { params.put(abstractParameter.getId(), abstractParameter); } //parameters bg = parser.propertyNoCheck(data, FIELD_PARAMETERS); if (bg != null) { len = parser.length(bg); for (int i = 0; i < len; i++) { Object row = parser.getRow(bg, i); id = parser.propertyString(row,FIELD_PARAMETER_ID); AbstractParameter abstractParameter = params.get(id); if (abstractParameter instanceof Parameter) { Parameter parameter = (Parameter) abstractParameter; int value = parser.propertyInt(row, FIELD_PARAMETER_VALUE); parameters.put(parameter, value); } else { throw new IllegalArgumentException("Error code 5"); } } } //modificators id =parser.propertyNoCheckString(data, FIELD_MODIFICATORS); if (id != null) { String[] mods = id.split(","); for (String key : mods) { AbstractParameter abstractParameter = params.get(key); if (abstractParameter instanceof Modificator) { modificators.add((Modificator) abstractParameter); } else { throw new IllegalArgumentException("Error code 6"); } } } } //Background picture Picture picture=null; id = parser.propertyNoCheckString(data, FIELD_BACKGROUND); if (id != null) { picture = model.getPictureByID(id); if (picture==null) { throw new IllegalArgumentException("Error code 3"); } } //Background sound Sound sound=null; id = parser.propertyNoCheckString(data, FIELD_BACKGROUND_SOUND); if (id != null) { sound = model.getSoundByID(id); if (sound==null) { throw new IllegalArgumentException("Error code 4"); } } bg = parser.property(data, FIELD_METADATA); PlayerStateMetadata metadata; if (bg == null) { metadata = null; } else { metadata = new PlayerStateMetadata(); metadata.fromJS(parser,bg); } //battle Battle battle = null; bg = parser.property(data, FIELD_FIGHTERS); FighterData target = null; ArrayList<FighterData> fighters=null; currentBattleRound = 0; if (bg != null && paragraph.getBattle() != null) { //restore battle currentBattleRound = parser.propertyNoCheckInt(data, FIELD_BATTLE_ROUND); len = parser.length(bg); battle = paragraph.getBattle(); fighters = new ArrayList<PlayerState.FighterData>(len); for (int i = 0; i < len; i++) { Object row = parser.getRow(bg, i); NPCParams npc; try { npc = NPCParams.fromJS(parser.propertyNoCheck(row, FIELD_NPC), parser, params); } catch (JSONException e) { throw new IllegalArgumentException(e.getMessage()); } Object prs = parser.property(row,FIELD_PARAMETERS); int m = parser.length(prs); LinkedHashMap<Parameter, Integer> values = new LinkedHashMap<Parameter, Integer>(m); for (int j = 0; j < m; j++) { Object pair = parser.getRow(prs, j); id = parser.propertyString(pair,FIELD_PARAMETER_ID); AbstractParameter abstractParameter = params.get(id);; if (abstractParameter instanceof Parameter) { Parameter parameter = (Parameter) abstractParameter; int value = parser.propertyInt(pair, FIELD_PARAMETER_VALUE); values.put(parameter, value); } else { throw new IllegalArgumentException("Error code 8"); } } FighterData fighter = new FighterData(values,paragraph.getBattle(),npc); if (parser.propertyNoCheckInt(row,FIELD_PARAMETER_VALUE)>0) { target = fighter; } fighters.add(fighter); } } LinkedHashMap<Parameter, Integer> heroBatleLimits=null; bg = parser.property(data, FIELD_BATTLE_LIMITS); if (bg != null) { int m = parser.length(bg); heroBatleLimits = new LinkedHashMap<Parameter, Integer>(m); for (int j = 0; j < m; j++) { Object pair = parser.getRow(bg, j); id = parser.propertyString(pair,FIELD_PARAMETER_ID); AbstractParameter abstractParameter = params.get(id);; if (abstractParameter instanceof Parameter) { Parameter parameter = (Parameter) abstractParameter; int value = parser.propertyInt(pair, FIELD_PARAMETER_VALUE); heroBatleLimits.put(parameter, value); } else { throw new IllegalArgumentException("Error code 9"); } } } //success this.metadata = metadata; currentBattle = battle; this.fighters.clear(); if (battle != null) { this.fighters.addAll(fighters); alchemyWasUsed = parser.propertyNoCheckInt(data, FIELD_ALCEMY_WAS_USED)>0; if (heroBatleLimits != null) { this.heroBatleLimits = heroBatleLimits; } updateBattleConnections(paragraph); } background = picture; backgroundSound = sound; allowAudio = (parser.propertyInt(data,FIELD_AUDIO)>0); allowImages = (parser.propertyInt(data,FIELD_IMAGES)>0); this.bag = bag; paragraphDescriptonBuilder.setObjects(bag); this.currentParagraph = paragraph; this.parameters = parameters; this.modificators = modificators; fireEvent(EVENT_RESET); if (currentBattle != null) { //start battle currentBattleTarget = target; fireEvent(EVENT_BATTLE,battle,1); for ( FighterData npc : fighters) { fireEvent(EVENT_ENEMY, npc.getNpc().getNpc(), 1); } selectTarget(); } for (int i = 0; i < history.length; i++) { history[i] = null; } String old = parser.propertyNoCheckString(data, FIELD_OLD_STATE); if (old != null) { //there is an old state history[history.length-1] = Base64Coder.decodeString(old); } } /* (non-Javadoc) * @see com.iambookmaster.client.player.PlayerGameState#restoreState(java.lang.String) */ public void restoreState(String data) { String json = Base64Coder.decodeString(data); reset(); fromJS(JSONParser.eval(json)); finished = false; if (currentParagraph != null) { if (currentParagraph.isFail() || currentParagraph.isSuccess()) { finished = true; } } } public Paragraph getCurrentParagraph() { return currentParagraph; } public boolean isAllowAudio() { return allowAudio; } public void setAllowAudio(boolean allowAudio) { this.allowAudio = allowAudio; } public boolean isAllowImages() { return allowImages; } public void setAllowImages(boolean allowImages) { this.allowImages = allowImages; } public Picture getBackground() { return background; } public void setBackground(Picture background) { this.background = background; } public void setBackgroundSound(Sound sound) { backgroundSound = sound; } public Sound getBackgroundSound() { return backgroundSound; } public LinkedHashMap<Parameter, Integer> getParameters() { return parameters; } public HashSet<Modificator> getModificators() { return modificators; } public void heroIsDeadInBattle() { fireEvent(EVENT_FINISH); } public void heroWonBattle() { fireEvent(EVENT_BATTLE, currentBattle,0); } public void update(Parameter parameter, int value) { parameters.put(parameter, value); fireEvent(EVENT_CHANGE_PARAMETER, parameter, value); } public boolean meetsCondition(Alchemy alchemy,boolean battle) { if (currentParagraph.getAlchemy() != null && currentParagraph.getAlchemy().containsKey(alchemy)) { boolean value = currentParagraph.getAlchemy().get(alchemy); if (value==false) { //disable it return false; } } else if (alchemy.isOnDemand()) { //not available return false; } if (battle) { if (alchemy.getPlace()==Alchemy.PLACE_PEACE){ //available in peaceful time only return false; } if (alchemy.getBattleLimit() != null) { //battle and Alchemy has battle limit Integer value = heroBatleLimits.get(alchemy.getBattleLimit()); if (value == null || value==0) { return false; } } } else if (alchemy.getPlace()==Alchemy.PLACE_BATTLE){ //available in battle only return false; } if (parameters.containsKey(alchemy.getFrom())==false) { return false; } int value = parameters.get(alchemy.getFrom()); if (value <alchemy.getFromValue()) { return false; } if (alchemy.isWeapon()==false && alchemy.getTo().getLimit() != null && (model.getSettings().isOverflowControl() ^ alchemy.isOverflowControl())) { //overflow control for peaceful alchemy if (parameters.containsKey(alchemy.getTo().getLimit())==false) { //limit is not set at all return false; } value = parameters.containsKey(alchemy.getTo()) ? parameters.get(alchemy.getTo()) : 0; int max = parameters.get(alchemy.getTo().getLimit()); if (value>=max) { //no reason to use this alchemy now return false; } } //can be used return true; } /** * Check that the connection is visible * @param connection * @return */ public boolean alwaysVisible(ParagraphConnection connection) { if (connection.isConditional()==false) { return true; } else if (connection.getType()==ParagraphConnection.TYPE_NORMAL) { return model.getSettings().isHiddenUsingObjects()==connection.isReverseHiddenUsage(); } else { return model.getSettings().isHideNonMatchedParameterConnections()==connection.isReverseHiddenUsage(); } } public boolean meetsCondition(ParagraphConnection connection) { boolean xor = connection.getStrictness()==ParagraphConnection.STRICTNESS_MUST_NOT; if (currentBattle != null && isBattleActive()) { //battle mode switch (connection.getType()) { case ParagraphConnection.TYPE_ENEMY_VITAL_LESS: case ParagraphConnection.TYPE_VITAL_LESS: if (currentBattleTarget != null) { if (connection.getStrictness()==ParagraphConnection.STRICTNESS_MUST_NOT) { return Boolean.FALSE.equals(vitalConnections.get(connection)); } else { return Boolean.TRUE.equals(vitalConnections.get(connection)); } } case ParagraphConnection.TYPE_BATTLE_ROUND_MORE: Integer res = escapeBattleConnections.get(connection); if (res != null && res.intValue() <= currentBattleRound) { if (currentBattleTarget == null) { //battle end, only must-go conditions are actual return connection.getStrictness()==ParagraphConnection.STRICTNESS_MUST; } else { //in battle return connection.getStrictness()!=ParagraphConnection.STRICTNESS_MUST_NOT; } } } return false; } else { //peace mode switch (connection.getType()) { case ParagraphConnection.TYPE_NORMAL: return (connection.getObject()==null || bag.contains(connection.getObject())) ^ xor; case ParagraphConnection.TYPE_MODIFICATOR: if (connection.getModificator()==null) { return xor; } else { return modificators.contains(connection.getModificator()) ^ xor; } case ParagraphConnection.TYPE_NO_MODIFICATOR: if (connection.getModificator()==null) { return xor; } else { return (modificators.contains(connection.getModificator())==false) ^ xor; } case ParagraphConnection.TYPE_PARAMETER_LESS: if (connection.getParameter()==null|| parameters.containsKey(connection.getParameter())==false) { return xor; } else { return (parameters.get(connection.getParameter()) < connection.getParameterValue().calculate()) ^ xor; } case ParagraphConnection.TYPE_PARAMETER_MORE: if (connection.getParameter()==null|| parameters.containsKey(connection.getParameter())==false) { return xor; } else { return (parameters.get(connection.getParameter()) > connection.getParameterValue().calculate()) ^ xor; } } } return xor; } public boolean apply(Alchemy alchemy) { if (parameters.containsKey(alchemy.getFrom())) { int value = parameters.get(alchemy.getFrom()); if (value >=alchemy.getFromValue()) { value = value - alchemy.getFromValue(); parameters.put(alchemy.getFrom(),value); fireEvent(EVENT_CHANGE_PARAMETER, alchemy.getFrom(),value); if (alchemy.getFrom().isVital() && value<=0 && !finished ) { //hero is dead fireEvent(EVENT_FINISH); } if (alchemy.isWeapon()==false){ if (parameters.containsKey(alchemy.getTo())) { value = parameters.get(alchemy.getTo()); } else { value = 0; } value = value + alchemy.getToValue().calculate(); if (model.getSettings().isOverflowControl() ^ alchemy.isOverflowControl()) { if (alchemy.getTo().getLimit() != null && parameters.containsKey(alchemy.getTo().getLimit())) { //control limits int max = parameters.get(alchemy.getTo().getLimit()); int val = parameters.containsKey(alchemy.getTo()) ? parameters.get(alchemy.getTo()) : 0; if (val>max) { if (value>val) { //it was a useless action value = val; } } else if (value>max) { value=max; } } } parameters.put(alchemy.getTo(),value); fireEvent(EVENT_CHANGE_PARAMETER, alchemy.getTo(),value); if (alchemy.getTo().isVital() && value<=0 && !finished ) { //hero is dead fireEvent(EVENT_FINISH); } } return true; } } return false; } private void enableVitalConnection(ParagraphConnection connection) { vitalConnections.put(connection, Boolean.TRUE); fireEvent(EVENT_ENABLE, connection); } private void disableVitalConnection(ParagraphConnection connection) { vitalConnections.put(connection, Boolean.FALSE); fireEvent(EVENT_DISABLE, connection); } public Model getModel() { return model; } public boolean isFinished() { return finished; } public boolean hasObjects() { return bag.isEmpty()==false; } public ArrayList<Alchemy> getAlchemy(boolean battle,boolean currentParagraph) { ArrayList<AbstractParameter> list = model.getParameters(); ArrayList<Alchemy> result = null; for (AbstractParameter parameter : list) { if (parameter instanceof Alchemy) { Alchemy alchemy = (Alchemy) parameter; if (battle) { if (alchemyWasUsed && alchemy.isOneTimePerRound()) { //this alchemy can be used just one time per round continue; } } else { //peas if (alchemy.isOnDemand() && currentParagraph==false) { //this alchemy is used in paragraph test continue; } } if (meetsCondition(alchemy, battle)) { if (result==null) { result = new ArrayList<Alchemy>(); } result.add(alchemy); } } } return result; } public boolean nextBattleRound(BattleListener playerListener) { FighterData localTaget=selectTarget(); if (localTaget==null) { heroWonBattle(); return false; } FighterData victim = selectVictim(); int oldVital = parameters.get(currentBattle.getVital()); localTaget.round = currentBattle.calculateBattleRound(localTaget.getParameters()); if (victim != null) { victim.round = currentBattle.calculateBattleRound(victim.getParameters()); playerListener.victimAttack(victim.getNpc().getNpc(),localTaget.getNpc().getNpc()); currentBattle.attack(victim.getParameters(),victim.round,localTaget.getParameters(),localTaget.round,playerListener,true); if (victim.isAlive()==false) { //victim is dead victim = null; } if (localTaget.isAlive()==false) { localTaget = selectTarget(); if (localTaget==null) { heroWonBattle(); return false; } else { //select next target localTaget.round = currentBattle.calculateBattleRound(localTaget.getParameters()); } } } boolean heroFight = victim==null || currentParagraph.isFightTogether(); BattleRound heroRound = currentBattle.calculateBattleRound(parameters); if (heroFight) { alchemyWasUsed = false; playerListener.heroAttack(localTaget.getNpc().getNpc()); currentBattle.attack(parameters,heroRound,localTaget.getParameters(),localTaget.round,playerListener,true); if (victim != null && localTaget.isAlive()==false) { //target is dead, select next target for victim localTaget = selectTarget(); if (localTaget==null) { heroWonBattle(); return false; } else { //select next target localTaget.round = currentBattle.calculateBattleRound(localTaget.getParameters()); } } } if (isHeroAlive()) { //NPC attack for (FighterData widget : fighters) { if (widget.isAlive()) { if (widget.isFriend() && widget.getRound()<currentBattleRound) { //active friend in battle if (widget != victim) { //this friend is not fought yet widget.round = currentBattle.calculateBattleRound(widget.getParameters()); playerListener.victimAttack(victim.getNpc().getNpc(),localTaget.getNpc().getNpc()); currentBattle.attack(victim.getParameters(),widget.round,localTaget.getParameters(),localTaget.round,playerListener,false); if (victim.isAlive()==false) { //victim is dead victim = null; } if (localTaget.isAlive()==false) { localTaget = selectTarget(); if (localTaget==null) { heroWonBattle(); return false; } else if (localTaget.round==null){ //select next target localTaget.round = currentBattle.calculateBattleRound(localTaget.getParameters()); } } } } else if (currentBattle.isAttackDefense() || localTaget != widget) { //Enemy, for attack vs. attack battle Hero already fought with localTarget if (widget.round==null) { widget.round = currentBattle.calculateBattleRound(widget.getParameters()); } if (victim == null) { //fight with hero playerListener.heroDefence(widget.getNpc().getNpc()); currentBattle.attack(widget.getParameters(),widget.round,parameters,heroRound,playerListener,false); } else { //fight with victim playerListener.victimDefence(victim.npc.getNpc(),widget.getNpc().getNpc()); currentBattle.attack(widget.getParameters(),widget.round,victim.getParameters(),victim.round,playerListener,false); if (victim.isAlive()==false) { //victim is dead victim = null; } } } } } } if (localTaget.isAlive()==false) { localTaget = null; } for (FighterData npcWidget : fighters) { if (npcWidget.isAlive()) { //at least one enemy is alive if (localTaget==null) { currentBattleTarget = npcWidget; localTaget = npcWidget; } break; } } for (Parameter parameter : parameters.keySet()) { int val = parameters.get(parameter); if (parameter.isVital() || currentBattle.getVital()==parameter) { if (val<=0) { //hero is dead for (ParagraphConnection connection : vitalConnections.keySet()) { if (connection.getType()==ParagraphConnection.TYPE_VITAL_LESS) { if (connection.getParameterValue() != null) { if (connection.getParameterValue().calculate() ==0 ) { //enable this vital connection, it is "Hero is dead" enableVitalConnection(connection); return true; } } } } fireEvent(EVENT_CHANGE_PARAMETER, parameter, val); finished = true; heroIsDeadInBattle(); return false; } } } //check vital connections if (vitalConnections.isEmpty() == false) { for (ParagraphConnection connection : vitalConnections.keySet()) { int vital=10000000; if (connection.getType()==ParagraphConnection.TYPE_ENEMY_VITAL_LESS) { for (FighterData npcWidget : fighters) { if (npcWidget.isFriend() || npcWidget.getRound()>currentBattleRound) { //friend or join later continue; } int val = npcWidget.getParameters().get(currentBattle.getVital()); if (val<vital) { vital = val; } } } else if (connection.getType()==ParagraphConnection.TYPE_VITAL_LESS) { vital = parameters.get(currentBattle.getVital()); } if (connection.getParameterValue() != null) { if (connection.getParameterValue().calculate() > vital && vital>0) { //enable this vital connections enableVitalConnection(connection); if (connection.getStrictness()==ParagraphConnection.STRICTNESS_MUST) { //stop battle return false; } } else { //disable it disableVitalConnection(connection); } } } } int value = parameters.get(currentBattle.getVital()); if (oldVital !=value) { fireEvent(EVENT_CHANGE_PARAMETER, currentBattle.getVital(), value); } if (escapeBattleConnections.size()>0) { //there are some escape connections for (ParagraphConnection connection : escapeBattleConnections.keySet()) { if (currentBattleRound==connection.getParameterValue().getConstant()) { //this connection has to be enable on this round if (connection.getStrictness()==ParagraphConnection.STRICTNESS_MUST_NOT) { disableVitalConnection(connection); } else { //we can cancel battle enableVitalConnection(connection); if (connection.getStrictness()==ParagraphConnection.STRICTNESS_MUST) { //disable all other connections // for (ParagraphConnection connection2 : escapeBattleConnections.keySet()) { // if (connection2 != connection) { // disableVitalConnection(connection2); // } // } //we must cancel the battle fireEvent(EVENT_BATTLE, currentBattle,0); return false; } } } } } if (localTaget==null) { //hero is winner heroWonBattle(); return false; } else { currentBattleRound++; return true; } } private FighterData selectVictim() { boolean friends=false; for (FighterData npcWidget : fighters) { if (npcWidget.isFriend() && npcWidget.isAlive()) { //at least one friend is alive friends = true; if (npcWidget.getRound()<currentBattleRound) { //found victim return npcWidget; } } } if (currentParagraph.isFightTogether()) { //fight with Hero, NPC friend(s) joins later return null; } if (friends) { //move battle round counter for (FighterData npcWidget : fighters) { if (npcWidget.isFriend() && npcWidget.isAlive()) { currentBattleRound = npcWidget.getRound(); return npcWidget; } } } //all friends are dead or non-exist return null; } public boolean isHeroAlive() { for (Parameter parameter : parameters.keySet()) { int value = parameters.get(parameter); if (value < 1 && parameter.isVital()) { return false; } } return true; } public boolean doAlchemyInBattle(Alchemy alchemy) { alchemyWasUsed = true; boolean updateHeroParameters=false; if (alchemy.isWeapon()) { if (currentBattleTarget == null || !currentBattleTarget.getParameters().containsKey(alchemy.getTo())) { return false; } //update Hero if (apply(alchemy)==false) { //hm..error return false; } //update NPC int value = currentBattleTarget.getParameters().get(alchemy.getTo()); value = value - alchemy.getToValue().calculate(); if (value<0 && !alchemy.getTo().isNegative()) { value = 0; } currentBattleTarget.getParameters().put(alchemy.getTo(), value); selectTarget(); updateHeroParameters=true; } else { apply(alchemy); if (parameters.containsKey(alchemy.getTo())) { parameters.put(alchemy.getTo(),getParameters().get(alchemy.getTo())); updateHeroParameters=true; } } // if (parameters.containsKey(alchemy.getFrom())) { // parameters.put(alchemy.getFrom(),getParameters().get(alchemy.getFrom())); // updateHeroParameters=true; // } if (alchemy.getBattleLimit() != null) { //battle limit int value = heroBatleLimits.get(alchemy.getBattleLimit()); heroBatleLimits.put(alchemy.getBattleLimit(), value-1); updateHeroParameters=true; } return updateHeroParameters; } public class FighterData { public BattleRound round; private Map<Parameter,Integer> parameters; private NPCParams npc; private boolean dead; public boolean isAlive() { if (dead) { return false; } for (Parameter parameter : parameters.keySet()) { int value = parameters.get(parameter); if (value < 1 && parameter.isVital()) { dead = true; return false; } } return true; } public NPCParams getNpc() { return npc; } public Map<Parameter, Integer> getParameters() { return parameters; } public FighterData() { } public FighterData(Map<Parameter,Integer> initialParameters,Battle battle,NPCParams npc) { this.npc = npc; parameters = new LinkedHashMap<Parameter, Integer>(initialParameters.size()); //make a copy of parameters for (Parameter parameter : initialParameters.keySet()) { if (battle.dependsOn(parameter)) { parameters.put(parameter,new Integer(initialParameters.get(parameter))); } } } public int getVital() { return parameters.get(currentBattle.getVital()); } public boolean isFriend() { return npc.isFriend(); } public int getRound() { return npc.getRound(); } public boolean isCanBeTarget() { if (npc.isFriend() || npc.getRound()>currentBattleRound) { //cannot be attacked now return false; } else { return isAlive(); } } } public ArrayList<FighterData> getFighters() { return fighters; } public HashMap<Parameter, Integer> getHeroBattleLimits() { return heroBatleLimits; } public Battle getCurrentBattle() { return currentBattle; } // public boolean isAvailableInBattle(ParagraphConnection connection) { // if (connection.isBothDirections()) { // return false; // } else if (connection.getType()==ParagraphConnection.TYPE_ENEMY_VITAL_LESS || connection.getType()==ParagraphConnection.TYPE_VITAL_LESS){ // Boolean res = vitalConnections.get(connection); // if (res==null) { // return false; // } else { // return res.booleanValue(); // } // } else if (connection.getType()==ParagraphConnection.TYPE_BATTLE_ROUND_MORE){ // return connection.getParameterValue().getConstant() < currentBattleRound; // } else { // return false; // } // } private FighterData selectTarget() { if (currentBattleTarget != null && currentBattleTarget.isCanBeTarget()) { return currentBattleTarget; } FighterData localTaget = null; boolean skipRounds=false; for (FighterData npc : fighters) { if (npc.isFriend()) { continue; } if (npc.isAlive()) { if (npc.getRound()>currentBattleRound) { //this NPC joins later skipRounds=true; continue; } //still in battle localTaget = npc; break; } } if (localTaget==null && skipRounds) { //no enemies now but there are something later for (FighterData npc : fighters) { if (npc.isFriend()) { continue; } if (npc.isAlive()) { //still in battle currentBattleRound = npc.getRound(); localTaget = npc; break; } } } if (localTaget==null) { heroWonBattle(); } else { currentBattleTarget = localTaget; } return localTaget; } public boolean isTarget(FighterData target) { return target==currentBattleTarget; } public void selectTarget(FighterData target) { currentBattleTarget = target; } public boolean isBattleActive() { return currentBattle != null && currentBattleTarget != null; } public String getStateFromHistory() { for (int i = 0; i < history.length; i++) { if (history[i] != null) { history[history.length-1] = history[i]; return Base64Coder.encodeString(history[i]); } } return null; } public boolean isCanRestoreFromHistory() { for (int i = 0; i < history.length; i++) { if (history[i] != null) { return true; } } return false; } public boolean isHeroFighting() { return currentParagraph.isFightTogether() || selectVictim()==null; } public boolean isHasBackState() { return history[history.length-2] != null; } public void goBack() { restoreState(Base64Coder.encodeString((history[history.length-2]))); } public void killAllOpponents() { if (isBattleActive()) { for (FighterData data : fighters) { if (data.isCanBeTarget()) { data.dead = true; } } } } }