diff --git a/build.xml b/build.xml index 79bca13f32..42aacd41ce 100644 --- a/build.xml +++ b/build.xml @@ -1,10 +1,10 @@ - + - + @@ -18,9 +18,10 @@ - + + source="1.8" target="1.8" includeantruntime="no" + excludesfile="render-exclude"> @@ -52,25 +53,27 @@ - - + + - + - + - - - + + + + + @@ -78,7 +81,8 @@ - + + @@ -87,7 +91,7 @@ - + @@ -97,11 +101,11 @@ - + - + diff --git a/doc/gpu-profiling b/doc/gpu-profiling new file mode 100644 index 0000000000..725796dc67 --- /dev/null +++ b/doc/gpu-profiling @@ -0,0 +1,34 @@ +GTX 750 + * Order fastmesh faces after min vertex ID per face + * Resource-loaded meshes on crop-heavy scene + * 25.2 ms -> 23.8 ms (1.4 ms, 5.9%) + * For mesh-bufs affecting probably only map-meshes + * Null (probably) + * Use FLOAT16 instead of FLOAT32 for vertex data + * Crop-heavy scene + * Null + * Brutally remove second half of all (resource-loaded) fastmesh faces + * Crop-heavy scene + * 25.4 -> 15.6 ms (9.8 ms, 63%) + * Same scene sees only 10% improvement from 37% resolution, so vertex-bound? + * Add more vertex-shader work (multiply pos/nrm with dummy ID uniform matrix) + * Crop-heavy scene + * Null + * Disable outlines + * Tree-heavy wilderness scene + * 8.5 -> 7.7 ms (0.8 ms, 10%) + * Crop-heavy scene + * 25.2 -> 24.7 ms (0.5 ms, 2%) + * Disable all terrain (incl. flavobjs) + * Tree-heavy wilderness scene + * 8.3 -> 4.2 ms (4.1 ms, 98%) + * Disable all gobs (not flavobjs) + * Tree-heavy wilderness scene + * 8.3 -> 5.2 ms (3.1 ms, 60%) + * Disable all alpha-testing (from materials) + * Tree-heavy wilderness scene + * 8.3 -> 6.9 ms (1.4 ms, 20%) + * Crop-heavy scene + * 25.2 -> 24.2 ms (1.0 ms, 4%) + * Disable color blending in all materials + * Null diff --git a/etc/findgl b/etc/findgl index cb6bce1059..178098c9d0 100755 --- a/etc/findgl +++ b/etc/findgl @@ -1,6 +1,6 @@ #!/usr/bin/python3 -import sys, zipfile +import sys, zipfile, fnmatch from classfile import file classpath=["build/jogl.jar"] @@ -51,13 +51,13 @@ classes = resolve(roots) for func in sys.argv[1:]: for cl in classes: for mth in cl.methods: - if cl.cp[mth.nm] == func: - sys.stdout.write("%s %s\n" % (func, cl.cp[cl.cp[cl.this].nm])) + if fnmatch.fnmatchcase(cl.cp[mth.nm], func): + sys.stdout.write("%s %s\n" % (cl.cp[mth.nm], cl.cp[cl.cp[cl.this].nm])) for fl in cl.fields: val = None if fl.const: val = cl.cp[fl.const] if isinstance(val, file.constint): val = val.val - if (cl.cp[fl.nm] == func) or (val is not None and str(val) == func): + if fnmatch.fnmatchcase(cl.cp[fl.nm], func) or (val is not None and str(val) == func): sys.stdout.write("%s %s %r\n" % (cl.cp[fl.nm], cl.cp[cl.cp[cl.this].nm], val)) diff --git a/etc/unsignjar b/etc/unsignjar new file mode 100755 index 0000000000..b9cdb25d9f --- /dev/null +++ b/etc/unsignjar @@ -0,0 +1,22 @@ +#!/bin/sh + +set -e + +if [ $# -lt 1 ]; then + echo "usage: unsignjar JAR-FILE..." >&2 + exit 1 +fi + +while [ $# -gt 0 ]; do + jarfile="$1"; shift + dir="$(mktemp -d /tmp/unsignXXXXXX)" + newmf="$(mktemp /tmp/manifestXXXXXX)" + trap 'rm -r "$dir"; rm "$newmf"' EXIT + + unzip -qd "$dir" "$jarfile" + sed -n '0,/^\r\?$/p' "$dir/META-INF/MANIFEST.MF" >"$newmf" + rm -r "$dir/META-INF" + newjar="$(mktemp /tmp/newjarXXXXXX)" + jar cfm "$newjar" "$newmf" -C "$dir" . + mv -f "$newjar" "$jarfile" +done diff --git a/lib/.gitignore b/lib/.gitignore index 43ad0d7c9a..ca8c34e2e6 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -1 +1,2 @@ -/haven-res.jar +/hafen-res.jar +/builtin-res.jar diff --git a/render-exclude b/render-exclude new file mode 100644 index 0000000000..4f13422646 --- /dev/null +++ b/render-exclude @@ -0,0 +1,13 @@ +haven/TexE.java +haven/Tex3D.java +haven/TexMS.java +haven/TexMSE.java +haven/FBConfig.java +haven/GBuffer.java +haven/HavenApplet.java +haven/resutil/EnvMap.java +haven/rs/AvaRender.java +haven/rs/BufView.java +haven/rs/GBuffer.java +haven/rs/Server.java +haven/TexIM.java diff --git a/src/haven/ActAudio.java b/src/haven/ActAudio.java index 730f39b989..f949a7f3c6 100644 --- a/src/haven/ActAudio.java +++ b/src/haven/ActAudio.java @@ -29,65 +29,110 @@ import java.util.*; import java.io.*; import java.lang.ref.WeakReference; +import haven.render.*; import haven.Audio.CS; import haven.Audio.VolAdjust; -public class ActAudio extends GLState.Abstract { - public static final GLState.Slot slot = new GLState.Slot(GLState.Slot.Type.SYS, ActAudio.class); - public final Channel pos = new Channel("pos"); - public final Channel amb = new Channel("amb"); +public class ActAudio extends State { + public static final Slot audio = new State.Slot<>(Slot.Type.SYS, ActAudio.class); + public final Channel pos; + public final Channel amb; private final Map global = new HashMap(); - public void prep(Buffer st) { - st.put(slot, this); + public haven.render.sl.ShaderMacro shader() {return(null);} + + public ActAudio(Root root) { + this.pos = new Adapter(root.pos); + this.amb = new Adapter(root.amb); + } + + public void apply(Pipe st) { + st.put(audio, this); + } + + public static interface Channel { + public void add(CS clip); + public void remove(CS clip); + public void clear(); } - public class Channel { + public static class Adapter implements Channel { + public final Channel parent; + private Collection clips = new HashSet(); + + public Adapter(Channel parent) { + this.parent = parent; + } + + public void add(CS clip) { + synchronized(this) { + if(clips != null) { + clips.add(clip); + parent.add(clip); + } + } + } + + public void remove(CS clip) { + synchronized(this) { + if(clips != null) { + clips.remove(clip); + parent.remove(clip); + } + } + } + + public void clear() { + synchronized(this) { + Collection clips = this.clips; + this.clips = null; + for(Iterator i = clips.iterator(); i.hasNext();) { + parent.remove(i.next()); + i.remove(); + } + } + } + } + + public static class RootChannel implements Channel { public final String name; public double volume; private Audio.VolAdjust volc = null; private Audio.Mixer mixer = null; - private final Collection clips = new ArrayList(); - private Channel(String name) { + private RootChannel(String name) { this.name = name; this.volume = Double.parseDouble(Utils.getpref("sfxvol-" + name, "1.0")); } - public void setvolume(double volume) { - this.volume = volume; - Utils.setpref("sfxvol-" + name, Double.toString(volume)); - } - - private void cycle() { - synchronized(clips) { - if(mixer != null) { - for(CS clip : mixer.current()) { - if(!clips.contains(clip)) - mixer.stop(clip); + public Audio.Mixer mixer() { + Audio.Mixer ret = this.mixer; + if(ret == null) { + synchronized(this) { + if((ret = this.mixer) == null) { + this.volc = new Audio.VolAdjust(ret = this.mixer = new Audio.Mixer(true)); + this.volc.vol = volume; + Audio.play(this.volc); } } - for(CS clip : clips) { - if(mixer == null) { - volc = new Audio.VolAdjust(mixer = new Audio.Mixer(true)); - volc.vol = volume; - Audio.play(volc); - } - if(!mixer.playing(clip)) - mixer.add(clip); - } - if(volc != null) - volc.vol = volume; - clips.clear(); } + return(ret); + } + + public void setvolume(double volume) { + if(volc != null) + volc.vol = volume; + this.volume = volume; + Utils.setpref("sfxvol-" + name, Double.toString(volume)); } public void clear() { - synchronized(clips) { + synchronized(this) { if(mixer != null) { Audio.stop(volc); - /* XXX: More likely, cycling should be fixed so as - * to not go on cycling a discarded actaudio. + /* XXX? clear() should only be called once, so + * ensure mixer isn't later mistakenly re-added + * due to racy code still using this channel. mixer = null; volc = null; */ @@ -96,19 +141,48 @@ public void clear() { } public void add(CS clip) { - synchronized(clips) { - clips.add(clip); + synchronized(this) { + mixer().add(clip); + } + } + + public void remove(CS clip) { + synchronized(this) { + if(mixer != null) + mixer.stop(clip); } } } + public static class Root { + public final RootChannel pos = new RootChannel("pos"); + public final RootChannel amb = new RootChannel("amb"); + + public void clear() { + pos.clear(); + amb.clear(); + } + } + public interface Global { public boolean cycle(ActAudio list); } - public static class PosClip implements Rendered { + public static Coord3f spos(Pipe st) { + Coord3f pos = Coord3f.o; + Location.Chain loc = st.get(Homo3D.loc); + if(loc != null) + pos = loc.fin(Matrix4f.id).mul4(pos); + Camera cam = st.get(Homo3D.cam); + if(cam != null) + pos = cam.fin(Matrix4f.id).mul4(pos); + return(pos); + } + + public static class PosClip implements RenderTree.Node, TickList.TickNode, TickList.Ticking { private final VolAdjust clip; - + private final Collection slots = new ArrayList<>(1); + public PosClip(VolAdjust clip) { this.clip = clip; } @@ -116,36 +190,42 @@ public PosClip(VolAdjust clip) { public PosClip(CS clip) { this(new VolAdjust(clip)); } - - public void draw(GOut g) { - g.apply(); - ActAudio list = g.st.cur(slot); - if(list != null) { - Coord3f pos = PView.mvxf(g).mul4(Coord3f.o); - double pd = Math.sqrt((pos.x * pos.x) + (pos.y * pos.y)); - this.clip.vol = Math.min(1.0, 50.0 / pd); - this.clip.bal = Utils.clip(Math.atan2(pos.x, -pos.z) / (Math.PI / 8.0), -1, 1); - list.pos.add(clip); - } + + public void added(RenderTree.Slot slot) { + ActAudio list = slot.state().get(audio); + if(list == null) + return; + slots.add(slot); + autotick(0); + list.pos.add(clip); } - public boolean setup(RenderList rl) { - return(true); + public void removed(RenderTree.Slot slot) { + ActAudio list = slot.state().get(audio); + if(list == null) + return; + slots.remove(slot); + list.pos.remove(clip); + } + + public TickList.Ticking ticker() {return(this);} + public void autotick(double dt) { + for(RenderTree.Slot slot : slots) { + Coord3f pos = spos(slot.state()); + this.clip.vol = Math.min(1.0, 50.0 / Math.hypot(pos.x, pos.y)); + this.clip.bal = Utils.clip(Math.atan2(pos.x, -pos.z) / (Math.PI / 8.0), -1, 1); + break; + } } } - public static class Ambience implements Rendered { + public static class Ambience implements RenderTree.Node { public final Resource res; public final double bvol; - private Glob glob = null; public Ambience(Resource res, double bvol) { - if(res.layer(Resource.audio, "amb") == null) { - /* This check is mostly just to make sure the resource - * is loaded and doesn't throw Loading exception in - * the setup routine. */ + if(res.layer(Resource.audio, "amb") == null) throw(new RuntimeException("No ambient clip found in " + res)); - } this.res = res; this.bvol = bvol; } @@ -157,9 +237,9 @@ public Ambience(Resource res) { public static class Glob implements Global { public final Resource res; private final VolAdjust clip; - private int n; - private double vacc; + private final Collection> active = new ArrayList<>(); private double lastupd = Utils.rtime(); + private boolean added = false; public Glob(Resource res) { this.res = res; @@ -181,44 +261,64 @@ public boolean equals(Object other) { return((other instanceof Glob) && (((Glob)other).res == this.res)); } + private double curvol() { + double acc = 0; + for(RenderList.Slot slot : active) { + Coord3f pos = spos(slot.state()); + double bvol = slot.obj().bvol; + double svol = Math.min(1.0, 50.0 / Math.hypot(pos.x, pos.y)); + acc += svol * bvol; + } + return(acc); + } + public boolean cycle(ActAudio list) { double now = Utils.rtime(); double td = Math.max(now - lastupd, 0.0); - if(vacc < clip.vol) - clip.vol = Math.max(clip.vol - (td * 0.5), 0.0); - else if(vacc > clip.vol) - clip.vol = Math.min(clip.vol + (td * 0.5), 1.0); - if((n == 0) && (clip.vol < 0.005)) - return(true); - vacc = 0.0; - n = 0; + synchronized(active) { + double vacc = curvol(); + if(vacc < clip.vol) + clip.vol = Math.max(clip.vol - (td * 0.5), 0.0); + else if(vacc > clip.vol) + clip.vol = Math.min(clip.vol + (td * 0.5), 1.0); + if(active.isEmpty() && (clip.vol < 0.005)) { + list.amb.remove(clip); + return(true); + } + } lastupd = now; - list.amb.add(clip); + if(!added) { + list.amb.add(clip); + added = true; + } return(false); } - public void add(double vol) { - vacc += vol; - n++; + public void add(RenderList.Slot slot) { + synchronized(active) { + active.add(slot); + } + } + + public void remove(RenderList.Slot slot) { + synchronized(active) { + active.remove(slot); + } } } - public void draw(GOut g) { - g.apply(); - if(glob == null) { - ActAudio list = g.st.cur(slot); - if(list == null) - return; - glob = list.intern(new Glob(res)); - } - Coord3f pos = PView.mvxf(g).mul4(Coord3f.o); - double pd = Math.sqrt((pos.x * pos.x) + (pos.y * pos.y)); - double svol = Math.min(1.0, 50.0 / pd); - glob.add(svol * bvol); + public void added(RenderTree.Slot slot) { + ActAudio list = slot.state().get(audio); + if(list == null) + return; + list.intern(new Glob(res)).add(slot.cast(Ambience.class)); } - public boolean setup(RenderList rl) { - return(true); + public void removed(RenderTree.Slot slot) { + ActAudio list = slot.state().get(audio); + if(list == null) + return; + list.intern(new Glob(res)).remove(slot.cast(Ambience.class)); } } @@ -236,8 +336,6 @@ public void cycle() { if(glob.cycle(this)) i.remove(); } - pos.cycle(); - amb.cycle(); } public void clear() { diff --git a/src/haven/AnimSprite.java b/src/haven/AnimSprite.java index 2ce7feb38a..eeafb55eec 100644 --- a/src/haven/AnimSprite.java +++ b/src/haven/AnimSprite.java @@ -27,16 +27,21 @@ package haven; import java.util.*; +import haven.render.*; public class AnimSprite extends Sprite { - private Rendered[] parts; - private MeshAnim.Anim[] anims; + private final RenderTree.Node[] parts; + private final MeshAnim.Anim[] anims; public static final Factory fact = new Factory() { public Sprite create(Owner owner, Resource res, Message sdt) { if(res.layer(MeshAnim.Res.class) == null) return(null); - return(new AnimSprite(owner, res, sdt)); + return(new AnimSprite(owner, res, sdt) { + public String toString() { + return(String.format("#", res.name)); + } + }); } }; @@ -50,7 +55,7 @@ private AnimSprite(Owner owner, Resource res, Message sdt) { } this.anims = anims.toArray(new MeshAnim.Anim[0]); MorphedMesh.Morpher.Factory morph = MorphedMesh.combine(this.anims); - Collection rl = new LinkedList(); + Collection rl = new LinkedList<>(); for(FastMesh.MeshRes mr : res.layers(FastMesh.MeshRes.class)) { if((mr.mat != null) && ((mr.id < 0) || (((1 << mr.id) & mask) != 0))) { boolean stat = true; @@ -60,30 +65,26 @@ private AnimSprite(Owner owner, Resource res, Message sdt) { break; } } - if(stat) + if(stat) { rl.add(mr.mat.get().apply(mr.m)); - else + } else { rl.add(mr.mat.get().apply(new MorphedMesh(mr.m, morph))); + } } } - parts = rl.toArray(new Rendered[0]); + parts = rl.toArray(new RenderTree.Node[0]); } - public boolean setup(RenderList rl) { - for(Rendered p : parts) - rl.add(p, null); - return(false); + public void added(RenderTree.Slot slot) { + for(RenderTree.Node p : parts) + slot.add(p); } - public boolean tick(int idt) { + public boolean tick(double ddt) { + float dt = (float)ddt; boolean ret = false; - float dt = idt / 1000.0f; for(MeshAnim.Anim anim : anims) ret = ret | anim.tick(dt); return(ret); } - - public Object staticp() { - return((anims.length == 0)?CONSTANS:null); - } } diff --git a/src/haven/Astronomy.java b/src/haven/Astronomy.java new file mode 100644 index 0000000000..eead469492 --- /dev/null +++ b/src/haven/Astronomy.java @@ -0,0 +1,47 @@ +/* + * This file is part of the Haven & Hearth game client. + * Copyright (C) 2009 Fredrik Tolf , and + * Björn Johannessen + * + * 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 , or write + * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package haven; + +import java.awt.Color; + +public class Astronomy { + public final double dt, mp, yt, sp, sd; + public final boolean night; + public final Color mc; + public final int is; + + public Astronomy(double dt, double mp, double yt, boolean night, Color mc, int is, double sp, double sd) { + this.dt = dt; + this.mp = mp; + this.yt = yt; + this.night = night; + this.mc = mc; + this.is = is; + this.sp = sp; + this.sd = sd; + } +} diff --git a/src/haven/AsyncCheck.java b/src/haven/AsyncCheck.java deleted file mode 100644 index 6096c72b71..0000000000 --- a/src/haven/AsyncCheck.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * This file is part of the Haven & Hearth game client. - * Copyright (C) 2009 Fredrik Tolf , and - * Björn Johannessen - * - * 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 , or write - * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307 USA - */ - -package haven; - -import java.util.*; - -public class AsyncCheck { - public final Object mon; - public final Source check; - public final Sink use; - public final String name; - private double timeout = 5; - private Thread th = null; - - public static interface Source { - public T get(boolean peek); - } - - public static interface Sink { - public void accept(T item) throws InterruptedException; - } - - public AsyncCheck(Object mon, String name, Source check, Sink use) { - this.mon = mon; - this.check = check; - this.use = use; - this.name = name; - } - - public static Source src(Iterable from) { - return(new Source() { - public T get(boolean peek) { - Iterator i = from.iterator(); - if(!i.hasNext()) - return(null); - T ret = i.next(); - if(!peek) - i.remove(); - return(ret); - } - }); - } - - private void checkloop() { - try { - while(true) { - T item; - synchronized(mon) { - double start = Utils.rtime(), now = start; - while(true) { - if((item = check.get(false)) != null) - break; - if((now - start) >= timeout) - return; - mon.wait((long)((timeout - (now - start)) * 1000) + 100); - now = Utils.rtime(); - } - } - use.accept(item); - } - } catch(InterruptedException e) { - } finally { - synchronized(mon) { - if(th == Thread.currentThread()) - th = null; - } - } - check(); - } - - public void check() { - synchronized(mon) { - if((check.get(true) != null) && (th == null)) { - th = new HackThread(this::checkloop, name); - th.setDaemon(true); - th.start(); - } - } - } - - public AsyncCheck timeout(double timeout) { - this.timeout = timeout; - return(this); - } -} diff --git a/src/haven/AudioSprite.java b/src/haven/AudioSprite.java index e939a16126..42fde54392 100644 --- a/src/haven/AudioSprite.java +++ b/src/haven/AudioSprite.java @@ -27,6 +27,7 @@ package haven; import java.util.*; +import haven.render.*; import haven.Audio.CS; public class AudioSprite { @@ -81,29 +82,24 @@ protected void eof() { }); } - public boolean setup(RenderList r) { - r.add(clip, null); - return(false); + public void added(RenderTree.Slot slot) { + slot.add(clip); } - public boolean tick(int dt) { + public boolean tick(double dt) { /* XXX: This is slightly bad, because virtual sprites that * are stuck as loading (by getting outside the map, for * instance), never play and therefore never get done, * effectively leaking. For now, this is seldom a problem - * because in practive most (all?) virtual audio-sprites + * because in practice most (all?) virtual audio-sprites * come from Skeleton.FxTrack which memoizes its origin * instead of asking the map for it, but also see comment * in glsl.MiscLib.maploc. Solve pl0x. */ return(done); } - - public Object staticp() { - return(CONSTANS); - } } - public static class RepeatSprite extends Sprite implements Gob.Overlay.CDel { + public static class RepeatSprite extends Sprite implements Sprite.CDel { private ActAudio.PosClip clip; private final Resource.Audio end; @@ -124,35 +120,31 @@ public CS cons() { this.clip = new ActAudio.PosClip(rep); } - public boolean setup(RenderList r) { + public void added(RenderTree.Slot slot) { if(clip != null) - r.add(clip, null); - return(false); + slot.add(clip); } - public boolean tick(int dt) { + public boolean tick(double dt) { return(clip == null); } public void delete() { - if(end != null) + if(end != null) { clip = new ActAudio.PosClip(new Audio.Monitor(end.stream()) { protected void eof() { super.eof(); RepeatSprite.this.clip = null; } }); - else + } else { clip = null; - } - - public Object staticp() { - return(CONSTANS); + } } } public static class Ambience extends Sprite { - public final Rendered amb; + public final RenderTree.Node amb; public Ambience(Owner owner, Resource res) { super(owner, res); @@ -163,13 +155,9 @@ public Ambience(Owner owner, Resource res) { this.amb = new ActAudio.Ambience(res); } - public boolean setup(RenderList r) { - r.add(amb, null); - return(false); - } - - public Object staticp() { - return(CONSTANS); + public void added(RenderTree.Slot slot) { + if(amb != null) + slot.add(amb); } } } diff --git a/src/haven/Avaview.java b/src/haven/Avaview.java index 7704cc8fc9..68a2c34939 100644 --- a/src/haven/Avaview.java +++ b/src/haven/Avaview.java @@ -28,7 +28,10 @@ import java.awt.Color; import java.util.*; +import haven.render.*; import haven.Composited.Desc; +import haven.Composited.MD; +import haven.Composited.ED; public class Avaview extends PView { public static final Tex missing = Resource.loadtex("gfx/hud/equip/missing"); @@ -38,6 +41,7 @@ public class Avaview extends PView { public Desc avadesc; public Resource.Resolver resmap = null; private Composited comp; + private RenderTree.Slot compslot; private List cmod = null; private List cequ = null; private final String camnm; @@ -62,6 +66,14 @@ public Avaview(Coord sz, long avagob, String camnm) { super(sz); this.camnm = camnm; this.avagob = avagob; + basic.add(new DirLight(Color.WHITE, Color.WHITE, Color.WHITE, new Coord3f(1, 1, 1).norm()), null); + makeproj(); + } + + protected void makeproj() { + float field = 0.5f; + float aspect = ((float)sz.y) / ((float)sz.x); + basic(Projection.class, Projection.frustum(-field, field, -aspect * field, aspect * field, 1, 5000)); } public void uimsg(String msg, Object... args) { @@ -104,6 +116,10 @@ private void initcomp(Composite gc) { if((comp == null) || (comp.skel != gc.comp.skel)) { comp = new Composited(gc.comp.skel); comp.eqowner = new AvaOwner(); + if(compslot != null) { + compslot.remove(); + compslot = null; + } } } @@ -111,23 +127,9 @@ private static Camera makecam(Resource base, Composited comp, String camnm) { Skeleton.BoneOffset bo = base.layer(Skeleton.BoneOffset.class, camnm); if(bo == null) throw(new Loading()); - GLState.Buffer buf = new GLState.Buffer(null); - bo.forpose(comp.pose).prep(buf); - return(new LocationCam(buf.get(PView.loc))); - } - - private Camera cam = null; - protected Camera camera() { - if(cam == null) - throw(new Loading()); - return(cam); - } - - protected void setup(RenderList rl) { - if(comp == null) - throw(new Loading()); - rl.add(comp, null); - rl.add(new DirLight(Color.WHITE, Color.WHITE, Color.WHITE, new Coord3f(1, 1, 1).norm()), null); + Pipe buf = new BufPipe(); + buf.prep(bo.forpose(comp.pose).get()); + return(new LocationCam(buf.get(Homo3D.loc))); } private Composite getgcomp() { @@ -143,37 +145,61 @@ private Composite getgcomp() { return(gc); } + private static List copy1(List in) { + List ret = new ArrayList<>(); + for(MD ob : in) + ret.add(ob.clone()); + return(ret); + } + + private static List copy2(List in) { + List ret = new ArrayList<>(); + for(ED ob : in) + ret.add(ob.clone()); + return(ret); + } + private Indir lbase = null; public void updcomp() { + /* XXX: This "retry mechanism" is quite ugly and should + * probably be rewritten to use the Loader instead. */ if(avagob != -1) { Composite gc = getgcomp(); if(gc == null) throw(new Loading()); initcomp(gc); - if((cam == null) || (gc.base != lbase)) - cam = makecam((lbase = gc.base).get(), comp, camnm); + if(gc.base != lbase) + basic(Camera.class, makecam((lbase = gc.base).get(), comp, camnm)); if(gc.comp.cmod != this.cmod) - comp.chmod(this.cmod = gc.comp.cmod); + comp.chmod(this.cmod = copy1(gc.comp.cmod)); if(gc.comp.cequ != this.cequ) - comp.chequ(this.cequ = gc.comp.cequ); + comp.chequ(this.cequ = copy2(gc.comp.cequ)); } else if(avadesc != null) { Desc d = avadesc; - if((d.base != lbase) || (cam == null) || (comp == null)) { + if((d.base != lbase) || (comp == null)) { lbase = d.base; comp = new Composited(d.base.get().layer(Skeleton.Res.class).s); comp.eqowner = new AvaOwner(); - cam = makecam(d.base.get(), comp, camnm); + basic(Camera.class, makecam(d.base.get(), comp, camnm)); + } + if(d.mod != this.cmod) { + comp.chmod(d.mod); + this.cmod = d.mod; } - if(d.mod != this.cmod) - comp.chmod(this.cmod = d.mod); - if(d.equ != this.cequ) - comp.chequ(this.cequ = d.equ); + if(d.equ != this.cequ) { + comp.chequ(d.equ); + this.cequ = d.equ; + } + } + if(compslot == null) { + compslot = basic.add(comp); } } public void tick(double dt) { + super.tick(dt); if(comp != null) - comp.tick((int)(dt * 1000)); + comp.tick(dt); } public void draw(GOut g) { diff --git a/src/haven/BuddyWnd.java b/src/haven/BuddyWnd.java index b66f0bf99b..88f5d35e08 100644 --- a/src/haven/BuddyWnd.java +++ b/src/haven/BuddyWnd.java @@ -30,7 +30,7 @@ import java.util.*; import java.text.Collator; -public class BuddyWnd extends Window implements Iterable { +public class BuddyWnd extends Widget implements Iterable { private List buddies = new ArrayList(); private Map idmap = new HashMap(); private BuddyList bl; @@ -38,12 +38,11 @@ public class BuddyWnd extends Window implements Iterable { private Button sbgroup; private Button sbstatus; private TextEntry pname, charpass, opass; - private Buddy editing = null; - private TextEntry nicksel; - private GroupSelector grpsel; private FlowerMenu menu; + private BuddyInfo info = null; + private Widget infof; public int serial = 0; - public static final int width = 200; + public static final int width = 263; public static final Tex online = Resource.loadtex("gfx/hud/online"); public static final Tex offline = Resource.loadtex("gfx/hud/offline"); public static final Color[] gc = new Color[] { @@ -90,7 +89,7 @@ public class Buddy { public int online; public int group; public boolean seen; - + public Buddy(int id, String name, int online, int group, boolean seen) { this.id = id; this.name = name; @@ -98,40 +97,64 @@ public Buddy(int id, String name, int online, int group, boolean seen) { this.group = group; this.seen = seen; } - + public void forget() { wdgmsg("rm", id); } - + public void endkin() { wdgmsg("rm", id); } - + public void chat() { wdgmsg("chat", id); } - + public void invite() { wdgmsg("inv", id); } - + public void describe() { wdgmsg("desc", id); } - + public void chname(String name) { wdgmsg("nick", id, name); } - + public void chgrp(int grp) { wdgmsg("grp", id, grp); } - + + private void chstatus(int status) { + online = status; + GameUI gui = getparent(GameUI.class); + if(gui != null) { + if(status == 1) + gui.msg(String.format("%s is now online.", name)); + } + } + public Text rname() { if((rname == null) || !rname.text.equals(name)) rname = Text.render(name); return(rname); } + + public Map opts() { + Map opts = new LinkedHashMap<>(); + if(online >= 0) { + opts.put("Chat", this::chat); + if(online == 1) + opts.put("Invite", this::invite); + opts.put("End kinship", this::endkin); + } else { + opts.put("Forget", this::forget); + } + if(seen) + opts.put("Describe", this::describe); + return(opts); + } } public Iterator iterator() { @@ -182,6 +205,115 @@ protected void changed(int group) { } } + private class BuddyInfo extends Widget { + private final Buddy buddy; + private final Avaview ava; + private final TextEntry nick; + private final GroupSelector grp; + private long atime, utime; + private Label atimel = null; + private Button[] opts = {}; + + private BuddyInfo(Coord sz, Buddy buddy) { + super(sz); + this.buddy = buddy; + this.ava = adda(new Avaview(Avaview.dasz, -1, "avacam"), sz.x / 2, 10, 0.5, 0); + this.nick = add(new TextEntry(sz.x - 20, buddy.name) { + {dshow = true;} + public void activate(String text) { + buddy.chname(text); + commit(); + } + }, 10, ava.c.y + ava.sz.y + 10); + this.grp = add(new GroupSelector(buddy.group) { + public void changed(int group) { + buddy.chgrp(group); + } + }, 15, nick.c.y + nick.sz.y + 10); + setopts(); + } + + public void draw(GOut g) { + g.chcolor(0, 0, 0, 128); + g.frect(Coord.z, sz); + g.chcolor(); + super.draw(g); + } + + public void tick(double dt) { + if((utime != 0) && (Utils.ntime() >= utime)) + setatime(); + } + + private void setatime() { + String text; + if(buddy.online == 1) { + this.utime = 0; + text = "Last seen: Now"; + } else { + int au, atime = (int)((long)Utils.ntime() - this.atime); + String unit; + if(atime >= (604800 * 2)) { + au = 604800; + unit = "week"; + } else if(atime >= 86400) { + au = 86400; + unit = "day"; + } else if(atime >= 3600) { + au = 3600; + unit = "hour"; + } else if(atime >= 60) { + au = 60; + unit = "minute"; + } else { + au = 1; + unit = "second"; + } + int am = atime / au; + this.utime = this.atime + ((am + 1) * au); + text = "Last seen: " + am + " " + unit + ((am > 1)?"s":"") + " ago"; + } + if(atimel != null) + ui.destroy(atimel); + atimel = add(new Label(text), 10, grp.c.y + grp.sz.y + 10); + } + + private void setopts() { + for(Button opt : this.opts) + ui.destroy(opt); + Map bopts = buddy.opts(); + List