/** * eAdventure (formerly <e-Adventure> and <e-Game>) is a research project of the * <e-UCM> research group. * * Copyright 2005-2010 <e-UCM> research group. * * You can access a list of all the contributors to eAdventure at: * http://e-adventure.e-ucm.es/contributors * * <e-UCM> is a research group of the Department of Software Engineering * and Artificial Intelligence at the Complutense University of Madrid * (School of Computer Science). * * C Profesor Jose Garcia Santesmases sn, * 28040 Madrid (Madrid), Spain. * * For more info please visit: <http://e-adventure.e-ucm.es> or * <http://www.e-ucm.es> * * **************************************************************************** * * This file is part of eAdventure, version 2.0 * * eAdventure is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * eAdventure is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with eAdventure. If not, see <http://www.gnu.org/licenses/>. */ package es.eucm.ead.importer.subconverters.actors.actions; import com.google.inject.Inject; import com.google.inject.Singleton; import es.eucm.ead.importer.ModelQuerier; import es.eucm.ead.importer.StringsConverter; import es.eucm.ead.importer.resources.ResourcesConverter; import es.eucm.ead.importer.subconverters.conditions.ConditionsConverter; import es.eucm.ead.importer.subconverters.effects.EffectsConverter; import es.eucm.ead.legacyplugins.model.LegacyVars; import es.eucm.ead.model.assets.drawable.basics.Image; import es.eucm.ead.model.elements.conditions.Condition; import es.eucm.ead.model.elements.conditions.EmptyCond; import es.eucm.ead.model.elements.conditions.NOTCond; import es.eucm.ead.model.elements.conditions.ORCond; import es.eucm.ead.model.elements.effects.ActorActionsEf; import es.eucm.ead.model.elements.effects.Effect; import es.eucm.ead.model.elements.effects.TriggerMacroEf; import es.eucm.ead.model.elements.extra.EAdList; import es.eucm.ead.model.elements.predef.effects.MoveActiveElementToMouseEf; import es.eucm.ead.model.elements.scenes.SceneElementDef; import es.eucm.ead.model.params.guievents.MouseGEv; import es.eucm.ead.model.params.text.EAdString; import es.eucm.eadventure.common.data.adventure.AdventureData; import es.eucm.eadventure.common.data.chapter.Action; import es.eucm.eadventure.common.data.chapter.CustomAction; import es.eucm.eadventure.common.data.chapter.resources.Resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Converts Actions to scene element definitions */ @Singleton public class ActionsConverter { static private Logger logger = LoggerFactory .getLogger(ActionsConverter.class); private final Image[] actionImages = new Image[] { new Image("@drawable/drag-normal.png"), new Image("@drawable/giveto-normal.png"), new Image("@drawable/grab-normal.png"), new Image("@drawable/use-normal.png"), new Image("@drawable/usewith-normal.png"), new Image("@drawable/examine-normal.png"), new Image("@drawable/talk-normal.png"), new Image("@drawable/use-normal.png") }; private final Image[] actionImagesOver = new Image[] { new Image("@drawable/drag-pressed.png"), new Image("@drawable/giveto-pressed.png"), new Image("@drawable/grab-pressed.png"), new Image("@drawable/use-pressed.png"), new Image("@drawable/usewith-pressed.png"), new Image("@drawable/examine-pressed.png"), new Image("@drawable/talk-pressed.png"), new Image("@drawable/use-pressed.png") }; private ResourcesConverter resourceConverter; private StringsConverter stringsConverter; private EffectsConverter effectsConverter; private ConditionsConverter conditionsConverter; private ModelQuerier modelQuerier; // Aux. variables /** * Actions converted */ private EAdList<SceneElementDef> definitions; /** * Map holding actions and its related trigger macro effect */ private Map<Integer, TriggerMacroEf> actions; /** * Map holding custom actions and its related trigger macro effect */ private Map<String, TriggerMacroEf> customActions; /** * Map holding custom interact actions and its related trigger macro effect */ private Map<String, TriggerMacroEf> customInteractActions; /** * Map holding actions and its related trigger macro effect */ private Map<Integer, Condition> actionsConditions; /** * Map holding custom actions and its related trigger macro effect */ private Map<String, Condition> customActionsConditions; /** * Map holding custom interact actions and its related trigger macro effect */ private Map<String, Condition> customInteractActionsConditions; /** * Map relating trigger macro effects with their action definition */ private Map<TriggerMacroEf, SceneElementDef> macros; @Inject public ActionsConverter(ResourcesConverter resourceConverter, StringsConverter stringsConverter, EffectsConverter effectsConverter, ConditionsConverter conditionsConverter, ModelQuerier modelQuerier) { this.resourceConverter = resourceConverter; this.stringsConverter = stringsConverter; this.effectsConverter = effectsConverter; this.conditionsConverter = conditionsConverter; this.modelQuerier = modelQuerier; // We keep three maps because in actions list we can have repeated // actions. In the new model, this actions are all simplified in one // definition with one trigger macro with one effect for every of the // actions in their conditions. actions = new HashMap<Integer, TriggerMacroEf>(); customActions = new HashMap<String, TriggerMacroEf>(); customInteractActions = new HashMap<String, TriggerMacroEf>(); // We keep other three maps to hold visibility conditions for the // actions actionsConditions = new HashMap<Integer, Condition>(); customActionsConditions = new HashMap<String, Condition>(); customInteractActionsConditions = new HashMap<String, Condition>(); // And finally, a list with all the actions definitions definitions = new EAdList<SceneElementDef>(); // A map relating trigger macro effects with actions definitions. We need them // to update the visibility condition macros = new LinkedHashMap<TriggerMacroEf, SceneElementDef>(); } /** * Converts a list of actions to a list scene element definitions * * @param owner the owner of the actions * @param ac the list of actions * @return the list of actions converted */ public EAdList<SceneElementDef> convert(SceneElementDef owner, List<Action> ac) { // Clean maps actions.clear(); customActions.clear(); customInteractActions.clear(); actionsConditions.clear(); customActionsConditions.clear(); customInteractActionsConditions.clear(); definitions.clear(); macros.clear(); // Conversion for (Action a : ac) { if (a.getType() != Action.DRAG_TO) convert(owner, a); } EAdList<SceneElementDef> actions = new EAdList<SceneElementDef>(); actions.addAll(definitions); return actions; } /** * Converts an action to a scene element definition * * @param a * @return */ public void convert(SceneElementDef owner, Action a) { TriggerMacroEf triggerMacroEf; Condition visibility; // Fetch if the there is already an action like "a" if (a.getType() == Action.CUSTOM) { String name = ((CustomAction) a).getName(); triggerMacroEf = customActions.get(name); visibility = customActionsConditions.get(name); } else if (a.getType() == Action.CUSTOM_INTERACT) { String name = ((CustomAction) a).getName(); triggerMacroEf = customInteractActions.get(name); visibility = customInteractActionsConditions.get(name); } else { triggerMacroEf = actions.get(a.getType()); visibility = actionsConditions.get(a.getType()); } // If not, create one boolean created = false; if (triggerMacroEf == null) { created = true; triggerMacroEf = new TriggerMacroEf(); visibility = conditionsConverter.convert(a.getConditions()); SceneElementDef def = new SceneElementDef(); addAppearance(a, def); addName(a, def); def.addBehavior(MouseGEv.MOUSE_LEFT_PRESSED, triggerMacroEf); // Add the definition to the list definitions.add(def); macros.put(triggerMacroEf, def); } // We add the effects to the macro and updates the visibility condition visibility = addEffects(owner, a, triggerMacroEf, visibility); // Update visibility macros.get(triggerMacroEf).putProperty(ActorActionsEf.VAR_ACTION_COND, visibility); // We update the maps if (a.getType() == Action.CUSTOM) { String name = ((CustomAction) a).getName(); if (created) { customActions.put(name, triggerMacroEf); } customActionsConditions.put(name, visibility); } else if (a.getType() == Action.CUSTOM_INTERACT) { String name = ((CustomAction) a).getName(); if (created) { customInteractActions.put(name, triggerMacroEf); } customInteractActionsConditions.put(name, visibility); } else { if (created) { actions.put(a.getType(), triggerMacroEf); } actionsConditions.put(a.getType(), visibility); } } /** * Add effects to the macro * * @param a * @param triggerMacroEf * @param visibility * @return the visibility condition updated */ private Condition addEffects(SceneElementDef owner, Action a, TriggerMacroEf triggerMacroEf, Condition visibility) { List<Effect> effects = effectsConverter.convert(a.getEffects()); if (effects.size() == 0) { logger.debug("Action '" + getActionName(a.getType()) + "' of element '" + owner.getId() + "' has no effects."); } // I think that click effects should always be empty for actions, but... whatever List<Effect> clickEffects = effectsConverter.convert(a .getClickEffects()); if (clickEffects.size() > 0) { if (effects.size() > 0) { effects.get(effects.size() - 1).addNextEffect( clickEffects.get(0)); } effects.addAll(clickEffects); } Effect firstEffect = null; // Add move to, if necessary if (modelQuerier.getAventureData().getPlayerMode() == AdventureData.MODE_PLAYER_3RDPERSON) { MoveActiveElementToMouseEf moveTo = new MoveActiveElementToMouseEf(); moveTo.setTarget(owner); firstEffect = moveTo; if (effects.size() > 0) { moveTo.addNextEffect(effects.get(0)); } effects.add(0, moveTo); } else if (effects.size() > 0) { firstEffect = effects.get(0); } Condition condition = conditionsConverter.convert(a.getConditions()); if (effects.size() > 0) { // We add the effect to the macro if (firstEffect != null) { triggerMacroEf.putEffect(condition, firstEffect); } // We expand the visibility condition visibility = new ORCond(visibility, condition); } // If the action has not effects, it is always visible. By the way, the OR will be simplified in // the writing process to TRUE, even if the condition is modified by posteriors actions if (a.isActivatedNotEffects()) { List<Effect> notEffects = effectsConverter.convert(a .getNotEffects()); if (notEffects.size() > 0) { triggerMacroEf.putEffect(new NOTCond(condition), notEffects .get(0)); visibility = new ORCond(EmptyCond.TRUE); } } return visibility; } /** * Adds the name of the action * * @param a * @param def */ private void addName(Action a, SceneElementDef def) { String name = null; if (a instanceof CustomAction) { CustomAction ca = (CustomAction) a; name = ca.getName(); } else { name = getActionName(a.getType()); } // In eAd1, expressions are not evaluated for action names EAdString string = stringsConverter.convert(name, false); def.putProperty(LegacyVars.BUBBLE_NAME, string); } /** * Add appearance to the button * * @param a * @param def */ protected void addAppearance(Action a, SceneElementDef def) { if (a instanceof CustomAction) { // Custom actions has their own images CustomAction ca = (CustomAction) a; if (ca.getResources().size() > 1) { logger .warn( "Weird. Custom action {} contains more than one set of resources. Custom actions should only contain one set of resources", ((CustomAction) a).getName()); } // Possible resources: (not in constants) "buttonOver", // "buttonNormal", "buttonSound", "actionAnimation" // XXX Resource group Animations: "actionAnimation" // XXX Resource group Button: "buttonSound" Resources r = ca.getResources().get(0); String appearance = r.getAssetPath("buttonNormal"); String overAppearance = r.getAssetPath("buttonOver"); if (appearance != null) { def.setAppearance(new Image(resourceConverter .getPath(appearance))); } else { def.setAppearance(getImage(Action.USE, false)); } if (overAppearance != null) { def.setOverAppearance(new Image(resourceConverter .getPath(overAppearance))); } else { def.setOverAppearance(getImage(Action.USE, true)); } } else { // Normal actions use standard images def.setAppearance(getImage(a.getType(), false)); def.setOverAppearance(getImage(a.getType(), true)); } } /** * Returns the corresponding image for the given action type * * @param actionType * @return */ public Image getImage(int actionType, boolean over) { int image; switch (actionType) { case Action.DRAG_TO: image = 0; break; case Action.GIVE_TO: image = 1; break; case Action.GRAB: image = 2; break; case Action.USE: image = 3; break; case Action.USE_WITH: image = 4; break; case Action.EXAMINE: image = 5; break; case Action.TALK_TO: image = 6; break; default: image = 0; } return over ? actionImagesOver[image] : actionImages[image]; } /** * Returns the name (in English) of the given action * * @param actionType * @return */ private String getActionName(int actionType) { switch (actionType) { case Action.DRAG_TO: return "Drag to"; case Action.EXAMINE: return "Examine"; case Action.GIVE_TO: return "Give to"; case Action.GRAB: return "Grab"; case Action.TALK_TO: return "Talk to"; case Action.USE: return "Use"; case Action.USE_WITH: return "Use with"; case Action.CUSTOM: return "Custom"; case Action.CUSTOM_INTERACT: return "Custom interact"; default: return "Action"; } } }