/* * 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.util.*; import java.lang.ref.*; import haven.Resource.Tileset; import haven.Resource.Tile; public class MCache { public static final Coord tilesz = new Coord(11, 11); public static final Coord cmaps = new Coord(100, 100); public static final Coord cutsz = new Coord(25, 25); public static final Coord cutn = cmaps.div(cutsz); private final Resource.Spec[] nsets = new Resource.Spec[256]; @SuppressWarnings("unchecked") private final Reference<Resource>[] sets = new Reference[256]; @SuppressWarnings("unchecked") private final Reference<Tileset>[] csets = new Reference[256]; @SuppressWarnings("unchecked") private final Reference<Tiler>[] tiles = new Reference[256]; Map<Coord, Request> req = new HashMap<Coord, Request>(); Map<Coord, Grid> grids = new HashMap<Coord, Grid>(); Session sess; Set<Overlay> ols = new HashSet<Overlay>(); int olseq = 0; Random gen = new Random(); Map<Integer, Defrag> fragbufs = new TreeMap<Integer, Defrag>(); public static class LoadingMap extends Loading { public LoadingMap() {} public LoadingMap(Throwable cause) { super(cause); } } private static class Request { private long lastreq = 0; private int reqs = 0; } public class Overlay { private Coord c1, c2; private int mask; public Overlay(Coord c1, Coord c2, int mask) { this.c1 = c1; this.c2 = c2; this.mask = mask; ols.add(this); olseq++; } public void destroy() { ols.remove(this); olseq++; } public void update(Coord c1, Coord c2) { if(!c1.equals(this.c1) || !c2.equals(this.c2)) { olseq++; this.c1 = c1; this.c2 = c2; } } public void update() { olseq++; } } public class Grid { public final int tiles[] = new int[cmaps.x * cmaps.y]; public final int z[] = new int[cmaps.x * cmaps.y]; public final int ol[] = new int[cmaps.x * cmaps.y]; private final Cut cuts[]; int olseq = -1; private Collection<Gob>[] fo = null; public final Coord gc, ul; public long id; String mnm; private class Cut { MapMesh mesh; Defer.Future<MapMesh> dmesh; Rendered[] ols; int deftag; } private class Flavobj extends Gob { private Flavobj(Coord c, double a) { super(sess.glob, c); this.a = a; } public Random mkrandoom() { Random r = new Random(Grid.this.id); r.setSeed(r.nextInt() ^ rc.x); r.setSeed(r.nextInt() ^ rc.y); return(r); } } public Grid(Coord gc) { this.gc = gc; this.ul = gc.mul(cmaps); cuts = new Cut[cutn.x * cutn.y]; for(int i = 0; i < cuts.length; i++) cuts[i] = new Cut(); } public int gettile(Coord tc) { return(tiles[tc.x + (tc.y * cmaps.x)]); } public int getz(Coord tc) { return(z[tc.x + (tc.y * cmaps.x)]); } public int getol(Coord tc) { return(ol[tc.x + (tc.y * cmaps.x)]); } private void makeflavor() { @SuppressWarnings("unchecked") Collection<Gob>[] fo = (Collection<Gob>[])new Collection[cutn.x * cutn.y]; for(int i = 0; i < fo.length; i++) fo[i] = new LinkedList<Gob>(); Coord c = new Coord(0, 0); Coord tc = gc.mul(cmaps); int i = 0; Random rnd = new Random(id); for(c.y = 0; c.y < cmaps.x; c.y++) { for(c.x = 0; c.x < cmaps.y; c.x++, i++) { Tileset set = tileset(tiles[i]); if(set.flavobjs.size() > 0) { if(rnd.nextInt(set.flavprob) == 0) { Resource r = set.flavobjs.pick(rnd); double a = rnd.nextDouble() * 2 * Math.PI; Gob g = new Flavobj(c.add(tc).mul(tilesz).add(tilesz.div(2)), a); g.setattr(new ResDrawable(g, r)); Coord cc = c.div(cutsz); fo[cc.x + (cc.y * cutn.x)].add(g); } } } } this.fo = fo; } public Collection<Gob> getfo(Coord cc) { if(fo == null) makeflavor(); return(fo[cc.x + (cc.y * cutn.x)]); } private Cut geticut(Coord cc) { return(cuts[cc.x + (cc.y * cutn.x)]); } public MapMesh getcut(Coord cc) { Cut cut = geticut(cc); if(cut.dmesh != null) { if(cut.dmesh.done() || (cut.mesh == null)) { MapMesh old = cut.mesh; cut.mesh = cut.dmesh.get(); cut.dmesh = null; if(old != null) old.dispose(); } } return(cut.mesh); } public Rendered getolcut(int ol, Coord cc) { int nseq = MCache.this.olseq; if(this.olseq != nseq) { for(int i = 0; i < cutn.x * cutn.y; i++) { if(cuts[i].ols != null) { for(Rendered r : cuts[i].ols) { if(r instanceof Disposable) ((Disposable)r).dispose(); } } cuts[i].ols = null; } this.olseq = nseq; FlatnessTool.recalcheight(); } Cut cut = geticut(cc); if(cut.ols == null) cut.ols = getcut(cc).makeols(); return(cut.ols[ol]); } private void buildcut(final Coord cc) { final Cut cut = geticut(cc); final int deftag = ++cut.deftag; cut.dmesh = Defer.later(new Defer.Callable<MapMesh>() { public MapMesh call() { Random rnd = new Random(id); rnd.setSeed(rnd.nextInt() ^ cc.x); rnd.setSeed(rnd.nextInt() ^ cc.y); return(MapMesh.build(MCache.this, rnd, ul.add(cc.mul(cutsz)), cutsz)); } }); } public void ivneigh(Coord nc) { Coord cc = new Coord(); for(cc.y = 0; cc.y < cutn.y; cc.y++) { for(cc.x = 0; cc.x < cutn.x; cc.x++) { if((((nc.x < 0) && (cc.x == 0)) || ((nc.x > 0) && (cc.x == cutn.x - 1)) || (nc.x == 0)) && (((nc.y < 0) && (cc.y == 0)) || ((nc.y > 0) && (cc.y == cutn.y - 1)) || (nc.y == 0))) { buildcut(new Coord(cc)); } } } } public void tick(int dt) { if(fo != null) { for(Collection<Gob> fol : fo) { for(Gob fo : fol) fo.ctick(dt); } } } private void invalidate() { for(int y = 0; y < cutn.y; y++) { for(int x = 0; x < cutn.x; x++) buildcut(new Coord(x, y)); } fo = null; for(Coord ic : new Coord[] { new Coord(-1, -1), new Coord( 0, -1), new Coord( 1, -1), new Coord(-1, 0), new Coord( 1, 0), new Coord(-1, 1), new Coord( 0, 1), new Coord( 1, 1)}) { Grid ng = grids.get(gc.add(ic)); if(ng != null) ng.ivneigh(ic.inv()); } } public void dispose() { for(Cut cut : cuts) { if(cut.mesh != null) cut.mesh.dispose(); if(cut.ols != null) { for(Rendered r : cut.ols) { if(r instanceof Disposable) ((Disposable)r).dispose(); } } } } public void fill(Message msg) { String mmname = msg.string().intern(); if(mmname.equals("")) mnm = null; else mnm = mmname; int[] pfl = new int[256]; while(true) { int pidx = msg.uint8(); if(pidx == 255) break; pfl[pidx] = msg.uint8(); } Message blob = msg.inflate(); id = blob.int64(); for(int i = 0; i < tiles.length; i++) tiles[i] = blob.uint8(); for(int i = 0; i < z.length; i++) z[i] = blob.int16(); for(int i = 0; i < ol.length; i++) ol[i] = 0; while(true) { int pidx = blob.uint8(); if(pidx == 255) break; int fl = pfl[pidx]; int type = blob.uint8(); Coord c1 = new Coord(blob.uint8(), blob.uint8()); Coord c2 = new Coord(blob.uint8(), blob.uint8()); int ol; if(type == 0) { if((fl & 1) == 1) ol = 2; else ol = 1; } else if(type == 1) { if((fl & 1) == 1) ol = 8; else ol = 4; } else if(type == 2) { ol = 16; } else { throw(new RuntimeException("Unknown plot type " + type)); } for(int y = c1.y; y <= c2.y; y++) { for(int x = c1.x; x <= c2.x; x++) { this.ol[x + (y * cmaps.x)] |= ol; } } } invalidate(); } } public MCache(Session sess) { this.sess = sess; } public void ctick(int dt) { synchronized(grids) { for(Grid g : grids.values()) { g.tick(dt); } } } public void invalidate(Coord cc) { synchronized(req) { if(req.get(cc) == null) req.put(cc, new Request()); } } public void invalblob(Message msg) { int type = msg.uint8(); if(type == 0) { invalidate(msg.coord()); } else if(type == 1) { Coord ul = msg.coord(); Coord lr = msg.coord(); trim(ul, lr); } else if(type == 2) { trimall(); } } private Grid cached = null; public Grid getgrid(Coord gc) { synchronized(grids) { if((cached == null) || !cached.gc.equals(cached)) { cached = grids.get(gc); if(cached == null) { request(gc); throw(new LoadingMap()); } } return(cached); } } public Grid getgridt(Coord tc) { return(getgrid(tc.div(cmaps))); } public int gettile(Coord tc) { Grid g = getgridt(tc); return(g.gettile(tc.sub(g.ul))); } public int getz(Coord tc) { Grid g = getgridt(tc); return(g.getz(tc.sub(g.ul))); } public float getcz(float px, float py) { float tw = tilesz.x, th = tilesz.y; Coord ul = new Coord(Utils.floordiv(px, tw), Utils.floordiv(py, th)); float sx = Utils.floormod(px, tw) / tw; float sy = Utils.floormod(py, th) / th; return(((1.0f - sy) * (((1.0f - sx) * getz(ul)) + (sx * getz(ul.add(1, 0))))) + (sy * (((1.0f - sx) * getz(ul.add(0, 1))) + (sx * getz(ul.add(1, 1)))))); } public float getcz(Coord pc) { return(getcz(pc.x, pc.y)); } public int getol(Coord tc) { Grid g = getgridt(tc); int ol = g.getol(tc.sub(g.ul)); for(Overlay lol : ols) { if(tc.isect(lol.c1, lol.c2.add(lol.c1.inv()).add(new Coord(1, 1)))) ol |= lol.mask; } return(ol); } public MapMesh getcut(Coord cc) { return(getgrid(cc.div(cutn)).getcut(cc.mod(cutn))); } public Collection<Gob> getfo(Coord cc) { return(getgrid(cc.div(cutn)).getfo(cc.mod(cutn))); } public Rendered getolcut(int ol, Coord cc) { return(getgrid(cc.div(cutn)).getolcut(ol, cc.mod(cutn))); } public void mapdata2(Message msg) { Coord c = msg.coord(); synchronized(grids) { synchronized(req) { if(req.containsKey(c)) { Grid g = grids.get(c); if(g == null) grids.put(c, g = new Grid(c)); g.fill(msg); req.remove(c); olseq++; } } } } public void mapdata(Message msg) { long now = System.currentTimeMillis(); int pktid = msg.int32(); int off = msg.uint16(); int len = msg.uint16(); Defrag fragbuf; synchronized(fragbufs) { if((fragbuf = fragbufs.get(pktid)) == null) { fragbuf = new Defrag(len); fragbufs.put(pktid, fragbuf); } fragbuf.add(msg.blob, 8, msg.blob.length - 8, off); fragbuf.last = now; if(fragbuf.done()) { mapdata2(fragbuf.msg()); fragbufs.remove(pktid); } /* Clean up old buffers */ for(Iterator<Map.Entry<Integer, Defrag>> i = fragbufs.entrySet().iterator(); i.hasNext();) { Map.Entry<Integer, Defrag> e = i.next(); Defrag old = e.getValue(); if(now - old.last > 10000) i.remove(); } } } public Resource tilesetr(int i) { synchronized(sets) { Resource res = (sets[i] == null)?null:(sets[i].get()); if(res == null) { if(nsets[i] == null) return(null); res = nsets[i].get(); sets[i] = new SoftReference<Resource>(res); } return(res); } } public Tileset tileset(int i) { synchronized(csets) { Tileset cset = (csets[i] == null)?null:(csets[i].get()); if(cset == null) { Resource res = tilesetr(i); if(res == null) return(null); try { cset = res.layer(Resource.tileset); } catch(Loading e) { throw(new LoadingMap(e)); } csets[i] = new SoftReference<Tileset>(cset); } return(cset); } } public Tiler tiler(int i) { synchronized(tiles) { Tiler tile = (tiles[i] == null)?null:(tiles[i].get()); if(tile == null) { Tileset set = tileset(i); if(set == null) return(null); tile = set.tfac().create(i, set); tiles[i] = new SoftReference<Tiler>(tile); } return(tile); } } public void tilemap(Message msg) { while(!msg.eom()) { int id = msg.uint8(); String resnm = msg.string(); int resver = msg.uint16(); nsets[id] = new Resource.Spec(resnm, resver); } } public void trimall() { synchronized(grids) { synchronized(req) { for(Grid g : grids.values()) g.dispose(); grids.clear(); req.clear(); } } } public void trim(Coord ul, Coord lr) { synchronized(grids) { synchronized(req) { for(Iterator<Map.Entry<Coord, Grid>> i = grids.entrySet().iterator(); i.hasNext();) { Map.Entry<Coord, Grid> e = i.next(); Coord gc = e.getKey(); Grid g = e.getValue(); if((gc.x < ul.x) || (gc.y < ul.y) || (gc.x > lr.x) || (gc.y > lr.y)) { g.dispose(); i.remove(); } } for(Iterator<Coord> i = req.keySet().iterator(); i.hasNext();) { Coord gc = i.next(); if((gc.x < ul.x) || (gc.y < ul.y) || (gc.x > lr.x) || (gc.y > lr.y)) i.remove(); } } } } public void request(Coord gc) { synchronized(req) { if(!req.containsKey(gc)) req.put(gc, new Request()); } } public void reqarea(Coord ul, Coord br) { ul = ul.div(cutsz); br = br.div(cutsz); Coord rc = new Coord(); for(rc.y = ul.y; rc.y <= br.y; rc.y++) { for(rc.x = ul.x; rc.x <= br.x; rc.x++) { try { getcut(new Coord(rc)); } catch(Loading e) {} } } } public void sendreqs() { long now = System.currentTimeMillis(); synchronized(req) { for(Iterator<Map.Entry<Coord, Request>> i = req.entrySet().iterator(); i.hasNext();) { Map.Entry<Coord, Request> e = i.next(); Coord c = e.getKey(); Request r = e.getValue(); if(now - r.lastreq > 1000) { r.lastreq = now; if(++r.reqs >= 5) { i.remove(); } else { Message msg = new Message(Session.MSG_MAPREQ); msg.addcoord(c); sess.sendmsg(msg); } } } } } }