/*
* 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);
}
}
}
}