/* * Copyright (c) 2003-onwards Shaven Puppy Ltd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'Shaven Puppy' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package worm.features; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import net.puppygames.applet.Anchor; import net.puppygames.applet.Bounded; import net.puppygames.applet.Game; import net.puppygames.applet.Screen; import net.puppygames.applet.TickableObject; import net.puppygames.applet.effects.Effect; import net.puppygames.applet.effects.Emitter; import org.lwjgl.input.Mouse; import org.lwjgl.util.Dimension; import org.lwjgl.util.Point; import org.lwjgl.util.ReadableRectangle; import org.lwjgl.util.Rectangle; import org.w3c.dom.Element; import worm.Statistics; import worm.Worm; import worm.animation.SimpleThingWithLayers; import worm.features.StoryFeature.ParagraphFeature; import com.shavenpuppy.jglib.Resources; import com.shavenpuppy.jglib.resources.Background; import com.shavenpuppy.jglib.resources.Data; import com.shavenpuppy.jglib.resources.Feature; import com.shavenpuppy.jglib.sprites.SimpleRenderer; import com.shavenpuppy.jglib.util.XMLUtil; /** * A story setting. This has a background consisting of a LayersFeature, and then a number of references to * CharacterFeatures with coordinates and bounding boxes for speech bubbles. Not all characters will necessarily * be used. */ public class SettingFeature extends Feature { private static final long serialVersionUID = 1L; /** Rectangular background */ @Data private String bg; /** Bg layer */ private int bgLayer; /** Background */ @Data private String background; /** Background position */ private Point position; /** Background size */ private Dimension size; /** Actors: map of character ids to ActorFeatures */ private Map<String, ActorFeature> actors; // chaz hack! use world story characters? private boolean worldIntro; /** Centre x / y/ both */ @Data private String centre = "both"; /** Anchors for setting */ private ArrayList<Anchor> anchors; /** Anchors for bg */ private ArrayList<Anchor> bgAnchors; /* * Transient */ private transient LayersFeature backgroundFeature; private transient Background bgFeature; /** * Instances */ private class SettingInstance extends Effect implements Setting { private final StoryFeature story; private SimpleThingWithLayers backgroundLayers; private Emitter[] backgroundEmitter; private TickableObject bgObject; private class BGInstance implements Bounded { Background.Instance bgInstance; Rectangle bounds = new Rectangle(position, size); BGInstance(Background.Instance bgInstance) { this.bgInstance = bgInstance; bgInstance.setBounds(bounds); } @Override public ReadableRectangle getBounds() { return bounds; } @Override public void setBounds(int x, int y, int w, int h) { bounds.setBounds(x, y, w, h); } void render(SimpleRenderer renderer) { bgInstance.render(renderer); } } private BGInstance bgInstance; private Set<CharacterFeature> actorsInUse; private List<Actor> actorInstances; private Actor currentActor; private boolean waitForMouse; private int currentIdx; private boolean done; private Rectangle bounds = new Rectangle(position == null ? new Point(0, 0) : position, size == null ? new Dimension(Game.getScale(), Game.getScale()) : size); /** * C'tor * @param story The story to read out in this setting */ public SettingInstance(StoryFeature story) { this.story = story; } @Override public boolean isCentredX() { return "x".equals(centre); } @Override public boolean isCentredY() { return "y".equals(centre); } @Override public boolean isCentred() { return "both".equals(centre); } @Override public void onResized() { // Apply anchors unless screen is "centred" if (anchors != null) { for (Iterator<Anchor> i = anchors.iterator(); i.hasNext(); ) { Anchor anchor = i.next(); anchor.apply(this); } } else if (position != null) { boolean centreX = isCentred() || isCentredX(); boolean centreY = isCentred() || isCentredY(); if (centreX || centreY) { ReadableRectangle currentBounds = getBounds(); int newX = centreX ? (Game.getWidth() - Game.getScale()) / 2 + position.getX() : position.getX(); int newY = centreY ? (Game.getHeight() - Game.getScale()) / 2 + position.getY() : position.getY(); setBounds(newX, newY, currentBounds.getWidth(), currentBounds.getHeight()); } } // Resize bg if (bgAnchors != null) { for (Iterator<Anchor> i = bgAnchors.iterator(); i.hasNext(); ) { Anchor anchor = i.next(); anchor.apply(bgInstance); } } // Resize actors for (Iterator<Actor> i = actorInstances.iterator(); i.hasNext(); ) { Actor actor = i.next(); actor.onResized(); } } @Override public ReadableRectangle getBounds() { return bounds; } @Override public void setBounds(int x, int y, int w, int h) { bounds.setBounds(x, y, w, h); if (backgroundLayers != null && backgroundLayers.getSprites() != null) { for (int i = 0; i < backgroundLayers.getSprites().length; i ++) { backgroundLayers.getSprite(i).setLocation(x, y); } } } @Override protected void doSpawnEffect() { // Create area bg if (bgFeature != null) { bgInstance = new BGInstance(bgFeature.spawn()); bgObject = new TickableObject() { @Override protected void render() { bgInstance.render(this); } }; bgObject.spawn(getScreen()); bgObject.setLayer(bgLayer); } // Create the background backgroundLayers = new SimpleThingWithLayers(getScreen()); if (backgroundFeature != null) { backgroundFeature.createSprites(getScreen(), backgroundLayers); backgroundEmitter = backgroundFeature.createEmitters(getScreen(), 0.0f, 0.0f); } // Create the actors actorInstances = new ArrayList<Actor>(); List<ParagraphFeature> characters = story.getChars(); actorsInUse = new HashSet<CharacterFeature>(); for (Iterator<ParagraphFeature> i = characters.iterator(); i.hasNext(); ) { ParagraphFeature pf = i.next(); CharacterFeature cf = pf.getCharacter(); // Find the corresponding ActorFeature if (cf != null) { ActorFeature af = actors.get(cf.getName()); if (af != null) { Actor actor = null; if (!actorsInUse.contains(cf)) { actorsInUse.add(cf); actor = af.spawn(getScreen(), this); } else { // Find actor already spawned for (Iterator<Actor> j = actorInstances.iterator(); j.hasNext(); ) { Actor a = j.next(); if (a.getCharacter() == cf) { actor = a; break; } } } assert actor != null; String text = pf.getText(); // Parse special values if (text.indexOf("[stats]") != -1) { text = text.replaceAll("\\[stats\\]", Worm.getGameState().getStatsText()); } while (text.indexOf("[") != -1) { int idx1 = text.indexOf("["); int idx3 = text.indexOf(":", idx1); String type = text.substring(idx1 + 1, idx3); int idx2 = text.indexOf("]", idx1); String statsName = text.substring(idx1 + 7, idx2); Statistics stats = (Statistics) Resources.peek(statsName); StringBuilder sb = new StringBuilder(256); if (type.equals("stats")) { stats.appendFullStats(sb); } else if (type.equals("basic")) { stats.appendBasicStats(sb); } else if (type.equals("title")) { stats.appendTitle(sb); } text = text.substring(0, idx1) + sb + text.substring(idx2 + 1); } actor.setDelay(pf.getDelay()); actor.setFadeAfter(pf.getFadeAfter()); actor.addText(text); actorInstances.add(actor); if (currentActor == null) { currentActor = actor; } } } } // Start the first one off if (currentActor != null) { currentActor.begin(); } currentIdx = 0; waitForMouse = true; } @Override protected void doRemove() { if (currentActor != null) { currentActor.remove(); currentActor = null; } if (bgObject != null) { bgObject.remove(); bgObject = null; } // Remove the background if (backgroundLayers != null) { backgroundLayers.remove(); backgroundLayers = null; } if (backgroundEmitter != null) { for (Emitter element : backgroundEmitter) { if (element != null) { element.remove(); } } backgroundEmitter = null; } } @Override protected void doTick() { if (Mouse.isButtonDown(0)) { if (!waitForMouse) { waitForMouse = true; if (currentActor != null) { if (!currentActor.advance()) { next(); } } } } else { waitForMouse = false; } if (currentActor != null && currentActor.isFinished()) { next(); } } private void next() { currentIdx ++; if (currentIdx < actorInstances.size()) { currentActor = actorInstances.get(currentIdx); currentActor.begin(); } else { currentActor = null; } } @Override public boolean isEffectActive() { return !done; } @Override protected void render() { // Nothing to render } @Override public void finish() { done = true; } } /** * C'tor */ public SettingFeature() { setAutoCreated(); } /** * C'tor * @param name */ public SettingFeature(String name) { super(name); setAutoCreated(); } /** * Spawn an instance of this Setting, and read out the given story in it * @param screen * @param story * @return */ public Setting spawn(Screen screen, StoryFeature story) { SettingInstance ret = new SettingInstance(story); ret.spawn(screen); return ret; } /* (non-Javadoc) * @see com.shavenpuppy.jglib.resources.Feature#load(org.w3c.dom.Element, com.shavenpuppy.jglib.Resource.Loader) */ @Override public void load(Element element, Loader loader) throws Exception { super.load(element, loader); List<Element> children = XMLUtil.getChildren(element, "actor"); actors = new HashMap<String, ActorFeature>(); for (Element child : children) { ActorFeature af = new ActorFeature(); af.load(child, loader); actors.put(af.getCharacter(), af); } // Anchors List<Element> anchorElements = XMLUtil.getChildren(element, "anchor"); if (anchorElements.size() > 0) { anchors = new ArrayList<Anchor>(anchorElements.size()); for (Element anchorChild : anchorElements) { Anchor anchor = (Anchor) loader.load(anchorChild); anchors.add(anchor); } } // bg Anchors if (XMLUtil.hasChild(element, "bganchors")) { Element bgAnchorsElement = XMLUtil.getChild(element, "bganchors"); List<Element> bganchorElements = XMLUtil.getChildren(bgAnchorsElement, "anchor"); if (bganchorElements.size() > 0) { bgAnchors = new ArrayList<Anchor>(bganchorElements.size()); for (Element bganchorChild : bganchorElements) { Anchor bganchor = (Anchor) loader.load(bganchorChild); bgAnchors.add(bganchor); } } } } /* (non-Javadoc) * @see com.shavenpuppy.jglib.resources.Feature#doCreate() */ @Override protected void doCreate() { super.doCreate(); for (Iterator<ActorFeature> i = actors.values().iterator(); i.hasNext(); ) { ActorFeature af = i.next(); af.create(); } } /* (non-Javadoc) * @see com.shavenpuppy.jglib.resources.Feature#doDestroy() */ @Override protected void doDestroy() { super.doDestroy(); for (Iterator<ActorFeature> i = actors.values().iterator(); i.hasNext(); ) { ActorFeature af = i.next(); af.destroy(); } } }