/* * 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 static haven.MCache.tilesz; import haven.MapView.BorderCam; import haven.Resource.Tile; import haven.Resource.Tileset; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TreeMap; import java.util.zip.Inflater; public class MCache { Tileset[] sets = null; public Grid last = null; java.util.Map<Coord, Grid> req = new TreeMap<Coord, Grid>(); public java.util.Map<Coord, Grid> grids = new TreeMap<Coord, Grid>(); Session sess; Set<Overlay> ols = new HashSet<Overlay>(); public static final Coord tilesz = new Coord(11, 11); public static final Coord cmaps = new Coord(100, 100); Random gen; java.util.Map<Integer, Defrag> fragbufs = new TreeMap<Integer, Defrag>(); public static final Map<Integer, Color> colors = new TreeMap<Integer, Color>(); static { colors.put(0, new Color(0x3152a2)); // deep water colors.put(1, new Color(0x4480c8)); // shallow water colors.put(3, new Color(0xd85151)); // brick red colors.put(4, new Color(0xcba920)); // brick yellow colors.put(5, new Color(0x2a2a2a)); // brick black colors.put(6, new Color(0x5ab2f8)); // brick blue colors.put(7, new Color(0xe0e0e0)); // brick white colors.put(8, new Color(160, 160, 160)); // stone paving colors.put(9, new Color(200, 200, 200)); // plowed colors.put(10, new Color(0x497937)); // coniferous forest colors.put(11, new Color(0x60864f)); // broadleaf forest colors.put(12, new Color(220, 220, 200)); // thicket colors.put(13, new Color(0x468d37)); // grass colors.put(14, new Color(0xac7664)); // heath colors.put(15, new Color(0x999927)); // moor colors.put(16, new Color(0x60ad8a)); // swamp 1 colors.put(17, new Color(0x3d6242)); // swamp 2 colors.put(18, new Color(0x5e6453)); // swamp 3 colors.put(19, new Color(0xa67936)); // dirt colors.put(20, new Color(212, 164, 81)); // sand colors.put(21, new Color(212, 212, 212)); // house colors.put(22, new Color(85, 85, 85)); // house cellar colors.put(23, new Color(90, 90, 90)); // mine entry colors.put(24, new Color(80, 80, 80)); // mine colors.put(25, new Color(112, 116, 112)); // cave colors.put(26, new Color(125, 125, 125)); // mountain colors.put(255, new Color(0, 0, 0)); // void } public class Overlay { Set<Overlay> list; Coord c1, c2; int mask; public Overlay(Coord c1, Coord c2, int mask) { this(ols, c1, c2, mask); } public Overlay(Set<Overlay> set, Coord c1, Coord c2, int mask) { this.list = set; this.c1 = c1; this.c2 = c2; this.mask = mask; set.add(this); } public void destroy() { list.remove(this); list = null; } public void update(Coord c1, Coord c2) { this.c1 = c1; this.c2 = c2; } } public class Grid { public int tiles[][]; public Tile gcache[][]; // ground tile set public Tile tcache[][][]; public int ol[][]; Set<Overlay> ols = new HashSet<Overlay>(); Collection<Gob> fo = new LinkedList<Gob>(); // flavor objects boolean regged = false; public long lastreq = 0; public int reqs = 0; Coord gc; OCache oc = sess.glob.oc; String mnm; BufferedImage img; Tex tex; public Grid(Coord gc) { this.gc = gc; tiles = new int[cmaps.x][cmaps.y]; ol = new int[cmaps.x][cmaps.y]; gcache = new Tile[cmaps.x][cmaps.y]; tcache = new Tile[cmaps.x][cmaps.y][]; } public int gettile(Coord tc) { return (tiles[tc.x][tc.y]); } public int getol(Coord tc) { return (ol[tc.x][tc.y]); } public void remove() { if (regged) { oc.lrem(fo); regged = false; } } public void render() { img = TexI.mkbuf(cmaps); Graphics2D g = img.createGraphics(); Coord c = new Coord(); for (c.y = 0; c.y < cmaps.x; c.y++) { for (c.x = 0; c.x < cmaps.y; c.x++) { int id = tiles[c.x][c.y]; Color col = colors.get(id); if (col == null) { col = new Color(255, 0, 255); // System.out.println(id); } g.setColor(col); g.fillRect(c.x, c.y, 1, 1); } } } public Tex getTex() { if ((tex == null) && (img != null)) { tex = new TexI(img); } return tex; } public void makeflavor() { fo.clear(); Coord c = new Coord(0, 0); Coord tc = gc.mul(cmaps); for (c.y = 0; c.y < cmaps.x; c.y++) { for (c.x = 0; c.x < cmaps.y; c.x++) { Tileset set = sets[tiles[c.x][c.y]]; if (set.flavobjs.size() > 0) { Random rnd = mkrandoom(c); if (rnd.nextInt(set.flavprob) == 0) { Resource r = set.flavobjs.pick(rnd); Gob g = new Gob(sess.glob, c.add(tc).mul(tilesz), -1, 0); g.setattr(new ResDrawable(g, r)); fo.add(g); } } } } if (!regged) { oc.ladd(fo); regged = true; } } public int randoom(Coord c, int r) { return (MCache.this.randoom(c.add(gc.mul(cmaps)), r)); } public Random mkrandoom(Coord c) { return (MCache.this.mkrandoom(c.add(gc.mul(cmaps)))); } } private Tileset loadset(String name, int ver) { Resource res = Resource.load(name, ver); res.loadwait(); return (res.layer(Resource.tileset)); } public MCache(Session sess) { this.sess = sess; sets = new Tileset[256]; gen = new Random(); } private static void initrandoom(Random r, Coord c) { r.setSeed(c.x); r.setSeed(r.nextInt() ^ c.y); } public int randoom(Coord c) { int ret; synchronized (gen) { initrandoom(gen, c); ret = Math.abs(gen.nextInt()); return (ret); } } public int randoom(Coord c, int r) { return (randoom(c) % r); } public static Random mkrandoom(Coord c) { Random ret = new Random(); initrandoom(ret, c); return (ret); } private void replace(Grid g) { if (g == last) last = null; } public void invalidate(Coord cc) { synchronized (req) { if (req.get(cc) == null) req.put(cc, new Grid(cc)); } } private Coord prevPos = null; public void invalblob(Message msg, UI ui) { int type = msg.uint8(); if (type == 0) { invalidate(msg.coord()); } else if (type == 1) { if (Config.noborders && ui.mainview.cam instanceof BorderCam) { Gob p = ui.sess.glob.oc.getgob(ui.mainview.playergob); Coord curPos = null; if (p != null) { curPos = p.getc(); if (curPos != null && prevPos != null && curPos.dist(prevPos) > 30 * tilesz.x) ui.mainview.resetcam(); } prevPos = curPos; return; } Coord ul = msg.coord(); Coord lr = msg.coord(); trim(ul, lr); } else if (type == 2) { trimall(); } } public Tile[] gettrans(Coord tc) { Grid g; synchronized (grids) { Coord gc = tc.div(cmaps); if ((last != null) && last.gc.equals(gc)) g = last; else last = g = grids.get(gc); } if (g == null) return (null); Coord gtc = tc.mod(cmaps); if (g.tcache[gtc.x][gtc.y] == null) { int tr[][] = new int[3][3]; for (int y = -1; y <= 1; y++) { for (int x = -1; x <= 1; x++) { if ((x == 0) && (y == 0)) continue; int tn = gettilen(tc.add(new Coord(x, y))); if (tn < 0) return (null); tr[x + 1][y + 1] = tn; } } if (tr[0][0] >= tr[1][0]) tr[0][0] = -1; if (tr[0][0] >= tr[0][1]) tr[0][0] = -1; if (tr[2][0] >= tr[1][0]) tr[2][0] = -1; if (tr[2][0] >= tr[2][1]) tr[2][0] = -1; if (tr[0][2] >= tr[0][1]) tr[0][2] = -1; if (tr[0][2] >= tr[1][2]) tr[0][2] = -1; if (tr[2][2] >= tr[2][1]) tr[2][2] = -1; if (tr[2][2] >= tr[1][2]) tr[2][2] = -1; int bx[] = { 0, 1, 2, 1 }; int by[] = { 1, 0, 1, 2 }; int cx[] = { 0, 2, 2, 0 }; int cy[] = { 0, 0, 2, 2 }; ArrayList<Tile> buf = new ArrayList<Tile>(); for (int i = gettilen(tc) - 1; i >= 0; i--) { if ((sets[i] == null) || (sets[i].btrans == null) || (sets[i].ctrans == null)) continue; int bm = 0, cm = 0; for (int o = 0; o < 4; o++) { if (tr[bx[o]][by[o]] == i) bm |= 1 << o; if (tr[cx[o]][cy[o]] == i) cm |= 1 << o; } if (bm != 0) buf.add(sets[i].btrans[bm - 1].pick(randoom(tc))); if (cm != 0) buf.add(sets[i].ctrans[cm - 1].pick(randoom(tc))); } g.tcache[gtc.x][gtc.y] = buf.toArray(new Tile[0]); } return (g.tcache[gtc.x][gtc.y]); } public Tile getground(Coord tc) { Grid g; synchronized (grids) { Coord gc = tc.div(cmaps); if ((last != null) && last.gc.equals(gc)) g = last; else last = g = grids.get(gc); } if (g == null) return (null); Coord gtc = tc.mod(cmaps); if (g.gcache[gtc.x][gtc.y] == null) { Tileset ts = sets[g.gettile(gtc)]; g.gcache[gtc.x][gtc.y] = ts.ground.pick(randoom(tc)); } return (g.gcache[gtc.x][gtc.y]); } public int gettilen(Coord tc) { Grid g; synchronized (grids) { Coord gc = tc.div(cmaps); if ((last != null) && last.gc.equals(gc)) g = last; else last = g = grids.get(gc); } if (g == null) return (-1); return (g.gettile(tc.mod(cmaps))); } public Tileset gettile(Coord tc) { int tn = gettilen(tc); if (tn == -1) return (null); return (sets[tn]); } public int getol(Coord tc) { Grid g; synchronized (grids) { Coord gc = tc.div(cmaps); if ((last != null) && last.gc.equals(gc)) g = last; else last = g = grids.get(gc); } if (g == null) return (-1); int ol = g.getol(tc.mod(cmaps)); 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 void mapdata2(Message msg) { Coord c = msg.coord(); String mmname = msg.string().intern(); if (mmname.equals("")) mmname = null; int[] pfl = new int[256]; while (true) { int pidx = msg.uint8(); if (pidx == 255) break; pfl[pidx] = msg.uint8(); } Message blob = new Message(0); { Inflater z = new Inflater(); z.setInput(msg.blob, msg.off, msg.blob.length - msg.off); byte[] buf = new byte[10000]; while (true) { try { int len; if ((len = z.inflate(buf)) == 0) { if (!z.finished()) throw (new RuntimeException("Got unterminated map blob")); break; } blob.addbytes(buf, 0, len); } catch (java.util.zip.DataFormatException e) { throw (new RuntimeException("Got malformed map blob", e)); } } } synchronized (req) { synchronized (grids) { if (req.containsKey(c)) { int i = 0; Grid g = req.get(c); g.mnm = mmname; for (int y = 0; y < cmaps.y; y++) { for (int x = 0; x < cmaps.x; x++) { g.tiles[x][y] = blob.uint8(); } } for (int y = 0; y < cmaps.y; y++) { for (int x = 0; x < cmaps.x; x++) g.ol[x][y] = 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 { 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++) { g.ol[x][y] |= ol; } } new Overlay(g.ols, c1, c2, ol); } req.remove(c); g.makeflavor(); if (grids.containsKey(c)) { grids.get(c).remove(); replace(grids.remove(c)); } grids.put(c, g); g.render(); } } } } 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 void tilemap(Message msg) { while (!msg.eom()) { int id = msg.uint8(); String resnm = msg.string(); int resver = msg.uint16(); if (id == 11 && Config.broadleafTile) { // new resnm = "gfx/tiles/wald/leaf"; resver = 6; } sets[id] = loadset(resnm, resver); } } public void trimall() { synchronized (req) { synchronized (grids) { for (Grid g : req.values()) g.remove(); for (Grid g : grids.values()) g.remove(); grids.clear(); req.clear(); } } synchronized (sess.ui.slen.mini.caveTex) { sess.ui.slen.mini.caveTex.clear(); } sess.ui.mainview.resetcam(); } public void trim(Coord ul, Coord lr) { synchronized (grids) { 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)) { i.remove(); g.remove(); } } } synchronized (req) { for (Iterator<Map.Entry<Coord, Grid>> i = req.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)) { i.remove(); g.remove(); } } } } public void request(Coord gc) { synchronized (req) { if (!req.containsKey(gc)) req.put(gc, new Grid(gc)); } } public void sendreqs() { long now = System.currentTimeMillis(); synchronized (req) { for (Iterator<Map.Entry<Coord, Grid>> i = req.entrySet().iterator(); i.hasNext();) { Map.Entry<Coord, Grid> e = i.next(); Coord c = e.getKey(); Grid gr = e.getValue(); if (now - gr.lastreq > 1000) { gr.lastreq = now; if (++gr.reqs >= 5) { i.remove(); } else { Message msg = new Message(Session.MSG_MAPREQ); msg.addcoord(c); sess.sendmsg(msg); } } } } } }