/*
* 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.cmaps;
import static haven.MCache.tilesz;
import haven.Defer.Future;
import haven.MCache.LoadingMap;
import haven.minimap.Marker;
import haven.minimap.Radar;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import haven.resutil.RidgeTile;
import javax.imageio.ImageIO;
public class LocalMiniMap extends Window implements Console.Directory{
private static final String OPT_SZ = "_sz";
static Tex bg = Resource.loadtex("gfx/hud/bgtex");
public static final Resource plx = Resource.load("gfx/hud/mmap/x");
public final MapView mv;
private Coord cc = null;
public Coord cgrid = null;
private Coord off = new Coord();
boolean rsm = false;
boolean dm = false;
private static Coord gzsz = new Coord(15,15);
public int scale = 4;
private static final Coord minsz = new Coord(125, 125);
private static final double scales[] = {0.5, 0.66, 0.8, 0.9, 1, 1.25, 1.5, 1.75, 2};
private Coord sp;
private String session;
private final Map<String, Console.Command> cmdmap = new TreeMap<String, Console.Command>();
private boolean radarenabled = true;
private int height = 0;
private Future<BufferedImage> heightmap;
private Coord lastplg;
private final Coord hmsz = cmaps.mul(3);
private final Map<Coord, Future<MapTile>> cache = new LinkedHashMap<Coord, Defer.Future<MapTile>>(9, 0.75f, true) {
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(Map.Entry<Coord, Defer.Future<MapTile>> eldest) {
if(size() > 75) {
try {
MapTile t = eldest.getValue().get();
t.img.dispose();
} catch(RuntimeException e) {
}
return(true);
}
return(false);
}
};
public static class MapTile {
public final Tex img;
public final Coord ul, c;
public MapTile(Tex img, Coord ul, Coord c) {
this.img = img;
this.ul = ul;
this.c = c;
}
}
private BufferedImage tileimg(int t, BufferedImage[] texes) {
BufferedImage img = texes[t];
if (img == null) {
Resource r = ui.sess.glob.map.tilesetr(t);
if (r == null)
return (null);
Resource.Image ir = r.layer(Resource.imgc);
if (ir == null)
return (null);
img = ir.img;
texes[t] = img;
}
return (img);
}
public BufferedImage drawmap(Coord ul, Coord sz, boolean pretty) {
BufferedImage[] texes = new BufferedImage[256];
MCache m = ui.sess.glob.map;
BufferedImage buf = TexI.mkbuf(sz);
Coord c = new Coord();
for(c.y = 0; c.y < sz.y; c.y++) {
for(c.x = 0; c.x < sz.x; c.x++) {
Coord c2 = ul.add(c);
int t;
try{
t = m.gettile(c2);
} catch (LoadingMap e) {
return null;
}
try {
BufferedImage tex = tileimg(t, texes);
int rgb = 0xffff33ff;
if (tex != null) {
Coord tc = pretty?c2:c;
rgb = tex.getRGB(Utils.floormod(tc.x, tex.getWidth()), Utils.floormod(tc.y, tex.getHeight()));
}
buf.setRGB(c.x, c.y, rgb);
} catch (Loading e){
return null;
}
try {
if((m.gettile(c2.add(-1, 0)) > t) ||
(m.gettile(c2.add( 1, 0)) > t) ||
(m.gettile(c2.add(0, -1)) > t) ||
(m.gettile(c2.add(0, 1)) > t))
buf.setRGB(c.x, c.y, Color.BLACK.getRGB());
} catch (LoadingMap e) {
continue;
}
}
}
drawRidges(ul, sz, m, buf, c);
return(buf);
}
private void drawRidges(Coord ul, Coord sz, MCache m, BufferedImage buf, Coord c) {
for(c.y = 1; c.y < sz.y - 1; c.y++) {
for(c.x = 1; c.x < sz.x - 1; c.x++) {
int t = m.gettile(ul.add(c));
Tiler tl = m.tiler(t);
if(tl instanceof RidgeTile) {
if(((RidgeTile)tl).ridgep(m, ul.add(c))) {
for(int y = c.y; y <= c.y + 1; y++) {
for(int x = c.x; x <= c.x + 1; x++) {
int rgb = buf.getRGB(x, y);
rgb = (rgb & 0xff000000) |
(((rgb & 0x00ff0000) >> 17) << 16) |
(((rgb & 0x0000ff00) >> 9) << 8) |
(((rgb & 0x000000ff) >> 1) << 0);
buf.setRGB(x, y, rgb);
}
}
}
}
}
}
}
private Future<BufferedImage> getheightmap(final Coord plg){
Future<BufferedImage> f = Defer.later(new Defer.Callable<BufferedImage> () {
public BufferedImage call() {
return drawheightmap(plg);
}
});
return f;
}
public BufferedImage drawheightmap(Coord plg) {
MCache m = ui.sess.glob.map;
Coord ul = (plg.sub(1, 1)).mul(cmaps);
BufferedImage buf = TexI.mkbuf(hmsz);
Coord c = new Coord();
int MAX = Integer.MIN_VALUE;
int MIN = Integer.MAX_VALUE;
try{
for(c.y = 0; c.y < hmsz.y; c.y++) {
for(c.x = 0; c.x < hmsz.x; c.x++) {
Coord c2 = ul.add(c);
int t = m.getz(c2);
if(t > MAX) {MAX = t;}
if(t < MIN) {MIN = t;}
}
}
} catch (LoadingMap e) {
return null;
}
int SIZE = MAX - MIN;
for(c.y = 0; c.y < hmsz.y; c.y++) {
for(c.x = 0; c.x < hmsz.x; c.x++) {
Coord c2 = ul.add(c);
int t2 = m.getz(c2);
int t = Math.max(t2, MIN);
t = Math.min(t, MAX);
t = t - MIN;
if(SIZE>0){
t = (255*t)/SIZE;
t = t|(t<<8)|(t<<16)|height;
} else {
t = 0x00FFFFFF|height;
}
buf.setRGB(c.x, c.y, t);
try {
if((m.getz(c2.add(-1, 0)) > (t2+11)) ||
(m.getz(c2.add( 1, 0)) > (t2+11)) ||
(m.getz(c2.add(0, -1)) > (t2+11)) ||
(m.getz(c2.add(0, 1)) > (t2+11)))
buf.setRGB(c.x, c.y, Color.RED.getRGB());
} catch (LoadingMap e) {
continue;
}
}
}
return(buf);
}
public LocalMiniMap(Coord c, Coord sz, Widget parent, MapView mv) {
super(c, sz, parent, "mmap");
cap = null;
this.mv = mv;
cmdmap.put("radar", new Console.Command() {
public void run(Console console, String[] args) throws Exception {
if (args.length == 2) {
String arg = args[1];
if (arg.equals("on")) {
radarenabled = true;
return;
}
else if (arg.equals("off")) {
radarenabled = false;
return;
}
else if (arg.equals("reload")) {
ui.sess.glob.oc.radar.reload();
return;
}
}
throw new Exception("No such setting");
}
});
}
public Coord p2c(Coord pc) {
return(pc.div(tilesz).sub(cc).add(sz.div(2)));
}
public Coord c2p(Coord c) {
return(c.sub(sz.div(2)).add(cc).mul(tilesz).add(tilesz.div(2)));
}
public void drawicons(GOut g) {
OCache oc = ui.sess.glob.oc;
synchronized(oc) {
for(Gob gob : oc) {
try {
GobIcon icon = gob.getattr(GobIcon.class);
if(icon != null) {
Coord gc = p2c(gob.rc);
Tex tex = icon.tex();
g.image(tex, gc.sub(tex.sz().div(2)));
}
} catch(Loading l) {}
}
}
}
public Gob findicongob(Coord c) {
OCache oc = ui.sess.glob.oc;
synchronized(oc) {
for(Gob gob : oc) {
try {
GobIcon icon = gob.getattr(GobIcon.class);
if(icon != null) {
Coord gc = p2c(gob.rc);
Coord sz = icon.tex().sz();
if(c.isect(gc.sub(sz.div(2)), sz))
return(gob);
}
} catch(Loading l) {}
}
}
return(null);
}
@Override
protected void loadOpts() {
super.loadOpts();
sz = getOptCoord(OPT_SZ, sz);
}
public void toggleHeight(){
if(height == 0){
height = 0xb5000000;
} else if(height == 0xb5000000){
height = 0xff000000;
} else {
height = 0;
}
clearheightmap();
}
private void clearheightmap() {
if(heightmap != null && heightmap.done() && heightmap.get() != null){
heightmap.get().flush();
}
heightmap = null;
}
public void tick(double dt) {
Gob pl = ui.sess.glob.oc.getgob(mv.plgob);
if(pl == null) {
this.cc = null;
return;
}
this.cc = pl.rc.div(tilesz);
}
public void draw(GOut og) {
if(cc == null)
return;
final Coord plg = cc.div(cmaps);
checkSession(plg);
if(!plg.equals(lastplg)){
lastplg = plg;
clearheightmap();
}
if((height!=0) && (heightmap == null)){
heightmap = getheightmap(plg);
}
double scale = getScale();
Coord hsz = sz.div(scale);
Coord tc = cc.add(off.div(scale));
Coord ulg = tc.div(cmaps);
int dy = -tc.y + (hsz.y / 2);
int dx = -tc.x + (hsz.x / 2);
while((ulg.x * cmaps.x) + dx > 0)
ulg.x--;
while((ulg.y * cmaps.y) + dy > 0)
ulg.y--;
Coord s = bg.sz();
for(int y = 0; (y * s.y) < sz.y; y++) {
for(int x = 0; (x * s.x) < sz.x; x++) {
og.image(bg, new Coord(x*s.x, y*s.y));
}
}
GOut g = og.reclipl(og.ul.mul((1-scale)/scale), hsz);
g.gl.glPushMatrix();
g.gl.glScaled(scale, scale, scale);
Coord cg = new Coord();
synchronized(cache) {
for(cg.y = ulg.y; (cg.y * cmaps.y) + dy < hsz.y; cg.y++) {
for(cg.x = ulg.x; (cg.x * cmaps.x) + dx < hsz.x; cg.x++) {
Defer.Future<MapTile> f = cache.get(cg);
final Coord tcg = new Coord(cg);
final Coord ul = cg.mul(cmaps);
if((f == null) && (cg.manhattan2(plg) <= 1)) {
f = Defer.later(new Defer.Callable<MapTile>() {
public MapTile call() {
BufferedImage img = drawmap(ul, cmaps, true);
if(img == null) { return null; }
MapTile mapTile = new MapTile(new TexI(img), ul, tcg);
if(Config.store_map) {
img = drawmap(ul, cmaps, false);
store(img, tcg);
}
return mapTile;
}
});
cache.put(tcg, f);
}
if((f == null) || (!f.done())) {
continue;
}
MapTile mt = f.get();
if(mt == null){
cache.put(cg, null);
continue;
}
Tex img = mt.img;
g.image(img, ul.add(tc.inv()).add(hsz.div(2)));
}
}
}
Coord c0 = hsz.div(2).sub(tc);
if((height!=0) && (heightmap != null) && heightmap.done()){
BufferedImage img = heightmap.get();
if(img != null){
g.image(img, c0.add(plg.sub(1,1).mul(cmaps)));
} else {
clearheightmap();
}
}
drawmarkers(g, c0);
synchronized(ui.sess.glob.party.memb) {
try {
Tex tx = plx.layer(Resource.imgc).tex();
Coord negc = plx.layer(Resource.negc).cc;
for(Party.Member memb : ui.sess.glob.party.memb.values()) {
Coord ptc = memb.getc();
if(ptc == null)
continue;
ptc = c0.add(ptc.div(tilesz));
g.chcolor(memb.col);
g.image(tx, ptc.sub(negc));
g.chcolor();
}
} catch (Loading e){}
}
g.gl.glPopMatrix();
og.chcolor(SeasonImg.color);
Window.swbox.draw(og, Coord.z, this.sz);
og.chcolor();
}
private String mapfolder(){
return String.format("%s/map/%s/", Config.userhome, Config.server);
}
private String mapfile(String file){
return String.format("%s%s", mapfolder(), file);
}
private String mapsessfile(String file){
return String.format("%s%s/%s",mapfolder(), session, file);
}
private String mapsessfolder(){
return mapsessfile("");
}
private void store(BufferedImage img, Coord cg) {
if(!Config.store_map || img == null){return;}
Coord c = cg.sub(sp);
String fileName = mapsessfile(String.format("tile_%d_%d.png", c.x, c.y));
File outputfile = new File(fileName);
try {
ImageIO.write(img, "png", outputfile);
} catch (IOException e) {}
}
private void checkSession(Coord plg) {
if(cgrid == null || plg.manhattan(cgrid) > 5){
sp = plg;
synchronized (cache) {
for (Future<MapTile> v : cache.values()) {
if(v != null && v.done()) {
MapTile tile = v.get();
if(tile != null && tile.img != null) {
tile.img.dispose();
}
}
}
cache.clear();
}
session = Utils.current_date();
if(Config.store_map){
(new File(mapsessfolder())).mkdirs();
try {
Writer currentSessionFile = new FileWriter(mapfile("currentsession.js"));
currentSessionFile.write("var currentSession = '" + session + "';\n");
currentSessionFile.close();
} catch (IOException e) {}
}
}
cgrid = plg;
}
public double getScale() {
return scales[scale];
}
public void setScale(int scale) {
this.scale = Math.max(0,Math.min(scale,scales.length-1));
}
public boolean mousedown(Coord c, int button) {
parent.setfocus(this);
raise();
Marker m = getmarkerat(c);
Coord mc = uitomap(c);
if(button == 3){
if (m != null) {
mv.wdgmsg("click", this.c.add(c), mc, button, ui.modflags(), 0, (int)m.gob.id, m.gob.rc, 0, (-1));
return true;
}
dm = true;
ui.grabmouse(this);
doff = c;
return true;
}
if (button == 1) {
if (m != null || ui.modctrl) {
if(m != null && m.gob != null){
m.gob.setattr(new GobHighlight(m.gob));
}
mv.wdgmsg("click", Coord.z, mc, button, 0);
return true;
}
ui.grabmouse(this);
doff = c;
if(c.isect(sz.sub(gzsz), gzsz)) {
rsm = true;
return true;
}
}
return super.mousedown(c, button);
}
public boolean mouseup(Coord c, int button) {
if(button == 2){
off.x = off.y = 0;
return true;
}
if(button == 3){
dm = false;
ui.grabmouse(null);
return true;
}
if (rsm){
ui.grabmouse(null);
rsm = false;
storeOpt(OPT_SZ, sz);
} else {
super.mouseup(c, button);
}
return (true);
}
public void mousemove(Coord c) {
Coord d;
if(dm){
d = c.sub(doff);
off = off.sub(d);
doff = c;
return;
}
if (rsm){
d = c.sub(doff);
sz = sz.add(d);
sz.x = Math.max(minsz.x, sz.x);
sz.y = Math.max(minsz.y, sz.y);
doff = c;
//pack();
} else {
super.mousemove(c);
}
}
@Override
public boolean mousewheel(Coord c, int amount) {
if( amount > 0){
setScale(scale - 1);
} else {
setScale(scale + 1);
}
return true;
}
private void drawmarkers(GOut g, Coord tc) {
if (!radarenabled)
return;
Radar radar = ui.sess.glob.oc.radar;
try {
for (Marker m : radar.getMarkers()) {
if (m.template.visible)
m.draw(g, tc);
}
} catch (MCache.LoadingMap e) {
}
}
private Coord uitomap(Coord c) {
return c.sub(sz.div(2)).add(off).div(getScale()).mul(MCache.tilesz).add(mv.cc);
}
private Marker getmarkerat(Coord c) {
if (radarenabled) {
Radar radar = ui.sess.glob.oc.radar;
try {
Coord mc = uitomap(c);
for (Marker m : radar.getMarkers()) {
if (m.template.visible && m.hit(mc))
return m;
}
} catch (MCache.LoadingMap e) {
}
}
return null;
}
@Override
public Object tooltip(Coord c, boolean again) {
Marker m = getmarkerat(c);
if (m != null)
return m.template.tooltip;
return null;
}
@Override
public Map<String, Console.Command> findcmds() {
return cmdmap;
}
@Override
public boolean type(char key, KeyEvent ev) {
if(key == 27) {
return false;
}
return super.type(key, ev);
}
/*if(cc == null)
return(false);
MapView mv = getparent(GameUI.class).map;
if(mv == null)
return(false);
Gob gob = findicongob(c);
if(gob == null)
mv.wdgmsg("click", rootpos().add(c), c2p(c), button, ui.modflags());
else
mv.wdgmsg("click", rootpos().add(c), c2p(c), button, ui.modflags(), 0, (int)gob.id, gob.rc, 0, -1);
return(true);*/
}