Onionfighter wrote:I was hoping this would make a noise for anyone who happened to be nearby (similar to being able to hear stuff being dropped in water). Linking the trigger to the appearance of an item sprite might make it hard to distinguish between something that was dropped and something that appeared as you changed areas or moved around.
Yeah that would be a problem that you'd have to address if you wanted to make it like that. I don't have any good ideas on that yet.
Onionfighter wrote:I was thinking more along the lines of being able to hear other characters. I guess it would be more complicated to determine the speed of other characters or animals, but it would probably just work to loop the same sound that changes based on what terrain the sprite is over, and changes volume based on distance from center. Also, the loop could run concurrent with the movement animation loop.
If you add in other characters to this then before any audio clip is played for that gob you'd have to calculate its velocity (simple abs([(after_pos - before_pos) / time_between_frame]) ) and then map that velocity to a speed (round them for any measurement errors due to lag) and then you could play the audio clip for that player.
This would make you introduce a new variable to the Moving.java class i guess to keep track of previous position and current velocity. Very possible though.
this can be applied to animals as well that are moving since any Gob that moves will have a Moving.java Attribute when doing it.
Onionfighter wrote:I meant these mainly to indicate what other characters were doing. These might be difficult to trigger. You could use the sudden appearance of a new character, but how do you differentiate between summoned characters and characters logging on or entering from a house?
yeah in that case it's impossible imo to determine if they did that action or just walked into your draw range.
Onionfighter wrote:A window similar to the server messages pops up when kin requests are accepted or denied. This makes a sound redundant, but it could always be an option.
oh, shows how long since i've kinned anyone in game lol. that would make this easy then to do if wanted.
Onionfighter wrote: Or just any death sprite from an animal (would that be too unspecific? It could be triggered by the death cloud animation in that case).
death cloud animation sounds easier to do than checking for certain animal res's
Also fun magically stuff below:
Here's me taking a look at Audio.java and basically writing out everything in a simple mannner for someone to understand:
Note: read the `//' comments for my thought process on it as we go through
refer to :
https://github.com/boshaw/haven-client/ ... Audio.javaFirst you should note: volume is a `public static double'
what does this mean?
--> There's only 1 copy of the volume variable which controls the volume for all clips. Also volume appears to go from 0.0 -> 1.0 (multiple by 100 to get %)
now the audio clips are invoked via `play' method as seen in Session code so when looking further into that
Code A- Code: Select all
public static void play(final Indir<Resource> clip) {
//So we make a new anonymous class that extends Runnable and overrides method run
//and we call queue with this new object created from above
queue(new Runnable() {
public void run() {
//Gets the resource file
Resource r = clip.get();
if(r == null) // If it doesn't exist YET add it back to the queue to be looked at later
queue.add(this); // this allows for server to send you the Resource file since you don't have it yet
else//if resource has a clip then it plays it
playres(r);
}
});
}
which shoots this off to `playres' method
Code B- Code: Select all
private static void playres(Resource res) {
//Find all audio clips within the res : AUDIO LAYER of res
Collection<Resource.Audio> clips = res.layers(Resource.audio);
int s = (int)(Math.random() * clips.size()); //pick a random clip number from [0->clip.size()]
Resource.Audio clip = null;
//pick the clip to use based on s
for(Resource.Audio cp : clips) {
clip = cp;
if(--s < 0)
break;
}
play(clip.clip); //play it
}
So lets take a look at what the Audio layer looks like first:
Refer to :
https://github.com/boshaw/haven-client/ ... ource.java Resource.java
Lines 899 - 911
Code C- Code: Select all
public class Audio extends Layer {
//byte array to hold all data from ogg formated clip
transient public byte[] clip;
public Audio(byte[] buf) {
//This just reads in the clip based on the raw byte buffer from the *.res and then sends it through a decoder
try {
clip = Utils.readall(new VorbisDecoder(new ByteArrayInputStream(buf)));
} catch(IOException e) {
throw(new LoadException(e, Resource.this));
}
}
public void init() {}
}
So the next method we want to look at is `play' but this time we want the one that takes a byte[] rather than Indir<Res>
Code D- Code: Select all
public static void play(byte[] clip) {
play(clip, 1.0, 1.0);
}
//which then goes to:
public static void play(byte[] clip, double vol, double sp) {
play(new DataClip(new java.io.ByteArrayInputStream(clip), vol, sp));
}
Originally i thought dataclips weren't used but i was wrong, they are and they have volumes and `sp' for themselves as well, but the default volume and sp is 1.0 (100%) and everything defaults with the current setup in Session.java
Now it goes even further to yet another `play' method:
Code E- Code: Select all
//note: DataClip implements CS
public static void play(CS clip) {
synchronized(ncl) {
ncl.add(clip); //add the clip to some collection
}
ckpl();
}
`ckpl' method simply starts the player if it's not already started or if audio is not enabled it clears the `ncl' collection list
Ok so now this is the end of the line for the ADDING process of a new audio clip into the audio class, now we need to look at the `Player' which is an inner Audio class class
to be exact: the `run' method
Code F- Code: Select all
public void run() {
SourceDataLine line = null;
try {
try {
line = (SourceDataLine)AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, fmt));
line.open(fmt, bufsize);
line.start();
} catch(Exception e) {
e.printStackTrace();
return;
}
byte[] buf = new byte[1024];
while(true) { //This is where the action happens, the infinite loop of the player
if(Thread.interrupted()) //If game is exiting: method which exits the thread that it's in
throw(new InterruptedException());
synchronized(queuemon) {
//shallow copy the queue that we added to earlier
Collection<Runnable> queue = Audio.queue;
//basically erase all data in it so it doesn't loop through again unwanted
Audio.queue = new LinkedList<Runnable>();
for(Runnable r : queue) //for everything in that queue
r.run(); //call its `run' method refer to `Code A' to see what its `run' method looks like
}
//Now if you remember from the the whole bunch of method calls from the r.run() found in `Code A' -> `Code E'
// the final product is added to the `ncl' collection list
synchronized(ncl) {
//So for everything in ncl
for(CS cs : ncl)
//we add it to the clips collection
clips.add(cs);
//then we clear ncl for later use
ncl.clear();
}
fillbuf(buf, 0, 1024); //refer to `Code G' below
//below this wonderful for loop makes the audio actually play through some magical AudioSystem that I'm not going to go into
for(int off = 0; off < buf.length; off += line.write(buf, off, buf.length - off));
} //End action
} catch(InterruptedException e) {
} finally {
synchronized(Audio.class) {
player = null;
}
if(line != null)
line.close();
}
}
Code G- Code: Select all
//First of all:
//buf is a byte array of size 1024
//off is always 0
//len is always 1024; thus we cover the entire buffer
//nch is always 2 i think, not to sure about that
private void fillbuf(byte[] buf, int off, int len) {
//make 2 double arrays of length nch
double[] val = new double[nch];
double[] sm = new double[nch];
while(len > 0) {
//init val double array to 0.0 in all slots
for(int i = 0; i < nch; i++)
val[i] = 0;
//For every audio clip we have in clips collection
for(Iterator<CS> i = clips.iterator(); i.hasNext();) {
CS cs = i.next(); //get the audio clip
//the `get' method actually refers to `DataClip' class: method: `get'
//it's a very fun method that does magically things that idk about
//But what i do know is that it fills the double array sm with values needed from their audio clips
//Note: within this `get' method the audio clips personal volume DOES come into play
if(!cs.get(sm)) {
i.remove();
continue;
}
//those values filled by the audio clip then are added to the val double array slots
for(int ch = 0; ch < nch; ch++)
val[ch] += sm[ch];
}
//then this goes through and changes the values in buf due to values in val double array
//which is also where volume comes into play:
for(int i = 0; i < nch; i++) {
//notice the volume variable that we noted above as static blah blah 1.0
//Note: volume is really just the `master' volume whereas the volume find in each
//DataClip class is that sub clips volume so Audio does support different volumes!!!
int iv = (int)(val[i] * volume * 32767.0);
if(iv < 0) {
if(iv < -32768)
iv = -32768;
iv += 65536;
} else {
if(iv > 32767)
iv = 32767;
}
buf[off++] = (byte)(iv & 0xff);
buf[off++] = (byte)((iv & 0xff00) >> 8);
len -= 2;
}
}
}
Conclusions:
different volumes per clip appear to be possible if you call the right `play' methods
loftars default audio use [in Session.java] does not appear to bother with the custom volumes per clip
adding audio to the game through triggers is very possible with slight edits of the Audio class and then the code for each trigger of course.