/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.WeakHashMap;
public class Layered extends Drawable {
List<Indir<Resource>> layers;
Map<Indir<Resource>, Sprite> sprites = new TreeMap<Indir<Resource>, Sprite>();
Map<Indir<Resource>, Integer> delays = new TreeMap<Indir<Resource>, Integer>();
final Indir<Resource> base;
boolean loading;
static LayerCache cache = new LayerCache(1000);
Map<Layer, Sprite.Part> pcache = new WeakHashMap<Layer, Sprite.Part>();
public static class Layer {
BufferedImage img;
Tex tex = null;
Coord cc;
Tex ol = null;
public Layer(BufferedImage img, Coord cc) {
this.img = img;
this.cc = cc;
}
public Tex tex() {
if (tex != null)
return (tex);
tex = new TexI(img);
return (tex);
}
public Tex ol() {
if (ol == null)
ol = new TexI(Utils.outline(img, java.awt.Color.YELLOW));
return (ol);
}
public void dispose() {
if (tex != null)
tex.dispose();
}
}
public static class LayerCache {
private int cachesz;
private Map<Object[], Layer> cache = new IdentityHashMap<Object[], Layer>();
private LinkedList<Object[]> recency = new LinkedList<Object[]>();
private int cached;
public LayerCache(int cachesz) {
this.cachesz = cachesz;
}
private synchronized void usecache(Object[] id) {
for (Iterator<Object[]> i = recency.iterator(); i.hasNext();) {
Object[] cid = (Object[]) i.next();
if (cid == id) {
i.remove();
recency.addFirst(id);
return;
}
}
throw (new RuntimeException("Used layered cache is not in recency list"));
}
public synchronized int size() {
return (recency.size());
}
public synchronized int cached() {
return (cached);
}
public synchronized Layer get(Object[] id) {
Layer l = cache.get(id);
if (l != null)
usecache(id);
return (l);
}
private synchronized void cleancache() {
while (recency.size() > cachesz) {
Object[] id = recency.removeLast();
cache.remove(id).dispose();
}
}
public synchronized void put(Object[] id, Layer l) {
cache.put(id, l);
recency.addFirst(id);
cleancache();
cached++;
}
}
public Layered(Gob gob, Indir<Resource> base) {
super(gob);
this.base = base;
layers = new ArrayList<Indir<Resource>>();
}
public synchronized void setlayers(List<Indir<Resource>> layers) {
Collections.sort(layers);
if (layers.equals(this.layers))
return;
loading = true;
this.layers = layers;
delays = new TreeMap<Indir<Resource>, Integer>();
sprites = new TreeMap<Indir<Resource>, Sprite>();
for (Indir<Resource> r : layers) {
delays.put(r, 0);
sprites.put(r, null);
}
}
public synchronized boolean checkhit(Coord c) {
if (base.get() == null)
return (false);
for (Sprite spr : sprites.values()) {
if (spr == null)
continue;
if (spr.checkhit(c))
return (true);
}
return (false);
}
public synchronized void setup(Sprite.Drawer drw, final Coord cc, final Coord off) {
if (base.get() == null)
return;
if (loading) {
loading = false;
for (Indir<Resource> r : layers) {
if (sprites.get(r) == null) {
if (r.get() == null)
loading = true;
else
sprites.put(r, Sprite.create(gob, r.get(), null));
}
}
}
/*
* XXX: Fix this to construct parts dynamically depending on which
* layers exist.
*/
Sprite.Part me;
me = makepart(0);
me.setup(cc, off);
drw.addpart(me);
me = makepart(-10);
me.setup(cc, off);
drw.addpart(me);
}
private synchronized Object[] stateid(Object... extra) {
Object[] ret = new Object[layers.size() + extra.length];
for (int i = 0; i < layers.size(); i++) {
Sprite spr = sprites.get(layers.get(i));
if (spr == null)
ret[i] = null;
else
ret[i] = spr.stateid();
}
for (int i = 0; i < extra.length; i++)
ret[i + layers.size()] = extra[i];
return (ArrayIdentity.intern(ret));
}
private Layer redraw(final int z) {
final ArrayList<Sprite.Part> parts = new ArrayList<Sprite.Part>();
Sprite.Drawer drw = new Sprite.Drawer() {
public void addpart(Sprite.Part p) {
if (p.z == z)
parts.add(p);
}
};
for (Sprite spr : sprites.values()) {
if (spr != null)
spr.setup(drw, Coord.z, Coord.z);
}
Collections.sort(parts, Sprite.partcmp);
Coord ul = new Coord(0, 0);
Coord lr = new Coord(0, 0);
for (Sprite.Part part : parts) {
if (part.ul.x < ul.x)
ul.x = part.ul.x;
if (part.ul.y < ul.y)
ul.y = part.ul.y;
if (part.lr.x > lr.x)
lr.x = part.lr.x;
if (part.lr.y > lr.y)
lr.y = part.lr.y;
}
BufferedImage buf = TexI.mkbuf(lr.add(ul.inv()).add(1, 1));
Graphics g = buf.getGraphics();
/*
* g.setColor(java.awt.Color.RED); g.fillRect(0, 0, buf.getWidth(),
* buf.getHeight());
*/
for (Sprite.Part part : parts) {
part.cc = part.cc.add(ul.inv());
part.draw(buf, g);
}
g.dispose();
return (new Layer(buf, ul.inv()));
}
private Sprite.Part makepart(int z) {
final Layer l;
synchronized (Layered.this) {
Object[] id = stateid(z);
synchronized (cache) {
Layer ll = cache.get(id);
if (ll == null) {
ll = redraw(z);
cache.put(id, ll);
}
l = ll;
}
}
synchronized (pcache) {
Sprite.Part p = pcache.get(l);
if (p == null) {
p = new Sprite.Part(z) {
public void draw(BufferedImage buf, Graphics g) {
g.drawImage(l.img, -l.cc.x, -l.cc.y, null);
}
public void draw(GOut g) {
g.image(l.tex(), cc.add(l.cc.inv()).add(off));
}
public void drawol(GOut g) {
g.image(l.ol(), cc.add(l.cc.inv()).add(off).add(-1, -1));
}
public void setup(Coord cc, Coord off) {
super.setup(cc, off);
ul = cc.add(l.cc.inv());
lr = ul.add(l.tex().sz());
}
public boolean checkhit(Coord c) {
return (Layered.this.checkhit(c));
}
};
pcache.put(l, p);
}
return (p);
}
}
public synchronized void ctick(int dt) {
for (Map.Entry<Indir<Resource>, Sprite> e : sprites.entrySet()) {
Indir<Resource> r = e.getKey();
Sprite spr = e.getValue();
if (spr != null) {
int ldt = dt;
if (delays.get(r) != null) {
ldt += delays.get(r);
delays.remove(r);
}
spr.tick(ldt);
} else {
delays.put(r, delays.get(r) + dt);
}
}
}
}