Compare commits

...
Sign in to create a new pull request.

44 commits

Author SHA1 Message Date
Hendrik Brummermann
d122d2f312 set version number to 0.81.3 2010-03-16 17:36:42 +00:00
Hendrik Brummermann
63f75892e1 added 0.81.3 change log entry: prevent crash on "No line matching interface" (fixes #2970406) 2010-03-16 17:33:31 +00:00
Hendrik Brummermann
0d96d840e1 prevent crash on "No line matching interface" (fixes #2970406) 2010-03-16 17:31:07 +00:00
Hendrik Brummermann
d266544259 change log for 0.81.2 2010-03-04 22:52:36 +00:00
Hendrik Brummermann
33bd04089d updated version number 2010-03-04 21:43:41 +00:00
Hendrik Brummermann
b9f1705f98 updated marauroa 2010-03-04 21:41:21 +00:00
Hendrik Brummermann
b9f9da1b2b log uncaught exceptions (fixes #2963720) 2010-03-04 21:35:32 +00:00
Hendrik Brummermann
68b202feab log residual entities as debug 2010-03-04 21:24:50 +00:00
Hendrik Brummermann
72e5f94fc2 added try catch blocks 2010-03-04 21:13:08 +00:00
Hendrik Brummermann
9c7e4016a2 fixed NullPointerException on failing ImageIO.read for existing files. 2010-03-04 21:09:09 +00:00
Hendrik Brummermann
5481e959ea backported try-catch blocks 2010-03-04 21:06:39 +00:00
Hendrik Brummermann
db5314d95c backported try-catch-block around view.draw() 2010-03-04 21:01:15 +00:00
Hendrik Brummermann
bbbce08552 don't crash on exception thrown by the sound system on init. 2010-03-02 20:02:16 +00:00
Hendrik Brummermann
acd2dfa173 updated CHANGES.txt 2010-02-24 01:43:57 +00:00
Hendrik Brummermann
47af4d3b82 increase 0 hp to 1 hp on login (fixes #2955566) 2010-02-24 01:38:21 +00:00
Hendrik Brummermann
7f39d3f3a4 changed base version for update to 0.81 2010-02-23 21:45:14 +00:00
Hendrik Brummermann
b125e64b71 - updated to Marauroa 3.5 to fix an issue with null-values in the timedate columns in gameEvents and account 2010-02-23 21:07:57 +00:00
Hendrik Brummermann
47489f8805 updated marauroa.jar to 3.5 2010-02-23 21:05:37 +00:00
Hendrik Brummermann
573e7525e1 prevent crash on empty soundNameList (thanks to samsat for reporting it) 2010-02-22 20:07:50 +00:00
Hendrik Brummermann
4ce712f90f prevent crash on empty soundNameList (thanks to samsat for reporting it) 2010-02-22 20:02:41 +00:00
Hendrik Brummermann
4f368781a5 backported using of admins.txt instead of admins.list by kymara 2010-02-21 17:02:10 +00:00
Hendrik Brummermann
77c090b027 added h2.jar to classpath 2010-02-21 16:51:16 +00:00
Hendrik Brummermann
3af7a3de81 add . to classpath 2010-02-21 16:50:11 +00:00
Hendrik Brummermann
a736031491 increased version number 2010-02-21 16:49:27 +00:00
Hendrik Brummermann
b8b9e40ef4 backported sound system changes by plassy 2010-02-19 19:14:50 +00:00
Hendrik Brummermann
558cc536aa backported current state of sound system 2010-02-19 06:53:41 +00:00
Kimmo Rundelin
061a9c90e2 converted to ogg 2010-02-18 21:47:30 +00:00
Hendrik Brummermann
8a1315a96b fixed logout while dying bug 2010-02-18 18:20:20 +00:00
Kimmo Rundelin
34e51b64f6 added ignored player drawing 2010-02-17 23:35:11 +00:00
Hendrik Brummermann
02319cbac9 increased volume of dungeon sound 2010-02-17 21:22:03 +00:00
Hendrik Brummermann
6f20d5ddb6 backport of fixed NullPointerException on non existing files by plassy (fixes #2953740) 2010-02-17 20:55:49 +00:00
Hendrik Brummermann
0f6a9dcd01 backported "Draw ignored players with DstOut composition" by kiheru 2010-02-17 20:33:53 +00:00
Hendrik Brummermann
34b1865a53 backported hint about changing zone one /mute 2010-02-15 21:30:39 +00:00
Hendrik Brummermann
4794177320 fixed thousands of NullPointerExceptions in the chat log on both Java Sun 5 and Java Sun 6. OpenJDK was not affected. 2010-02-15 21:17:20 +00:00
Kimmo Rundelin
d11ef4bdd7 backport of screen size calculation fix 2010-02-14 21:25:37 +00:00
Katie Russell
cf5413d6a1 Moving the tables outside the tavern one tile -> 2010-02-14 21:04:32 +00:00
Kimmo Rundelin
cbdc147d4d backport of divider location patch 2010-02-14 20:17:51 +00:00
Hendrik Brummermann
61131b8810 made layer in SoundEvent a required field server side and use infinite radius as default (fixes #2951610) 2010-02-14 16:54:42 +00:00
Hendrik Brummermann
ac309393aa added commands /manual and /faq 2010-02-14 16:36:38 +00:00
Kimmo Rundelin
4a977c5ed4 backport of netbook grey screen fix 2010-02-13 20:00:15 +00:00
Hendrik Brummermann
7f63885bc3 added wind sound originally used in semos village 2010-02-12 18:40:32 +00:00
Hendrik Brummermann
c8d62e9db4 added tavern background music to semos city, village, bank in addition to tavern, and forsaken_mine to semos dungeon 2010-02-12 18:40:12 +00:00
Hendrik Brummermann
f668fc51c9 workaround for volume control not working on first playing of a sound. 2010-02-12 18:36:34 +00:00
Hendrik Brummermann
6d52587f4e adjusted version numbers 2010-02-11 20:19:26 +00:00
59 changed files with 1695 additions and 1039 deletions

View file

@ -75,8 +75,8 @@ updates_server = http://arianne.sourceforge.net/stendhal/updates
version_server = http://arianne.sourceforge.net/stendhal.version
# current version of stendhal
version.old = 0.80
version = 0.80.5
version.old = 0.81
version = 0.81.3
# javac options
javac.deprecation = true

View file

@ -214,7 +214,7 @@
<jar jarfile="${lib}/${server_jarname}" basedir="${build_server}">
<manifest>
<attribute name="Main-Class" value="marauroa.server.marauroad"/>
<attribute name="Class-path" value="marauroa.jar ${maps_jarname} ${xmlconf_jarname} ${maps_jarname} commons-lang.jar log4j.jar groovy.jar ${mysqldriver_jarname} mysql.jar h2.jar"/>
<attribute name="Class-path" value=". marauroa.jar ${maps_jarname} ${xmlconf_jarname} ${maps_jarname} commons-lang.jar log4j.jar groovy.jar ${mysqldriver_jarname} mysql.jar h2.jar"/>
</manifest>
</jar>
@ -514,10 +514,13 @@
<include name="*.html"/>
</fileset>
</copy>
<copy todir="${buildroot}/stendhal-${version}/doc">
<copy todir="${buildroot}/stendhal-server-${version}/doc">
<fileset dir="doc"/>
<fileset dir="." includes="LICENSE.txt"/>
</copy>
<copy todir="${buildroot}/stendhal-server-${version}/data/conf" file="data/conf/admins.txt"/>
<copy todir="${buildroot}/stendhal-server-${version}" file="README.txt"/>
<mkdir dir="${buildroot}/stendhal-server-${version}/log" />
<touch file="${buildroot}/stendhal-server-${version}/log/empty" />

0
data/conf/admins.txt Normal file
View file

View file

@ -11,6 +11,14 @@
<zone name="7_kikareukin_clouds" level="7" x="499616" y="499744" file="Level 7/kikareukin/clouds.tmx">
<!--Don't allow teleporting in as we don't want marked scrolls made or ring abused-->
<configurator class-name="games.stendhal.server.core.config.zone.NoTeleportIn"/>
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.LoopedAmbientSoundSource">
<parameter name="sound">wind-2</parameter>
<parameter name="volume">20</parameter>
</implementation>
</entity>
<entity x="0" y="0">
<implementation class-name="games.stendhal.server.entity.mapstuff.area.MessagingArea">
<parameter name="coversZone">true</parameter>
@ -31,6 +39,14 @@
<!-- Level 6 -->
<zone name="6_kikareukin_islands" level="6" x="499616" y="499744" file="Level 6/kikareukin/islands.tmx">
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.LoopedAmbientSoundSource">
<parameter name="sound">wind-2</parameter>
<parameter name="volume">20</parameter>
</implementation>
</entity>
<entity x="0" y="0">
<implementation class-name="games.stendhal.server.entity.mapstuff.area.MessagingArea">
<parameter name="coversZone">true</parameter>

View file

@ -360,12 +360,20 @@ The sharper minded amongst you might prefer to double-click directly on the crea
</portal>
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.LoopedAmbientSoundSource">
<parameter name="sound">wind-2</parameter>
<parameter name="volume">25</parameter>
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_old_tavern</parameter>
<parameter name="volume">20</parameter>
</implementation>
</entity>
<!--
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.LoopedAmbientSoundSource">
<parameter name="sound">wind-2</parameter>
<parameter name="volume">5</parameter>
</implementation>
</entity>
-->
<entity x="13" y="42">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.PeriodicAmbientSoundSource">
<parameter name="sound">treecreak-1</parameter>
@ -503,6 +511,13 @@ Mature sheep wanted: please speak to Sato.
<destination zone="int_semos_bakery" ref="entrance" />
</portal>
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_old_tavern</parameter>
<parameter name="volume">20</parameter>
</implementation>
</entity>
<!-- birds and chicken -->
<entity x="29" y="8">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.PeriodicAmbientSoundSource">
@ -709,7 +724,7 @@ Shepherds please note: keep watch for the wolves while searching for berries her
</entity>
<!-- table on the left -->
<entity x="44" y="39">
<entity x="45" y="39">
<implementation
class-name="games.stendhal.server.entity.mapstuff.area.WalkBlocker" />
<attribute name="description">You see a nice clean table for resting your drinks on.</attribute>
@ -718,7 +733,7 @@ Shepherds please note: keep watch for the wolves while searching for berries her
</entity>
<!-- table on the right -->
<entity x="47" y="39">
<entity x="48" y="39">
<implementation
class-name="games.stendhal.server.entity.mapstuff.area.WalkBlocker" />
<attribute name="description">You see a nice clean table for resting your drinks on.</attribute>
@ -737,6 +752,7 @@ Shepherds please note: keep watch for the wolves while searching for berries her
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_old_tavern</parameter>
<parameter name="volume">80</parameter>
</implementation>
</entity>
@ -1109,6 +1125,13 @@ Holy mackerel! These chips are tasty.
class-name="games.stendhal.server.entity.mapstuff.portal.OneWayPortalDestination" />
</portal>
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_old_tavern</parameter>
<parameter name="volume">20</parameter>
</implementation>
</entity>
<entity x="9" y="23">
<implementation
class-name="games.stendhal.server.maps.semos.bank.CustomerAdvisorNPC">
@ -1954,15 +1977,35 @@ Mature sheep wanted: please speak to Sato
<configurator
class-name="games.stendhal.server.maps.semos.caves.BabyDragonSellerNPC" />
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
<zone name="-1_kotoch_entrance_n" level="-1" x="499744" y="500000"
file="Level -1/kotoch/entrance_n.tmx">
<configurator class-name="games.stendhal.server.maps.kotoch.SmithNPC" />
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
<zone name="-1_semos_dungeon" level="-1" x="500064" y="500000"
file="Level -1/semos/dungeon.tmx">
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
<zone name="-1_semos_jail" level="-1" x="499872" y="500000"
@ -2004,10 +2047,24 @@ Mature sheep wanted: please speak to Sato
<zone name="-1_semos_catacombs_ne" level="-1" x="500064" y="499872"
file="Level -1/semos/catacombs_ne.tmx">
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
<zone name="-1_semos_catacombs_nw" level="-1" x="500000" y="499872"
file="Level -1/semos/catacombs_nw.tmx">
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
<zone name="-1_semos_catacombs_se" level="-1" x="500064" y="499936"
@ -2015,10 +2072,24 @@ Mature sheep wanted: please speak to Sato
<portal x="28" y="37" ref="entrance" replacing="true">
<destination zone="0_semos_plains_n" ref="catacombs_entrance" />
</portal>
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
<zone name="-1_semos_catacombs_sw" level="-1" x="500000" y="499936"
file="Level -1/semos/catacombs_sw.tmx">
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
@ -2091,6 +2162,13 @@ Mature sheep wanted: please speak to Sato
<zone name="-2_semos_dungeon" level="-2" x="500096" y="500032"
file="Level -2/semos/dungeon.tmx">
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
<zone name="-2_semos_jail" level="-2" x="499872" y="500000"
@ -2296,6 +2374,13 @@ Mature sheep wanted: please speak to Sato
<zone name="-3_semos_dungeon" level="-3" x="500096" y="500032"
file="Level -3/semos/dungeon.tmx">
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
</zone>
<zone name="-3_semos_catacombs" level="-3" x="500064" y="499872"
@ -2305,6 +2390,14 @@ Mature sheep wanted: please speak to Sato
<portal x="30" y="57" ref="upstairs" replacing="true">
<destination zone="-2_semos_catacombs" ref="downstairs" />
</portal>
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
<entity x="60" y="61">
<!--
all dialogs for this NPC are quest-related, so we don't need a
@ -2328,6 +2421,14 @@ Mature sheep wanted: please speak to Sato
<zone name="-4_semos_dungeon" level="-4" x="500096" y="500064"
file="Level -4/semos/dungeon.tmx">
<entity x="1" y="1">
<implementation class-name="games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource">
<parameter name="sound">the_forsaken_mine</parameter>
<parameter name="volume">40</parameter>
</implementation>
</entity>
<entity x="67" y="13">
<implementation
class-name="games.stendhal.server.maps.semos.dungeon.SheepBuyerNPC">

BIN
data/sounds/creak-1.ogg Normal file

Binary file not shown.

BIN
data/sounds/fire-2.ogg Normal file

Binary file not shown.

BIN
data/sounds/frog-1.ogg Normal file

Binary file not shown.

BIN
data/sounds/gorilla-4.ogg Normal file

Binary file not shown.

BIN
data/sounds/kettle-1.ogg Normal file

Binary file not shown.

View file

@ -1,5 +1,32 @@
Changelog
---------
0.81.3
- prevent crash on "No line matching interface" (fixes #2970406)
0.81.2
*bug fixes*
- fixed a crash on Linux using alsa (non pulse) audio drivers and Sun Java (not OpenJDK)
if all channels are in use by another application
- fixed rare InvalidVersionExceptions in the network code on high latency connections
(for example for people living outside Europe to an server in Europe)
- handle failure to load sprite images more gracefully
- improved logging of uncaught exceptions
0.81.2 contains the first results of the Client Quality Weeks. Thanks everyone for providing
your error log files and pleace continue to do so.
0.81.1
*bug fixes*
- fixed an issue with 0 hp created by logging out while dying (thanks to Andrzej Kofański)
- prevent client crash on empty soundNameList
- renamed admins.list to admins.txt to simplify installation on windows with hidden file name extensions
- fixed a server side classpath issue
- updated to Marauroa 3.5 to fix an issue with null-values in the timedate columns in gameEvents and account
0.81
*features*
- player stats moved from the character window to the side panel
@ -8,6 +35,7 @@ Changelog
- having multiple correct answers for a riddle now possible
- athor labyrinth redrawn
- h2 database support
- ignored players are drawn as black forms
*bug fixes*
- numerous small map fixes
@ -18,6 +46,7 @@ Changelog
- feedback on using portals and chests from too far away
- fixed "Drag and drop sometimes misses slots and hits ground instead" (was for drag and drop onto same slot)
- better reject message for inactive accounts
- fixed leander stealing all the pizzas of a player who has failed the quest previously
*code*
- sound system rewritten and using oggs
@ -31,6 +60,8 @@ Changelog
0.80.1: server only release
* fixed logout while dying
0.80
*features*

Binary file not shown.

View file

@ -1,3 +1,3 @@
set STENDHAL_VERSION=0.80.5
set LOCALCLASSPATH=.;data\script;data\conf;stendhal-server-%STENDHAL_VERSION%.jar;marauroa.jar;mysql-connector.jar;log4j.jar;commons-lang.jar
set STENDHAL_VERSION=0.81.3
set LOCALCLASSPATH=.;data\script;data\conf;stendhal-server-%STENDHAL_VERSION%.jar;marauroa.jar;mysql-connector.jar;log4j.jar;commons-lang.jar;h2.jar
java -Xmx400m -cp "%LOCALCLASSPATH%" marauroa.server.marauroad -c server.ini -l

View file

@ -1,7 +1,7 @@
#!/bin/sh
STENDHAL_VERSION="0.80.5"
STENDHAL_VERSION="0.81.3"
LOCALCLASSPATH=.:data/script/:data/conf/:stendhal-server-$STENDHAL_VERSION.jar:marauroa.jar:mysql-connector.jar:log4j.jar:commons-lang.jar
LOCALCLASSPATH=.:data/script/:data/conf/:stendhal-server-$STENDHAL_VERSION.jar:marauroa.jar:mysql-connector.jar:log4j.jar:commons-lang.jar:h2.jar
java -Xmx400m -cp "${LOCALCLASSPATH}" marauroa.server.marauroad -c server.ini -l

View file

@ -110,7 +110,7 @@ public class GameObjects implements RPObjectChangeListener, Iterable<IEntity> {
while (it.hasNext()) {
final IEntity entity = it.next();
logger.error("Residual entity: " + entity);
logger.debug("Residual entity: " + entity);
entity.release();
}

View file

@ -56,6 +56,8 @@ import org.apache.log4j.Logger;
* can not be used for anything but trivial cases, so using Canvas instead
*/
public class GameScreen implements PositionChangeListener, IGameScreen {
private static Logger logger = Logger.getLogger(GameScreen.class);
/**
* Comparator used to sort entities to display.
*/
@ -81,7 +83,6 @@ public class GameScreen implements PositionChangeListener, IGameScreen {
private Canvas canvas;
private BufferStrategy buffer;
private Graphics g2d;
/**
* Static game layers.
*/
@ -184,14 +185,12 @@ public class GameScreen implements PositionChangeListener, IGameScreen {
public void componentResized(ComponentEvent e) {
Dimension size = canvas.getSize();
if (!size.equals(stendhal.screenSize)) {
sw = Math.min(canvas.getWidth(), stendhal.screenSize.width);
sh = Math.min(canvas.getHeight(), stendhal.screenSize.height);
// Set the user clip to avoid drawing artefacts on some setups
g2d.setClip(0, 0, sw, sh);
calculateView();
center();
}
sw = Math.min(canvas.getWidth(), stendhal.screenSize.width);
sh = Math.min(canvas.getHeight(), stendhal.screenSize.height);
// Reset the view so that the player is in the center
calculateView();
center();
}
public void componentShown(ComponentEvent e) { }
@ -482,6 +481,7 @@ public class GameScreen implements PositionChangeListener, IGameScreen {
draw((Graphics2D) g);
buffer.show();
g.dispose();
}
}
@ -489,22 +489,13 @@ public class GameScreen implements PositionChangeListener, IGameScreen {
/*
* swing does not want to give a valid GraphicsConfiguration until the
* window has been drawn, so this can not be done in the constructor.
*/
*/
if (buffer == null) {
canvas.createBufferStrategy(2);
buffer = canvas.getBufferStrategy();
g2d = buffer.getDrawGraphics();
} else if (buffer.contentsLost() || buffer.contentsRestored()) {
/*
* On some systems (windows) the draw buffer can get lost in
* certain conditions. Recreate it if that has happened.
*/
final Graphics old = g2d;
g2d = buffer.getDrawGraphics();
old.dispose();
buffer = canvas.getBufferStrategy();
}
return g2d;
return buffer.getDrawGraphics();
}
private void draw(final Graphics2D g2d) {
@ -605,7 +596,11 @@ public class GameScreen implements PositionChangeListener, IGameScreen {
*/
private void drawEntities(final Graphics2D g) {
for (final EntityView view : views) {
view.draw(g);
try {
view.draw(g);
} catch (RuntimeException e) {
logger.error(e, e);
}
}
}

View file

@ -0,0 +1,47 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2010 - Marauroa *
***************************************************************************
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
package games.stendhal.client;
import marauroa.common.Log4J;
import marauroa.common.Logger;
/**
* logs uncaught exceptions to the logging system before
* the exception is propagated to the Java VM which will
* kill the thread. The Java VM only logs to stderr so
* there would be no information in the logfile without
* this class.
*/
public class LogUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private static Logger logger = Log4J.getLogger(LogUncaughtExceptionHandler.class);
private Thread.UncaughtExceptionHandler next;
public void uncaughtException(Thread thread, Throwable exception) {
logger.error("Exception in thread " + thread.getName(), exception);
System.err.println("Exception in thread " + thread.getName());
exception.printStackTrace();
if (next != null) {
next.uncaughtException(thread, exception);
}
}
/**
* installs this uncaught exception handler
*/
public static void setup() {
LogUncaughtExceptionHandler handler = new LogUncaughtExceptionHandler();
handler.next = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(handler);
}
}

View file

@ -0,0 +1,48 @@
package games.stendhal.client.actions;
import games.stendhal.client.ClientSingletonRepository;
import games.stendhal.client.gui.BareBonesBrowserLaunch;
import games.stendhal.client.gui.chatlog.HeaderLessEventLine;
import games.stendhal.common.NotificationType;
/**
* Opens the manual
*/
class FAQAction implements SlashAction {
/**
* Opens the faq
*
* @param params ignored
* @param remainder ignored
* @return <code>true</code>
*/
public boolean execute(final String[] params, final String remainder) {
ClientSingletonRepository.getUserInterface().addEventLine(new HeaderLessEventLine(
"Trying to open #http://arianne.sourceforge.net/wiki/index.php/StendhalFAQ in your browser.",
NotificationType.CLIENT));
BareBonesBrowserLaunch.openURL("http://stendhal.game-host.org/wiki/index.php/StendhalFAQ");
return true;
}
/**
* Get the maximum number of formal parameters.
*
* @return The parameter count.
*/
public int getMaximumParameters() {
return 0;
}
/**
* Get the minimum number of formal parameters.
*
* @return The parameter count.
*/
public int getMinimumParameters() {
return 0;
}
}

View file

@ -0,0 +1,47 @@
package games.stendhal.client.actions;
import games.stendhal.client.ClientSingletonRepository;
import games.stendhal.client.gui.BareBonesBrowserLaunch;
import games.stendhal.client.gui.chatlog.HeaderLessEventLine;
import games.stendhal.common.NotificationType;
/**
* Opens the faq
*/
class ManualAction implements SlashAction {
/**
* Opens the manual
*
* @param params ignored
* @param remainder ignored
* @return <code>true</code>
*/
public boolean execute(final String[] params, final String remainder) {
ClientSingletonRepository.getUserInterface().addEventLine(new HeaderLessEventLine(
"Trying to open #http://arianne.sourceforge.net/wiki/index.php/StendhalManual in your browser.",
NotificationType.CLIENT));
BareBonesBrowserLaunch.openURL("http://stendhal.game-host.org/wiki/index.php/StendhalManual");
return true;
}
/**
* Get the maximum number of formal parameters.
*
* @return The parameter count.
*/
public int getMaximumParameters() {
return 0;
}
/**
* Get the minimum number of formal parameters.
*
* @return The parameter count.
*/
public int getMinimumParameters() {
return 0;
}
}

View file

@ -1,5 +1,11 @@
package games.stendhal.client.actions;
import games.stendhal.client.ClientSingletonRepository;
import games.stendhal.client.gui.chatlog.StandardEventLine;
import games.stendhal.client.gui.wt.core.WtWindowManager;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.client.sound.system.Time;
/**
* Set sound characteristics.
@ -17,7 +23,15 @@ class MuteAction implements SlashAction {
* @return <code>true</code> if was handled.
*/
public boolean execute(final String[] params, final String remainder) {
new SoundAction().toggleMute();
boolean play = Boolean.parseBoolean(WtWindowManager.getInstance().getProperty("sound.play", "true"));
play = !play;
WtWindowManager.getInstance().setProperty("sound.play", Boolean.toString(play));
SoundSystemFacade.get().mute(!play, new Time(2, Time.Unit.SEC));
if (play) {
ClientSingletonRepository.getUserInterface().addEventLine(new StandardEventLine("Sounds are now on."));
} else {
ClientSingletonRepository.getUserInterface().addEventLine(new StandardEventLine("Sounds are now off. (You may need to change the zone to stop the background music)."));
}
return true;
}

View file

@ -34,6 +34,8 @@ public class SlashActionRepository {
actions.put("drop", new DropAction());
actions.put("faq", new FAQAction());
actions.put("gag", new GagAction());
actions.put("gmhelp", new GMHelpAction());
actions.put("grumpy", new GrumpyAction());
@ -47,6 +49,7 @@ public class SlashActionRepository {
actions.put("jail", new JailAction());
actions.put("joinguild", new CreateGuildAction());
actions.put("manual", new ManualAction());
actions.put("me", new EmoteAction());
actions.put("msg", msg);
actions.put("mute", new MuteAction());

View file

@ -1,9 +1,9 @@
package games.stendhal.client.actions;
import games.stendhal.client.ClientSingletonRepository;
import games.stendhal.client.gui.j2DClient;
import games.stendhal.client.gui.chatlog.StandardEventLine;
import games.stendhal.client.gui.wt.core.WtWindowManager;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.common.MathHelper;
/**
@ -25,7 +25,7 @@ class SoundAction implements SlashAction {
final String command = params[0];
if (command.equals("mute")) {
toggleMute();
ClientSingletonRepository.getUserInterface().addEventLine(new StandardEventLine("Please use /mute."));
} else if (command.equals("volume")) {
volume(params);
}
@ -33,21 +33,6 @@ class SoundAction implements SlashAction {
return true;
}
/**
* toggles the mute state of sounds.
*/
void toggleMute() {
boolean play = Boolean.parseBoolean(WtWindowManager.getInstance().getProperty("sound.play", "true"));
play = !play;
WtWindowManager.getInstance().setProperty("sound.play", Boolean.toString(play));
SoundSystemFacade.get().setMute(!play);
String qualifier = " ";
if (!play) {
qualifier=" not ";
}
j2DClient.get().addEventLine(new StandardEventLine(
"Sounds are" +qualifier + "now on."));
}
/**
* sets the volume of sounds

View file

@ -0,0 +1,90 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package games.stendhal.client.entity;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.client.sound.manager.AudibleCircleArea;
import games.stendhal.client.sound.manager.SoundFile.Type;
import games.stendhal.client.sound.manager.SoundManager.Sound;
import games.stendhal.client.sound.system.Time;
import games.stendhal.common.Rand;
import games.stendhal.common.math.Algebra;
import java.util.ArrayList;
import java.util.HashMap;
/**
*
* @author silvio
*/
public abstract class AudibleEntity extends RPEntity {
private AudibleCircleArea mAudibleArea = new AudibleCircleArea(Algebra.vecf(0, 0), 1.5f, 23);
private HashMap<String, ArrayList<String>> mSoundGroups = new HashMap<String, ArrayList<String>>();
private long mWaitTime = 0;
protected void addSoundsToGroup(String groupName, String... soundNames) {
ArrayList<String> soundNameList = mSoundGroups.get(groupName);
if (soundNameList == null) {
soundNameList = new ArrayList<String>();
mSoundGroups.put(groupName, soundNameList);
}
for (String name : soundNames) {
if (SoundSystemFacade.get().loadSound(name, "audio:/" + name + ".ogg", Type.OGG, false) != null) {
soundNameList.add(name);
}
}
}
protected Sound getRandomSoundFromGroup(String groupName, boolean getCloned) {
ArrayList<String> soundNameList = mSoundGroups.get(groupName);
if ((soundNameList != null) && !soundNameList.isEmpty()) {
String name = soundNameList.get(Rand.rand(soundNameList.size()));
Sound sound = SoundSystemFacade.get().getSound(name);
if (sound != null && getCloned) {
return sound.clone();
}
}
return null;
}
protected Sound getSound(String soundName, boolean getCloned) {
Sound sound = SoundSystemFacade.get().getSound(soundName);
if (sound != null && getCloned) {
sound = sound.clone();
}
return sound;
}
@Override
protected void onPosition(double x, double y) {
super.onPosition(x, y);
mAudibleArea.setPosition(Algebra.vecf((float) x, (float) y));
SoundSystemFacade.get().update();
}
protected void playRandomSoundFromGroup(String groupName, float volume) {
Sound sound = getRandomSoundFromGroup(groupName, true);
SoundSystemFacade.get().play(sound, volume, 0, mAudibleArea, false, new Time());
}
protected void playRandomSoundFromGroup(String groupName, float volume, long waitTimeInMilliSec) {
if (mWaitTime < System.currentTimeMillis() && Rand.rand(100) < 5) {
playRandomSoundFromGroup(groupName, volume);
mWaitTime = System.currentTimeMillis() + waitTimeInMilliSec;
}
}
protected void playSound(String soundName, float volume) {
Sound sound = getSound(soundName, true);
SoundSystemFacade.get().play(sound, volume, 0, mAudibleArea, false, new Time());
}
}

View file

@ -12,16 +12,12 @@
***************************************************************************/
package games.stendhal.client.entity;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.common.Rand;
import games.stendhal.common.constants.SoundLayer;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import marauroa.common.game.RPObject;
public class Creature extends RPEntity {
public class Creature extends AudibleEntity {
/**
* Debug property.
*/
@ -102,95 +98,42 @@ public class Creature extends RPEntity {
if (object.has("name")) {
final String name = object.get("name");
addSoundsToGroup("attack",
"punch-1" , "punch-2", "punch-3",
"punch-4" , "punch-5", "punch-6",
"swingaxe-1", "slap-1" , "arrow-1");
if (type.startsWith("creature")) {
if (name.equals("wolf")) {
moveSounds = new String[] { "bark-1", "howl-5",
"howl-2", "howl-11" };
} else if (name.equals("rat") || name.equals("caverat")
|| name.equals("venomrat")) {
moveSounds = new String[2];
moveSounds[0] = "rats-2";
moveSounds[1] = "rats-41";
// moveSounds[2]="rats-3";
addSoundsToGroup("move", "bark-1", "howl-5", "howl-2", "howl-11");
} else if (name.equals("rat") || name.equals("caverat") || name.equals("venomrat")) {
addSoundsToGroup("move", "rats-2", "rats-41"/*, "rats-3*/);
} else if (name.equals("razorrat")) {
moveSounds = new String[1];
moveSounds[0] = "rats-1";
addSoundsToGroup("move", "rats-1");
} else if (name.equals("gargoyle")) {
moveSounds = new String[3];
moveSounds[0] = "hyena-1";
moveSounds[1] = "hyena-2";
moveSounds[2] = "hyena-3";
addSoundsToGroup("move", "hyena-1", "hyena-2", "hyena-3");
} else if (name.equals("boar")) {
moveSounds = new String[2];
moveSounds[0] = "pig-1";
moveSounds[1] = "pig-2";
addSoundsToGroup("move", "pig-1", "pig-2");
} else if (name.equals("bear")) {
moveSounds = new String[3];
moveSounds[0] = "bear-1";
moveSounds[1] = "bear-2";
moveSounds[2] = "bear-3";
addSoundsToGroup("move", "bear-1", "bear-2", "bear-3");
} else if (name.equals("giantrat")) {
moveSounds = new String[2];
moveSounds[0] = "bobcat-1";
moveSounds[1] = "leopard-11";
addSoundsToGroup("move", "bobcat-1", "leopard-11");
} else if (name.equals("cobra")) {
moveSounds = new String[1];
moveSounds[0] = "snake-1";
addSoundsToGroup("move", "snake-1");
} else if (name.equals("kobold")) {
moveSounds = new String[4];
moveSounds[0] = "panda-1";
moveSounds[1] = "panda-2";
moveSounds[2] = "racoon-1";
moveSounds[3] = "lama-1";
addSoundsToGroup("move", "panda-1", "panda-2", "racoon-1", "lama-1");
} else if (name.equals("goblin")) {
moveSounds = new String[2];
moveSounds[0] = "saur-3";
moveSounds[1] = "saur-4";
addSoundsToGroup("move", "saur-3", "saur-4");
} else if (name.equals("troll")) {
moveSounds = new String[5];
moveSounds[0] = "gorilla-1";
moveSounds[1] = "gorilla-2";
moveSounds[2] = "gorilla-3";
moveSounds[3] = "gorilla-4";
moveSounds[4] = "gorilla-5";
addSoundsToGroup("move", "gorilla-1", "gorilla-2", "gorilla-3", "gorilla-4", "gorilla-5");
} else if (name.equals("orc")) {
moveSounds = new String[2];
moveSounds[0] = "lion-11";
moveSounds[1] = "lion-22";
addSoundsToGroup("move", "lion-11", "lion-22");
} else if (name.equals("ogre")) {
moveSounds = new String[4];
moveSounds[0] = "yell-1";
moveSounds[1] = "groan-1";
moveSounds[2] = "moan-1";
moveSounds[3] = "fart-1";
addSoundsToGroup("move", "yell-1", "groan-1", "moan-1", "fart-1");
} else if (name.equals("skeleton")) {
moveSounds = new String[5];
moveSounds[0] = "bones-1";
moveSounds[1] = "evillaugh-3";
moveSounds[2] = "evillaugh-5";
moveSounds[3] = "ghost-1";
moveSounds[4] = "ghost-2";
addSoundsToGroup("move", "bones-1", "evillaugh-3", "evillaugh-5", "ghost-1", "ghost-2");
} else if (name.equals("cyclops")) {
moveSounds = new String[4];
moveSounds[0] = "laugh-33";
moveSounds[1] = "evillaugh-4";
moveSounds[2] = "grunt-1";
moveSounds[3] = "grunt-2";
addSoundsToGroup("move", "yell-1", "laugh-33", "evillaugh-4", "grunt-1", "grunt-2");
}
}
}
@ -202,8 +145,6 @@ public class Creature extends RPEntity {
}
}
private long soundWait;
/**
* When the entity's position changed.
*
@ -215,16 +156,16 @@ public class Creature extends RPEntity {
@Override
protected void onPosition(final double x, final double y) {
super.onPosition(x, y);
if ((soundWait < System.currentTimeMillis()) && (Rand.rand(100) < 5)) {
if (moveSounds != null) {
SoundSystemFacade.get().play(moveSounds[Rand.rand(moveSounds.length)], x, y, SoundLayer.CREATURE_NOISE, 100);
}
soundWait = System.currentTimeMillis() + 10000L;
}
playRandomSoundFromGroup("move", 1.0f, 10000);
}
@Override
public void onDamaged(Entity attacker, int damage) {
super.onDamaged(attacker, damage);
playRandomSoundFromGroup("attack", 1.0f);
}
//
// RPObjectChangeListener
//

View file

@ -9,14 +9,12 @@ package games.stendhal.client.entity;
//
//
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.common.constants.SoundLayer;
import marauroa.common.game.RPObject;
/**
* A domestic animal entity.
*/
public abstract class DomesticAnimal extends RPEntity {
public abstract class DomesticAnimal extends AudibleEntity {
/**
* DomesticAnimal idea property.
*/
@ -90,7 +88,8 @@ public abstract class DomesticAnimal extends RPEntity {
@Override
public void initialize(final RPObject object) {
super.initialize(object);
addSoundsToGroup("eat", "eat-1");
/*
* Idea
*/
@ -145,7 +144,7 @@ public abstract class DomesticAnimal extends RPEntity {
weight = changes.getInt("weight");
if (weight > oldWeight) {
SoundSystemFacade.get().play("eat-1", getX(), getY(), SoundLayer.CREATURE_NOISE, 100);
playRandomSoundFromGroup("eat", 1.0f);
}
fireChange(PROP_WEIGHT);

View file

@ -28,14 +28,6 @@ public class Entity implements RPObjectChangeListener, IEntity {
*
*/
public final byte[] ID_Token = new byte[0];
/**
* an array of sounds. out of these randomnly chosen sounds are played while
* moving.
*/
protected String[] moveSounds;
/** The current x location of this entity. */
protected double x;
@ -86,11 +78,6 @@ public class Entity implements RPObjectChangeListener, IEntity {
*/
protected String type;
/**
* Defines the distance in which the entity is heard by Player.
*/
protected double audibleRange = Double.POSITIVE_INFINITY;
/**
* Quick work-around to prevent fireMovementEvent() from calling in
* onChangedAdded() from other initialize() hack.
@ -270,26 +257,6 @@ public class Entity implements RPObjectChangeListener, IEntity {
return !rpObject.isContained();
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getAudibleArea()
*/
public Rectangle2D getAudibleArea() {
if (audibleRange == Double.POSITIVE_INFINITY) {
return null;
}
final double tempWidth = audibleRange * 2;
return new Rectangle2D.Double(getX() - audibleRange, getY()
- audibleRange, tempWidth, tempWidth);
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#setAudibleRange(double)
*/
public void setAudibleRange(final double range) {
audibleRange = range;
}
/**
* Process attribute changes that may affect positioning. This is needed
* because different entities may want to process coordinate changes more

View file

@ -12,14 +12,12 @@
***************************************************************************/
package games.stendhal.client.entity;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.common.constants.SoundLayer;
import marauroa.common.game.RPObject;
/**
* A food entity.
*/
public abstract class Food extends Entity {
public abstract class Food extends AudibleEntity {
/**
* Amount property.
*/
@ -64,6 +62,7 @@ public abstract class Food extends Entity {
@Override
public void initialize(final RPObject object) {
super.initialize(object);
addSoundsToGroup("food", "pop-2");
if (object.has("amount")) {
amount = object.getInt("amount");
@ -95,7 +94,7 @@ public abstract class Food extends Entity {
// TODO this causes problems because of unidentified content refresh
// events (e.g. synchronizing)
if (amount > oldAmount) {
SoundSystemFacade.get().play("pop-2", getX(), getY(), SoundLayer.CREATURE_NOISE, 100);
playRandomSoundFromGroup("food", 1.0f);
}
fireChange(PROP_AMOUNT);

View file

@ -145,24 +145,6 @@ public interface IEntity {
*/
boolean isOnGround();
/**
* @return the absolute world area (coordinates) to which audibility of
* entity sounds is confined. Returns <b>null</b> if confines do not exist
* (audible everywhere).
*/
Rectangle2D getAudibleArea();
/**
* Sets the audible range as radius distance from this entity's position,
* expressed in coordinate units. This reflects an abstract capacity of this
* unit to emit sounds and influences the result of
* <code>getAudibleArea()</code>.
*
* @param range
* double audibility area radius in coordinate units
*/
void setAudibleRange(final double range);
/**
* Get the resistance this has on other entities (0-100).
*

View file

@ -7,48 +7,48 @@
* Created 21.01.2006
* Version
*
This program is free software. You can use, redistribute and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, version 2 of the License.
This program is free software. You can use, redistribute and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, version 2 of the License.
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.
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.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/gpl.html.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/gpl.html.
*/
package games.stendhal.client.entity;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.client.sound.manager.AudibleCircleArea;
import games.stendhal.client.sound.manager.SoundFile;
import games.stendhal.client.sound.manager.SoundManager.Sound;
import games.stendhal.client.sound.system.Time;
import games.stendhal.common.constants.SoundLayer;
import games.stendhal.common.math.Algebra;
import games.stendhal.common.math.Numeric;
import marauroa.common.game.RPObject;
public class LoopedSoundSource extends InvisibleEntity {
private String sound;
private Sound sound;
private int radius;
private int volume;
private SoundLayer layer = SoundLayer.AMBIENT_SOUND;
private Sound soundObject;
private Time fadingDuration = new Time();
@Override
public void onChangedAdded(RPObject object, RPObject changes) {
// stop the current sound
if (sound != null) {
SoundSystemFacade.get().stop(soundObject);
}
SoundSystemFacade.get().stop(sound, fadingDuration);
// udpate
super.onChangedAdded(object, changes);
update(changes);
update(changes);
play();
}
@ -59,7 +59,8 @@ public class LoopedSoundSource extends InvisibleEntity {
*/
private void update(RPObject object) {
if (object.has("sound")) {
sound = object.get("sound");
String soundName = object.get("sound");
sound = SoundSystemFacade.get().loadSound(soundName, "audio:/" + soundName + ".ogg", SoundFile.Type.OGG, true);
}
if (object.has("radius")) {
radius = object.getInt("radius");
@ -72,6 +73,18 @@ public class LoopedSoundSource extends InvisibleEntity {
if (idx < SoundLayer.values().length) {
layer = SoundLayer.values()[idx];
}
if (layer == SoundLayer.BACKGROUND_MUSIC) {
fadingDuration.set(3, Time.Unit.SEC);
} else {
fadingDuration.set(100, Time.Unit.MILLI);
}
}
if (object.has("sound") || object.has("layer")) {
if (layer != SoundLayer.BACKGROUND_MUSIC) {
sound = sound.clone();
}
}
}
@ -79,7 +92,9 @@ public class LoopedSoundSource extends InvisibleEntity {
* plays the sound
*/
private void play() {
soundObject = SoundSystemFacade.get().start(sound, x, y, radius, layer, volume, true);
AudibleCircleArea area = new AudibleCircleArea(Algebra.vecf((float) x, (float) y), radius / 2.0f, radius);
float vol = Numeric.intToFloat(volume, 100.0f);
SoundSystemFacade.get().play(sound, vol, 0, area, true, fadingDuration);
}
/**
@ -91,6 +106,6 @@ public class LoopedSoundSource extends InvisibleEntity {
@Override
public void release() {
super.release();
SoundSystemFacade.get().stop(soundObject);
SoundSystemFacade.get().stop(sound, fadingDuration);
}
}

View file

@ -12,15 +12,12 @@
***************************************************************************/
package games.stendhal.client.entity;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.common.Rand;
import games.stendhal.common.constants.SoundLayer;
import marauroa.common.game.RPObject;
/**
* An NPC entity.
*/
public class NPC extends RPEntity {
public class NPC extends AudibleEntity {
//
// Entity
//
@ -40,46 +37,20 @@ public class NPC extends RPEntity {
final String type = getType();
if (type.startsWith("npc")) {
setAudibleRange(3);
if (name.equals("Diogenes")) {
moveSounds = new String[2];
moveSounds[0] = "laugh-1";
moveSounds[1] = "laugh-2";
// SoundSystem.startSoundCycle(this, "Diogenes-patrol", 10000,
// 20, 50, 100);
addSoundsToGroup("move", "laugh-1", "laugh-2");
} else if (name.equals("Carmen")) {
moveSounds = new String[2];
moveSounds[0] = "giggle-1";
moveSounds[1] = "giggle-2";
// SoundSystem.startSoundCycle(this, "Carmen-patrol", 60000, 20,
// 50, 75);
addSoundsToGroup("move", "giggle-1", "giggle-2");
} else if (name.equals("Nishiya")) {
moveSounds = new String[3];
moveSounds[0] = "cough-11";
moveSounds[1] = "cough-2";
moveSounds[2] = "cough-3";
// SoundSystem.startSoundCycle(this, "Nishiya-patrol", 40000,
// 20, 50, 80);
addSoundsToGroup("move", "cough-11", "cough-2", "cough-3");
} else if (name.equals("Margaret")) {
moveSounds = new String[3];
moveSounds[0] = "hiccup-1";
moveSounds[1] = "hiccup-2";
moveSounds[2] = "hiccup-3";
// SoundSystem.startSoundCycle(this, "Margaret-patrol", 30000,
// 10, 30, 70);
addSoundsToGroup("move", "hiccup-1", "hiccup-2", "hiccup-3");
} else if (name.equals("Sato")) {
moveSounds = new String[1];
moveSounds[0] = "sneeze-1";
// SoundSystem.startSoundCycle(this, "Sato-patrol", 60000, 30,
// 50, 70);
addSoundsToGroup("move", "hiccup-1", "sneeze-1");
}
}
}
private long soundWait;
/**
* When the entity's position changed.
*
@ -91,13 +62,6 @@ public class NPC extends RPEntity {
@Override
protected void onPosition(final double x, final double y) {
super.onPosition(x, y);
if ((soundWait < System.currentTimeMillis()) && (Rand.rand(1000) < 5)) {
if (moveSounds != null) {
SoundSystemFacade.get().play(moveSounds[Rand.rand(moveSounds.length)], x, y, SoundLayer.CREATURE_NOISE, 100);
}
soundWait = System.currentTimeMillis() + 2000L;
}
playRandomSoundFromGroup("move", 1.0f, 20000);
}
}

View file

@ -16,7 +16,7 @@ import games.stendhal.common.NotificationType;
import marauroa.common.game.RPObject;
/** A Player entity. */
public class Player extends RPEntity {
public class Player extends AudibleEntity {
/**
* Away property.
@ -129,6 +129,21 @@ public class Player extends RPEntity {
}
}
@Override
public void initialize(RPObject object) {
super.initialize(object);
addSoundsToGroup("attack",
"punch-1" , "punch-2", "punch-3",
"punch-4" , "punch-5", "punch-6",
"swingaxe-1", "slap-1" , "arrow-1");
}
@Override
public void onDamaged(Entity attacker, int damage) {
super.onDamaged(attacker, damage);
playRandomSoundFromGroup("attack", 1.0f);
}
//
// RPObjectChangeListener

View file

@ -18,12 +18,9 @@ import games.stendhal.client.stendhal;
import games.stendhal.client.gui.chatlog.HeaderLessEventLine;
import games.stendhal.client.gui.chatlog.StandardEventLine;
import games.stendhal.client.gui.chatlog.StandardHeaderedEventLine;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.common.Grammar;
import games.stendhal.common.ItemTools;
import games.stendhal.common.NotificationType;
import games.stendhal.common.Rand;
import games.stendhal.common.constants.SoundLayer;
import java.util.Collection;
import java.util.Iterator;
@ -85,11 +82,11 @@ public abstract class RPEntity extends ActiveEntity {
* The entities attacking this entity.
*/
protected List<Entity> attackers;
/*
String[] attackSounds = { "punch-1", "punch-2", "punch-3",
"punch-4", "punch-5", "punch-6", "swingaxe-1",
"slap-1", "arrow-1" };
*/
private boolean showBladeStrike;
public enum Resolution {
@ -440,11 +437,11 @@ public abstract class RPEntity extends ActiveEntity {
public void onDamaged(final Entity attacker, final int damage) {
combatIconTime = System.currentTimeMillis();
resolution = Resolution.HIT;
try {
/*try {
SoundSystemFacade.get().play(attackSounds[Rand.rand(attackSounds.length)], x, y, SoundLayer.CREATURE_NOISE, 100);
} catch (final NullPointerException e) {
// ignore errors
}
}*/
boolean showAttackInfoForPlayer = (this.isUser() || attacker.isUser());
showAttackInfoForPlayer = showAttackInfoForPlayer

View file

@ -12,9 +12,8 @@
***************************************************************************/
package games.stendhal.client.entity;
import games.stendhal.client.sound.SoundSystemFacade;
import games.stendhal.common.Rand;
import games.stendhal.common.constants.SoundLayer;
import marauroa.common.game.RPObject;
/** A Sheep entity. */
public class Sheep extends DomesticAnimal {
@ -23,19 +22,20 @@ public class Sheep extends DomesticAnimal {
//
@Override
protected void probableChat(final int chance) {
public void initialize(RPObject object) {
super.initialize(object);
addSoundsToGroup("small", "sheep-2", "sheep-4");
addSoundsToGroup("big" , "sheep-1", "sheep-3");
}
final String[][] soundnames = { { "sheep-1", "sheep-3" },
{ "sheep-2", "sheep-4" } };
final int which = Rand.rand(2);
@Override
protected void probableChat(final int chance) {
if (Rand.rand(100) < chance) {
final String token;
if (getWeight() > 50) {
token = soundnames[0][which];
playRandomSoundFromGroup("big", 1.0f);
} else {
token = soundnames[1][which];
playRandomSoundFromGroup("small", 1.0f);
}
SoundSystemFacade.get().play(token, x, y, SoundLayer.CREATURE_NOISE, 100);
}
}
}

View file

@ -7,10 +7,10 @@ import games.stendhal.client.stendhal;
import games.stendhal.client.gui.chatlog.HeaderLessEventLine;
import games.stendhal.client.update.Version;
import games.stendhal.common.Grammar;
import games.stendhal.common.KeyedSlotUtil;
import games.stendhal.common.NotificationType;
import java.awt.geom.Rectangle2D;
import java.util.HashSet;
import marauroa.common.game.RPObject;
@ -22,6 +22,7 @@ import marauroa.common.game.RPObject;
public class User extends Player {
private static User instance;
private HashSet<String> ignore = new HashSet<String>();
private String serverVersion = null;
@ -267,6 +268,13 @@ public class User extends Player {
NotificationType.ERROR));
}
}
if (changes.hasSlot("!ignore")) {
RPObject ign = changes.getSlot("!ignore").getFirst();
if (ign != null) {
addIgnore(ign);
}
}
}
}
@ -274,6 +282,12 @@ public class User extends Player {
public void onChangedRemoved(final RPObject base, final RPObject diff) {
modificationCount++;
super.onChangedRemoved(base, diff);
if (diff.hasSlot("!ignore")) {
RPObject ign = diff.getSlot("!ignore").getFirst();
if (ign != null) {
removeIgnore(ign);
}
}
}
/**
@ -362,11 +376,34 @@ public class User extends Player {
return false;
}
// compatibility with 0.79 server
if (!User.get().rpObject.hasSlot("!ignore")) {
return false;
return User.get().ignore.contains(name);
}
/**
* Add players to the set of ignored players.
* Player names are the attributes prefixed with '_'.
*
* @param ignoreObj The container object for player names
*/
private void addIgnore(RPObject ignoreObj) {
for (String attr : ignoreObj) {
if (attr.charAt(0) == '_') {
ignore.add(attr.substring(1));
}
}
}
/**
* Remove players from the set of ignored players.
* Player names are the attributes prefixed with '_'.
*
* @param ignoreObj The container object for player names
*/
private void removeIgnore(RPObject ignoreObj) {
for (String attr : ignoreObj) {
if (attr.charAt(0) == '_') {
ignore.remove(attr.substring(1));
}
}
// end compatibility
return KeyedSlotUtil.getKeyedSlot(User.get().rpObject, "!ignore", "_" + name) != null;
}
}

View file

@ -25,7 +25,11 @@ public class SoundEvent extends Event<Entity> {
if (event.has("volume")) {
volume = event.getInt("volume");
}
SoundSystemFacade.get().play(event.get("sound"), entity.getX(), entity.getY(), event.getInt("radius"), layer, volume);
int radius = 100000;
if (event.has("radius")) {
radius = event.getInt("radius");
}
SoundSystemFacade.get().play(event.get("sound"), entity.getX(), entity.getY(), radius, layer, volume);
}
}

View file

@ -346,7 +346,7 @@ public class j2DClient implements UserInterface {
chatBox.setMinimumSize(chatText.getPlayerChatText().getMinimumSize());
// Give the user the ability to make the the game area less tall
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, pane, chatBox);
final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, pane, chatBox);
// Works for showing the resize, but is extremely flickery
//splitPane.setContinuousLayout(true);
pane.addComponentListener(new SplitPaneResizeListener(screen.getComponent(), splitPane));
@ -397,6 +397,11 @@ public class j2DClient implements UserInterface {
mainFrame.getMainFrame().setMinimumSize(new Dimension(width, height));
mainFrame.getMainFrame().setVisible(true);
/*
* Needed for small screens; Sometimes the divider is placed
* incorrectly unless we explicitly set it.
*/
splitPane.setDividerLocation(stendhal.screenSize.height + 1);
}
});
directionRelease = null;
@ -405,7 +410,11 @@ public class j2DClient implements UserInterface {
SlashActionRepository.register();
checkAndComplainAboutJavaImplementation();
WorldObjects.addWorldListener(SoundSystemFacade.get());
try {
WorldObjects.addWorldListener(SoundSystemFacade.get());
} catch (RuntimeException e) {
logger.error(e, e);
}
} // constructor
private void checkAndComplainAboutJavaImplementation() {
@ -467,141 +476,145 @@ public class j2DClient implements UserInterface {
boolean canExit = false;
while (!canExit) {
fps++;
// figure out what time it is right after the screen flip then
// later we can figure out how long we have been doing redrawing
// / networking, then we know how long we need to sleep to make
// the next flip happen at the right time
try {
fps++;
// figure out what time it is right after the screen flip then
// later we can figure out how long we have been doing redrawing
// / networking, then we know how long we need to sleep to make
// the next flip happen at the right time
screen.nextFrame();
final long now = System.currentTimeMillis();
final int delta = (int) (now - refreshTime);
refreshTime = now;
logger.debug("Move objects");
gameObjects.update(delta);
if (!client.isInBatchUpdate() && gameLayers.changedArea()) {
/*
* Update the screen
*/
screen.setMaxWorldSize(gameLayers.getWidth(),
gameLayers.getHeight());
screen.center();
// [Re]create the map
screen.nextFrame();
final long now = System.currentTimeMillis();
final int delta = (int) (now - refreshTime);
refreshTime = now;
final CollisionDetection cd = gameLayers.getCollisionDetection();
final CollisionDetection pd = gameLayers.getProtectionDetection();
if (cd != null) {
minimap.update(cd, pd,
screen.getComponent().getGraphicsConfiguration(),
gameLayers.getArea());
}
gameLayers.resetChangedArea();
}
final User user = User.get();
if (user != null) {
// check if the player object has changed.
// Note: this is an exact object reference check
if (user != lastuser) {
character.setPlayer(user);
keyring.setSlot(user, "keyring", gameScreen);
inventory.setSlot(user, "bag", gameScreen);
lastuser = user;
}
}
if (!client.isInBatchUpdate()) {
if (mainFrame.getMainFrame().getState() != Frame.ICONIFIED) {
logger.debug("Draw screen");
screen.draw();
minimap.refresh();
}
}
logger.debug("Query network");
if (client.loop(0)) {
lastMessageHandle = refreshTime;
}
/*
* Process delayed direction release
*/
if ((directionRelease != null) && directionRelease.hasExpired()) {
client.removeDirection(directionRelease.getDirection(),
directionRelease.isFacing());
directionRelease = null;
}
if (logger.isDebugEnabled()) {
if ((refreshTime - lastFpsTime) >= 1000L) {
logger.debug("FPS: " + fps);
final long freeMemory = Runtime.getRuntime().freeMemory() / 1024;
final long totalMemory = Runtime.getRuntime().totalMemory() / 1024;
logger.debug("Total/Used memory: " + totalMemory + "/"
+ (totalMemory - freeMemory));
fps = 0;
lastFpsTime = refreshTime;
}
}
// Shows a offline icon if no messages are received in 120 seconds.
if ((refreshTime - lastMessageHandle > 120000L)
|| !client.getConnectionState()) {
setOffline(true);
} else {
setOffline(false);
}
logger.debug("Start sleeping");
// we know how long we want per screen refresh (40ms) then
// we add the refresh time and subtract the current time
// leaving us with the amount we still need to sleep.
long wait = frameLength + refreshTime - System.currentTimeMillis();
if (wait > 0) {
if (wait > 100L) {
logger.info("Waiting " + wait + " ms");
wait = 100L;
}
try {
Thread.sleep(wait);
} catch (final InterruptedException e) {
}
}
logger.debug("End sleeping");
if (!gameRunning) {
logger.info("Request logout");
try {
logger.debug("Move objects");
gameObjects.update(delta);
if (!client.isInBatchUpdate() && gameLayers.changedArea()) {
/*
* We request server permision to logout. Server can deny
* it.
* Update the screen
*/
if (client.logout()) {
canExit = true;
} else {
logger.warn("You can't logout now.");
gameRunning = true;
screen.setMaxWorldSize(gameLayers.getWidth(),
gameLayers.getHeight());
screen.center();
// [Re]create the map
final CollisionDetection cd = gameLayers.getCollisionDetection();
final CollisionDetection pd = gameLayers.getProtectionDetection();
if (cd != null) {
minimap.update(cd, pd,
screen.getComponent().getGraphicsConfiguration(),
gameLayers.getArea());
}
gameLayers.resetChangedArea();
}
final User user = User.get();
if (user != null) {
// check if the player object has changed.
// Note: this is an exact object reference check
if (user != lastuser) {
character.setPlayer(user);
keyring.setSlot(user, "keyring", gameScreen);
inventory.setSlot(user, "bag", gameScreen);
lastuser = user;
}
} catch (final Exception e) {
/*
* If we get a timeout exception we accept exit request.
*/
canExit = true;
logger.error(e, e);
}
if (!client.isInBatchUpdate()) {
if (mainFrame.getMainFrame().getState() != Frame.ICONIFIED) {
logger.debug("Draw screen");
screen.draw();
minimap.refresh();
}
}
logger.debug("Query network");
if (client.loop(0)) {
lastMessageHandle = refreshTime;
}
/*
* Process delayed direction release
*/
if ((directionRelease != null) && directionRelease.hasExpired()) {
client.removeDirection(directionRelease.getDirection(),
directionRelease.isFacing());
directionRelease = null;
}
if (logger.isDebugEnabled()) {
if ((refreshTime - lastFpsTime) >= 1000L) {
logger.debug("FPS: " + fps);
final long freeMemory = Runtime.getRuntime().freeMemory() / 1024;
final long totalMemory = Runtime.getRuntime().totalMemory() / 1024;
logger.debug("Total/Used memory: " + totalMemory + "/"
+ (totalMemory - freeMemory));
fps = 0;
lastFpsTime = refreshTime;
}
}
// Shows a offline icon if no messages are received in 120 seconds.
if ((refreshTime - lastMessageHandle > 120000L)
|| !client.getConnectionState()) {
setOffline(true);
} else {
setOffline(false);
}
logger.debug("Start sleeping");
// we know how long we want per screen refresh (40ms) then
// we add the refresh time and subtract the current time
// leaving us with the amount we still need to sleep.
long wait = frameLength + refreshTime - System.currentTimeMillis();
if (wait > 0) {
if (wait > 100L) {
logger.info("Waiting " + wait + " ms");
wait = 100L;
}
try {
Thread.sleep(wait);
} catch (final InterruptedException e) {
}
}
logger.debug("End sleeping");
if (!gameRunning) {
logger.info("Request logout");
try {
/*
* We request server permision to logout. Server can deny
* it.
*/
if (client.logout()) {
canExit = true;
} else {
logger.warn("You can't logout now.");
gameRunning = true;
}
} catch (final Exception e) {
/*
* If we get a timeout exception we accept exit request.
*/
canExit = true;
logger.error(e, e);
}
}
} catch (RuntimeException e) {
logger.error(e, e);
}
}
@ -1094,7 +1107,7 @@ public class j2DClient implements UserInterface {
* so return the divider to the maximum allowed height
* by force.
*/
splitPane.setDividerLocation(stendhal.screenSize.height);
splitPane.setDividerLocation(stendhal.screenSize.height + 1);
} else {
child.setSize(newSize);
}

View file

@ -18,6 +18,7 @@ import games.stendhal.client.entity.User;
import games.stendhal.client.sprite.Sprite;
import games.stendhal.client.sprite.SpriteStore;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.util.List;
@ -41,6 +42,8 @@ class Player2DView extends RPEntity2DView {
* Sprite representing grumpy.
*/
private static Sprite grumpySprite;
private boolean ignored = false;
/**
@ -104,6 +107,15 @@ class Player2DView extends RPEntity2DView {
return store.getFailsafeOutfit();
}
}
@Override
protected AlphaComposite getComposite() {
// Check for ghostmode to avoid ignored ghostmode admins becoming visible
if (User.isIgnoring(entity.getName()) && !((RPEntity) entity).isGhostMode()) {
return AlphaComposite.DstOut;
}
return super.getComposite();
}
/**
* Determine is the user can see this entity while in ghostmode.
@ -152,6 +164,13 @@ class Player2DView extends RPEntity2DView {
*/
@Override
protected void draw(final Graphics2D g2d, final int x, final int y, final int width, final int height) {
boolean newIgnoreStatus = User.isIgnoring(entity.getName());
if (newIgnoreStatus != ignored) {
visibilityChanged = true;
ignored = newIgnoreStatus;
markChanged();
}
super.draw(g2d, x, y, width, height);
if (((Player) entity).isAway()) {

View file

@ -6,6 +6,7 @@ import games.stendhal.client.gui.wt.core.WtWindowManager;
import games.stendhal.client.sound.manager.AudibleArea;
import games.stendhal.client.sound.manager.AudibleCircleArea;
import games.stendhal.client.sound.manager.SoundFile;
import games.stendhal.client.sound.manager.SoundFile.Type;
import games.stendhal.client.sound.manager.SoundManager;
import games.stendhal.client.sound.system.Time;
import games.stendhal.common.constants.SoundLayer;
@ -26,25 +27,34 @@ import org.apache.log4j.Logger;
*/
public class SoundSystemFacade extends SoundManager implements WorldListener {
private static final Logger logger = Logger.getLogger(SoundSystemFacade.class);
private static final SoundSystemFacade singletonInstance = new SoundSystemFacade();
private static final Time fadingTime = new Time(100, Time.Unit.MILLI);
private static SoundSystemFacade instance;
private final HashMap<String, Sound> sounds = new HashMap<String, Sound>();
private final ResourceLocator resourceLocator = ResourceManager.get();
private boolean mute = false;
public static SoundSystemFacade get() {
return singletonInstance;
if (instance == null) {
instance = new SoundSystemFacade();
}
return instance;
}
private SoundSystemFacade() {
mute = !Boolean.parseBoolean(WtWindowManager.getInstance().getProperty("sound.play", "true"));
try {
boolean mute = !Boolean.parseBoolean(WtWindowManager.getInstance().getProperty("sound.play", "true"));
super.mute(mute, null);
} catch (RuntimeException e) {
logger.error(e, e);
}
}
public void playerMoved() {
float[] position = Algebra.vecf((float) User.get().getX(), (float) User.get().getY());
setHearerPosition(position);
update();
try {
float[] position = Algebra.vecf((float) User.get().getX(), (float) User.get().getY());
super.setHearerPosition(position);
update();
} catch (RuntimeException e) {
logger.error(e, e);
}
}
public void zoneEntered(String zoneName) {
@ -55,91 +65,86 @@ public class SoundSystemFacade extends SoundManager implements WorldListener {
// ignored
}
public void exit() {
// exits the sound system
public void stop(String soundName, Time fadeOutDuration) {
try {
super.stop(getSound(soundName), fadeOutDuration);
} catch (RuntimeException e) {
logger.error(e, e);
}
}
public Sound start(String soundName, double x, double y, int radius, SoundLayer layer, int volume, boolean loop) {
if (!mute) {
Sound sound = prepareSound(soundName);
AudibleArea area = new AudibleCircleArea(Algebra.vecf((float) x, (float) y), radius / 2.0f, radius);
Time myFadingTime = new Time();
if (loop) {
myFadingTime = fadingTime;
}
play(sound, Numeric.intToFloat(volume, 100.0f), 0, area, loop, myFadingTime);
return sound;
public Sound getSound(String soundName) {
try {
return sounds.get(soundName);
} catch (RuntimeException e) {
logger.error(e, e);
}
return null;
}
public void stop(Sound sound) {
stop(sound, fadingTime);
public Sound openSound(String fileURI, SoundFile.Type fileType) {
try {
return super.openSound(resourceLocator.getResource(fileURI), fileType);
} catch (RuntimeException e) {
logger.error(e, e);
}
return null;
}
private Sound setSound(String soundName, Sound sound) {
return sounds.put(soundName, sound);
public Sound openSound(String fileURI, SoundFile.Type fileType, int numSamplesPerChunk, boolean enableStreaming) {
try {
return super.openSound(resourceLocator.getResource(fileURI), fileType, numSamplesPerChunk, enableStreaming);
} catch (RuntimeException e) {
logger.error(e, e);
}
return null;
}
private Sound getSound(String soundName) {
return sounds.get(soundName);
}
private Sound openSound(String fileURI, SoundFile.Type fileType) {
return super.openSound(resourceLocator.getResource(fileURI), fileType);
}
public void setMute(boolean mute) {
this.mute = mute;
public Sound loadSound(String name, String fileURI, SoundFile.Type fileType, boolean enableStreaming) {
try {
Sound sound = sounds.get(name);
if(sound == null) {
sound = super.openSound(resourceLocator.getResource(fileURI), fileType, 256, enableStreaming);
if(sound != null) {
sounds.put(name, sound);
}
}
return sound;
} catch (RuntimeException e) {
logger.error(e, e);
}
return null;
}
public void play(final String soundName, final SoundLayer soundLayer, int volume) {
AudibleArea area = SoundManager.INFINITE_AUDIBLE_AREA;
playNonLoopedSound(soundName, area, soundLayer.ordinal(), volume);
try {
Sound sound = loadSound(soundName, "audio:/" + soundName + ".ogg", Type.OGG, false);
super.play(sound, Numeric.intToFloat(volume, 100.0f), 0, null, false, new Time());
} catch (RuntimeException e) {
logger.error(e, e);
}
}
public void play(final String soundName, final double x, final double y, final SoundLayer soundLayer, int volume) {
AudibleArea area = new AudibleCircleArea(Algebra.vecf((float) x, (float) y), 3, 18);
playNonLoopedSound(soundName, area, soundLayer.ordinal(), volume);
try {
AudibleArea area = new AudibleCircleArea(Algebra.vecf((float) x, (float) y), 3, 20);
Sound sound = loadSound(soundName, "audio:/" + soundName + ".ogg", Type.OGG, false);
super.play(sound, Numeric.intToFloat(volume, 100.0f), 0, area, false, new Time());
} catch (RuntimeException e) {
logger.error(e, e);
}
}
public void play(final String soundName, final double x, final double y, int radius, final SoundLayer soundLayer, int volume) {
AudibleArea area = new AudibleCircleArea(Algebra.vecf((float) x, (float) y), radius / 4.0f, radius);
playNonLoopedSound(soundName, area, soundLayer.ordinal(), volume);
try {
AudibleArea area = new AudibleCircleArea(Algebra.vecf((float) x, (float) y), radius / 4.0f, radius);
Sound sound = loadSound(soundName, "audio:/" + soundName + ".ogg", Type.OGG, false);
super.play(sound, Numeric.intToFloat(volume, 100.0f), 0, area, false, new Time());
} catch (RuntimeException e) {
logger.error(e, e);
}
}
public void playNonLoopedSound(String soundName, AudibleArea area, int soundLayer, int volume) {
if (mute) {
return;
}
SoundSystemFacade.Sound sound = prepareSound(soundName);
play(sound, Numeric.intToFloat(volume, 100.0f), 0, area, false, new Time());
}
public Sound prepareSound(String soundName) {
if (soundName == null) {
return null;
}
Sound sound = getSound(soundName);
if (sound == null) {
sound = openSound("audio:/" + soundName + ".ogg", SoundFile.Type.OGG);
setSound(soundName, sound);
}
// TODO: Sound objects need to be cloned here to set individual properties
// But as long as that crashes the client because of OutOfMemoryErrors,
// we have to live with only one sound source per file doing the right thingy.
/*
if (sound != null) {
sound = sound.clone();
}*/
return sound;
}
}

View file

@ -11,6 +11,7 @@ import games.stendhal.client.sound.system.processors.PCMStreamConverter;
import games.stendhal.client.sound.system.SignalProcessor;
import games.stendhal.common.resource.Resource;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.sampled.AudioInputStream;
@ -28,6 +29,7 @@ public class SoundFile extends SignalProcessor implements Cloneable
private int mNumChannels;
private int mSampleRate;
private SignalProcessor mGenerator = null;
private Recorder mRecorder = null;
private final Resource mResource;
private final boolean mEnableStreaming;
private final int mOutputNumSamples;
@ -52,7 +54,10 @@ public class SoundFile extends SignalProcessor implements Cloneable
{
assert resource != null;
assert fileType != null;
if(!resource.exists())
throw new IOException("audio resource doesn't exist: " + resource.getURI());
mResource = resource;
mFileType = fileType;
mEnableStreaming = enableStreaming;
@ -60,7 +65,7 @@ public class SoundFile extends SignalProcessor implements Cloneable
SignalProcessor decoder = chooseDecoder(resource, fileType, outputNumSamplesPerChannel);
if(decoder == null || !resource.exists())
if(decoder == null)
throw new IOException("could not load audio resource: " + resource.getURI());
if(enableStreaming)
@ -70,47 +75,81 @@ public class SoundFile extends SignalProcessor implements Cloneable
}
else
{
Recorder recorder = new Recorder(outputNumSamplesPerChannel);
recorder.connectTo(decoder, false);
mRecorder = new Recorder();
mRecorder.connectTo(decoder, false);
while(recorder.request())
while(mRecorder.request())
/* pass */;
recorder.disconnect();
recorder.trim();
recorder.connectTo(mPropagator, true);
mGenerator = recorder;
mRecorder.disconnect();
mRecorder.trim();
Recorder.Player player = mRecorder.createPlayer(outputNumSamplesPerChannel);
player.connectTo(mPropagator, true);
mGenerator = player;
}
}
private SoundFile(Recorder recorder, Resource resource, Type fileType, int outputNumSamplesPerChannel)
{
mResource = resource;
mFileType = fileType;
mOutputNumSamples = outputNumSamplesPerChannel;
mEnableStreaming = false;
mRecorder = recorder;
mGenerator = recorder.createPlayer(outputNumSamplesPerChannel);
mGenerator.connectTo(mPropagator, true);
}
public int getNumChannels() { return mNumChannels; }
public int getSampleRate () { return mSampleRate; }
public void close()
{
if(mGenerator instanceof Recorder.Player)
{
mRecorder.clear();
}
else if(mGenerator instanceof OggVorbisDecoder)
{
OggVorbisDecoder decoder = (OggVorbisDecoder)mGenerator;
decoder.close();
}
else if(mGenerator instanceof PCMStreamConverter)
{
PCMStreamConverter converter = (PCMStreamConverter)mGenerator;
converter.close();
}
}
@Override
public SoundFile clone()
{
SoundFile file = null;
try
{
file = new SoundFile(mResource, mFileType, mOutputNumSamples, mEnableStreaming);
}
catch(IOException exception)
{
assert false: "catched exception - " + exception;
}
if(mGenerator instanceof Recorder.Player)
{
file = new SoundFile(mRecorder, mResource, mFileType, mOutputNumSamples);
}
else
{
try
{
file = new SoundFile(mResource, mFileType, mOutputNumSamples, mEnableStreaming);
}
catch(IOException exception)
{
assert false: "catched exception - " + exception;
}
}
return file;
}
public void restart()
{
if(mGenerator.getClass() == Recorder.class)
{
Recorder recorder = (Recorder)mGenerator;
recorder.restart();
}
else if(mGenerator.getClass() == OggVorbisDecoder.class)
if(mGenerator instanceof OggVorbisDecoder)
{
OggVorbisDecoder decoder = (OggVorbisDecoder)mGenerator;
decoder.close();
@ -122,7 +161,7 @@ public class SoundFile extends SignalProcessor implements Cloneable
catch(IOException exception) { }
}
else if(mGenerator.getClass() == PCMStreamConverter.class)
else if(mGenerator instanceof PCMStreamConverter)
{
PCMStreamConverter converter = (PCMStreamConverter)mGenerator;
converter.close();
@ -135,6 +174,11 @@ public class SoundFile extends SignalProcessor implements Cloneable
catch(IOException exception) { }
catch(UnsupportedAudioFileException exception) { }
}
else if(mGenerator instanceof Recorder.Player)
{
Recorder.Player player = (Recorder.Player)mGenerator;
player.restart();
}
}
private SignalProcessor chooseDecoder(Resource resource, Type fileType, int outputNumSamplesPerChannel)
@ -163,7 +207,7 @@ public class SoundFile extends SignalProcessor implements Cloneable
case WAV:
try
{
AudioInputStream stream = AudioSystem.getAudioInputStream(resource.getInputStream());
AudioInputStream stream = AudioSystem.getAudioInputStream(new BufferedInputStream(resource.getInputStream()));
decoder = new PCMStreamConverter(stream, stream.getFormat(), outputNumSamplesPerChannel);
mNumChannels = stream.getFormat().getChannels();

View file

@ -7,7 +7,6 @@ package games.stendhal.client.sound.manager;
import games.stendhal.client.sound.system.SignalProcessor;
import games.stendhal.client.sound.system.SoundSystem;
import games.stendhal.client.sound.system.SoundSystemException;
import games.stendhal.client.sound.system.Time;
import games.stendhal.client.sound.system.processors.DirectedSound;
import games.stendhal.client.sound.system.processors.Interruptor;
@ -17,6 +16,7 @@ import games.stendhal.common.math.Algebra;
import games.stendhal.common.resource.Resource;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@ -31,14 +31,14 @@ import org.apache.log4j.Logger;
*/
public class SoundManager
{
private final static Logger logger = Logger.getLogger(SoundManager.class);
private final static Logger logger = Logger.getLogger(SoundManager.class);
private final static int OUTPUT_NUM_SAMPLES = 256;
private final static int USE_NUM_MIXER_LINES = 15;
private final static int SOUND_CHANNEL_LIMIT = 50;
private final static int USE_NUM_MIXER_LINES = 0;
private final static int DIMENSION = 2;
private final static float[] HEARER_LOOKONG_DIRECTION = { 0.0f, 1.0f };
private final static AudioFormat AUDIO_FORMAT = new AudioFormat(44100, 16, 2, true, false);
public final static InfiniteAudibleArea INFINITE_AUDIBLE_AREA = new InfiniteAudibleArea();
private final static InfiniteAudibleArea INFINITE_AUDIBLE_AREA = new InfiniteAudibleArea();
public final static Time ZERO_DURATION = new Time();
public final static class Sound implements Cloneable
@ -51,13 +51,12 @@ public class SoundManager
{
Sound sound = new Sound();
sound.file.set(file.get().clone());
return sound;
}
public boolean isActive() { return channel.get() != null && channel.get().isActive(); }
}
private final class SoundChannel extends SignalProcessor
{
final float[] mSoundPosition = new float[DIMENSION];
@ -67,9 +66,9 @@ public class SoundManager
final Interruptor mInterruptor = new Interruptor();
final DirectedSound mDirectedSound = new DirectedSound();
final VolumeAdjustor mGlobalVolume = new VolumeAdjustor();
SoundLayers.VolumeAdjustor mLayerVolume = null;
SoundSystem.Output mOutput = null;
Sound mSound = null;
final SoundLayers.VolumeAdjustor mLayerVolume;
final SoundSystem.Output mOutput;
Sound mSound = null;
SoundChannel()
{
@ -84,10 +83,18 @@ public class SoundManager
void setVolume (float volume) { mGlobalVolume.setVolume(volume); }
void startFading (float volume, Time duration) { mGlobalVolume.startFading(volume, duration); }
void setLayer (int level) { mLayerVolume.setLayer(level); }
void setAudibleArea(AudibleArea area) { mAudibleArea.set(area); }
void resumePlayback() { mInterruptor.play(); }
void close () { mSoundSystem.closeOutput(mOutput); }
void playSound(Sound newSound, Time time)
void setAudibleArea(AudibleArea area)
{
if(area == null)
area = INFINITE_AUDIBLE_AREA;
mAudibleArea.set(area);
}
void playSound(Sound newSound, float volume, Time time)
{
if(mSound != null)
{
@ -104,7 +111,7 @@ public class SoundManager
mInterruptor.play();
mGlobalVolume.setVolume(0.0f);
mGlobalVolume.startFading(1.0f, time);
mGlobalVolume.startFading(volume, time);
newSound.channel.set(this);
newSound.file.get().connectTo(mInterruptor, true);
}
@ -115,6 +122,9 @@ public class SoundManager
void stopPlayback(Time time)
{
if(time == null)
time = ZERO_DURATION;
mAutoRepeat.set(false);
mGlobalVolume.startFading(0.0f, time);
mInterruptor.stop(time);
@ -137,7 +147,7 @@ public class SoundManager
}
else
{
playSound(null, null);
playSound(null, 0, null);
super.quit();
}
}
@ -146,44 +156,40 @@ public class SoundManager
private final LinkedList<SoundChannel> mChannels = new LinkedList<SoundChannel>();
private final float[] mHearerPosition = new float[DIMENSION];
private final SoundLayers mSoundLayers = new SoundLayers();
private SoundSystem mSoundSystem = null;
private boolean mMute = false;
private SoundSystem mSoundSystem;
protected SoundManager()
{
Algebra.mov_Vecf(mHearerPosition, 0.0f);
protected SoundManager()
{
try {
Algebra.mov_Vecf(mHearerPosition, 0.0f);
try
{
mSoundSystem = new SoundSystem(AUDIO_FORMAT, new Time(70, Time.Unit.MILLI), USE_NUM_MIXER_LINES);
mSoundSystem.setDaemon(true);
mSoundSystem.start();
}
catch(SoundSystemException exception)
{
mSoundSystem = null;
// TODO: write error message into logger
}
}
public boolean isSoundSystemRunnig()
{
return mSoundSystem != null;
}
mSoundSystem = new SoundSystem(AUDIO_FORMAT, new Time(15, Time.Unit.MILLI), USE_NUM_MIXER_LINES);
mSoundSystem.setDaemon(true);
mSoundSystem.start();
} catch (RuntimeException e) {
logger.error(e, e);
}
}
public Sound openSound(Resource resource, SoundFile.Type fileType)
{
return openSound(resource, fileType, OUTPUT_NUM_SAMPLES, true);
}
public Sound openSound(Resource resource, SoundFile.Type fileType, int numSamplesPerChunk, boolean enableStreaming)
{
Sound sound = null;
try
{
SoundFile file = new SoundFile(resource, fileType, OUTPUT_NUM_SAMPLES, true);
SoundFile file = new SoundFile(resource, fileType, numSamplesPerChunk, enableStreaming);
sound = new Sound();
sound.file.set(file);
}
catch(IOException exception)
{
assert false: exception;
logger.warn(exception);
return null;
}
@ -206,7 +212,7 @@ public class SoundManager
public void play(Sound sound, float volume, int layerLevel, AudibleArea area, boolean autoRepeat, Time fadeInDuration)
{
if(sound == null)
if(sound == null || mMute)
return;
if(sound.isActive())
@ -222,13 +228,14 @@ public class SoundManager
}
else
{
SoundChannel channel = findInactiveChannel();
SoundChannel channel = getInactiveChannel();
channel.setAutoRepeat(autoRepeat);
channel.setVolume(volume);
channel.setLayer(layerLevel);
channel.setAudibleArea(area);
channel.playSound(sound, fadeInDuration);
channel.playSound(sound, volume, fadeInDuration);
channel.update();
closeInactiveChannels(SOUND_CHANNEL_LIMIT);
}
}
@ -255,22 +262,63 @@ public class SoundManager
if(sound != null && sound.isActive())
sound.channel.get().setAudibleArea(area);
}
public void close()
{
if(isSoundSystemRunnig())
{
mSoundSystem.close();
try
{
mSoundSystem.join();
}
catch(InterruptedException exception) { }
}
public void mute(boolean turnOffSound, Time turnOffDuration)
{
if(turnOffSound && !mMute)
{
logger.info("turning off audio");
for(SoundChannel channel: mChannels)
channel.stopPlayback(turnOffDuration);
}
if(!turnOffSound && mMute)
logger.info("turning on audio");
mMute = turnOffSound;
}
public void exit()
{
mSoundSystem.exit();
try
{
mSoundSystem.join();
}
catch(InterruptedException exception)
{
logger.warn("joining sound system thread was interrupted: " + exception);
}
}
private SoundChannel findInactiveChannel()
private void closeInactiveChannels(int leaveNumChannelsOpen)
{
int numChannels = mChannels.size();
Iterator<SoundChannel> iChannel = mChannels.iterator();
while(iChannel.hasNext())
{
if(mChannels.size() <= leaveNumChannelsOpen)
break;
SoundChannel currChannel = iChannel.next();
if(!currChannel.isActive())
{
currChannel.close();
iChannel.remove();
}
}
numChannels -= mChannels.size();
if(numChannels > 0)
logger.debug("close " + numChannels + " inactive sound channels");
}
private SoundChannel getInactiveChannel()
{
SoundChannel foundChannel = null;
@ -285,8 +333,10 @@ public class SoundManager
if(foundChannel == null)
{
mChannels.add(new SoundChannel());
foundChannel = mChannels.getLast();
foundChannel = new SoundChannel();
mChannels.add(foundChannel);
logger.debug("open new sound channel (number " + mChannels.size() + ")");
}
return foundChannel;

View file

@ -5,7 +5,7 @@
package games.stendhal.client.sound.system;
import java.util.ArrayList;
import games.stendhal.common.memory.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
@ -20,7 +20,6 @@ import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import org.apache.log4j.Logger;
/**
@ -29,137 +28,279 @@ import org.apache.log4j.Logger;
*/
public class SoundSystem extends Thread
{
public final static class Output extends SignalProcessor
{
private SourceDataLine mLine = null; // the line we will write the PCM data to
private AudioFormat mAudioFormat = null; // the audio format the line was opened with
private float[] mUniformPCM = null; // the interleaved uniform PCM data. every samples value ranges form -1.0 to 1.0
private byte[] mPCM = null; // the uniform PCM data converted to the format defined in "mAudioFormat"
private int mNumBytesWritten = 0; // number of bytes of "mPCM" we have written to "mLine"
private int mNumSamplesMixed = 0; // number of samples we have mixed into the line of "mMixedOutput"
private int mNumChannels = 0;
private int mNumSamples = 0;
private int mSampleRate = 0;
private boolean mPCMWasConverted = false;
public static abstract class Output extends SignalProcessor { }
private int mRemainingBytesToWritePerCycle = 0;
private static class DummyOutput extends Output { }
private int getRemainingBytesToWrite() { return mPCM.length - mNumBytesWritten; }
private int getRemainingSamplesToMix() { return mNumSamples - mNumSamplesMixed; }
private boolean receivedData () { return mUniformPCM != null; }
private boolean wasPCMConverted () { return mPCMWasConverted; }
private boolean isLineUsable () { return mLine != null; }
private boolean wasAllDataWritten () { return mNumBytesWritten >= mPCM.length; }
private boolean whereAllSamplesMixed () { return mNumSamplesMixed >= mNumSamples; }
private static class SystemOutput extends Output
{
final SourceDataLine mLine; // the line we will write the PCM data to
float[] mAudioBuffer = null; // the interleaved uniform PCM data
int mNumSamples = 0; //
byte[] mPCMBuffer = null; // the uniform PCM data converted to the format defined in "mAudioFormat"
int mPCMBufferSize = 0; //
int mNumBytesWritten = 0; // number of bytes from "mPCMBuffer" we have written to "mLine"
int mNumBytesToWrite = 0;
private int writeToLine(int numBytes)
SystemOutput(SourceDataLine line)
{
assert line != null;
assert line.isOpen();
mLine = line;
mLine.start();
}
boolean isOpen () { return mLine.isOpen(); }
boolean receivedData () { return mNumSamples > 0; }
boolean isConverted () { return mPCMBufferSize > 0; }
AudioFormat getAudioFormat () { return mLine.getFormat(); }
float[] getBuffer () { return mAudioBuffer; }
int getNumSamples () { return mNumSamples; }
int getNumChannels () { return mLine.getFormat().getChannels(); }
int getSampleRate () { return (int)mLine.getFormat().getSampleRate(); }
int getNumBytesPerSample() { return mLine.getFormat().getSampleSizeInBits() / 8; }
int available () { return mLine.available(); }
int getNumBytesToWrite () { return mNumBytesToWrite; }
void close()
{
mLine.close();
mAudioBuffer = null;
mPCMBuffer = null;
reset();
}
void reset()
{
mNumBytesWritten = 0;
mNumSamples = 0;
mPCMBufferSize = 0;
}
void setBuffer(float[] buffer, int numSamples)
{
assert numSamples <= buffer.length;
assert (numSamples % getNumChannels()) == 0;
mAudioBuffer = buffer;
mNumSamples = numSamples;
}
void setNumBytesToWrite(int numBytesToWrite)
{
int frameSize = getNumBytesPerSample() * getNumChannels();
mNumBytesToWrite = numBytesToWrite;
mNumBytesToWrite /= frameSize;
mNumBytesToWrite *= frameSize;
}
void convert()
{
assert mAudioFormat.getChannels() == mNumChannels;
assert (int)mAudioFormat.getSampleRate() == mSampleRate;
assert (mLine.getFormat().getSampleSizeInBits() % 8) == 0;
int written = mLine.write(mPCM, mNumBytesWritten, numBytes);
mNumBytesWritten += written;
return written;
int numBytesPerSample = getNumBytesPerSample();
int numBytes = numBytesPerSample * mNumSamples;
mPCMBuffer = Field.expand(mPCMBuffer, numBytes, false);
mPCMBuffer = SoundSystem.convertUniformPCM(mPCMBuffer, mAudioBuffer, mNumSamples, numBytesPerSample);
mPCMBufferSize = numBytes;
}
private int mixToOutput(Output output, int offsetInSamples, int numSamplesToMix)
boolean write(int numBytes)
{
assert output.mAudioFormat.getChannels() == mNumChannels;
assert (int)output.mAudioFormat.getSampleRate() == mSampleRate;
int frameSize = getNumBytesPerSample() * getNumChannels();
int numRemainingBytesInBuffer = mPCMBufferSize - mNumBytesWritten;
SoundSystem.mixUniformPCM(output.mUniformPCM, offsetInSamples*mNumChannels, mUniformPCM, mNumSamplesMixed*mNumChannels, numSamplesToMix*mNumChannels);
mNumSamplesMixed += numSamplesToMix;
return numSamplesToMix;
numBytes = Math.min(numBytes, numRemainingBytesInBuffer);
numBytes = Math.min(numBytes, mNumBytesToWrite );
numBytes = Math.min(numBytes, mLine.available() );
numBytes /= frameSize;
numBytes *= frameSize;
numBytes = mLine.write(mPCMBuffer, mNumBytesWritten, numBytes);
mNumBytesWritten += numBytes;
mNumBytesToWrite -= numBytes;
numRemainingBytesInBuffer -= numBytes;
if(numRemainingBytesInBuffer < frameSize)
reset();
if(mNumBytesToWrite < frameSize)
return false;
return true;
}
private void convertPCM()
@Override
protected void modify(float[] buffer, int frames, int channels, int rate)
{
assert (mAudioFormat.getSampleSizeInBits() % 8) == 0;
int numBytesPerSample = mAudioFormat.getSampleSizeInBits() / 8;
mPCM = SoundSystem.convertUniformPCM(mPCM, mUniformPCM, mNumSamples * mNumChannels, numBytesPerSample);
mPCMWasConverted = true;
}
private void clearData()
{
mNumBytesWritten = 0;
mNumSamplesMixed = 0;
mUniformPCM = null;
mPCMWasConverted = false;
}
@Override
protected void modify(float[] buffer, int samples, int channels, int rate)
{
if(buffer != null && samples > 0 && channels > 0 && rate > 0)
if(buffer != null && frames > 0 && channels > 0 && rate > 0)
{
assert samples <= buffer.length;
assert (frames * channels) <= buffer.length;
buffer = SoundSystem.convertChannels(buffer, frames, channels, getNumChannels());
mUniformPCM = buffer;
mNumChannels = channels;
mNumSamples = samples;
mSampleRate = rate;
if(!SoundSystem.convertChannels(this))
if(buffer != null)
{
assert false: "could not convert channels";
mUniformPCM = null;
mNumChannels = 0;
mNumSamples = 0;
mSampleRate = 0;
setBuffer(buffer, (frames * getNumChannels()));
}
else
{
setBuffer(null, 0);
//assert false: "could not convert channels";
}
if(!SoundSystem.convertSampleRate(this))
{
assert false: "could not convert sample rate";
buffer = SoundSystem.convertSampleRate(buffer, (frames * channels), channels, rate, getSampleRate());
mUniformPCM = null;
mNumChannels = 0;
mNumSamples = 0;
mSampleRate = 0;
}
if(buffer != null)
{
float ratio = (float)frames / (float)rate;
setBuffer(buffer, (int)(ratio * getSampleRate() * channels));
}
else
{
setBuffer(null, 0);
//assert false: "could not convert sample rate";
}
}
}
}
}
private final static Logger logger = Logger.getLogger(SoundSystem.class);
private final ArrayList<Output> mOutputs = new ArrayList<Output>();
private final Output mMixOutput = new Output();
private Mixer mMixer = null;
private Time mBufferDuration = null;
private final AtomicBoolean mSystemIsRunning = new AtomicBoolean(false);
private final AtomicBoolean mUseDynamicLoadScaling = new AtomicBoolean(false);
private final int mMaxNumLines;
private static class MixerOutput extends Output
{
AudioFormat mAudioFormat;
float[] mAudioBuffer = null; // the interleaved uniform PCM data
int mNumSamples = 0; //
int mNumSamplesMixed = 0;
MixerOutput(AudioFormat format)
{
assert format != null;
mAudioFormat = format;
}
boolean receivedData () { return mNumSamples > 0; }
int getNumChannels() { return mAudioFormat.getChannels(); }
int getSampleRate () { return (int)mAudioFormat.getSampleRate(); }
void reset()
{
mNumSamples = 0;
mNumSamplesMixed = 0;
}
void setBuffer(float[] buffer, int numSamples)
{
assert numSamples <= buffer.length;
assert (numSamples % getNumChannels()) == 0;
mAudioBuffer = buffer;
mNumSamples = numSamples;
}
boolean mix(float[] buffer, int size)
{
int offset = 0;
int numSamplesToMix = size;
while(numSamplesToMix > 0)
{
if(!receivedData())
request();
if(!receivedData())
return false;
int numSamples = mNumSamples - mNumSamplesMixed;
numSamples = Math.min(numSamples, numSamplesToMix);
SoundSystem.mixUniformPCM(buffer, offset, mAudioBuffer, mNumSamplesMixed, numSamples);
offset += numSamples;
mNumSamplesMixed += numSamples;
numSamplesToMix -= numSamples;
if(mNumSamples == mNumSamplesMixed)
reset();
}
return true;
}
@Override
protected void modify(float[] buffer, int frames, int channels, int rate)
{
if(buffer != null && frames > 0 && channels > 0 && rate > 0)
{
assert (frames * channels) <= buffer.length;
buffer = SoundSystem.convertChannels(buffer, frames, channels, getNumChannels());
if(buffer != null)
{
setBuffer(buffer, (frames * getNumChannels()));
}
else
{
setBuffer(null, 0);
//assert false: "could not convert channels";
}
buffer = SoundSystem.convertSampleRate(buffer, (frames * channels), channels, rate, getSampleRate());
if(buffer != null)
{
float ratio = (float)frames / (float)rate;
setBuffer(buffer, (int)(ratio * getSampleRate() * channels));
}
else
{
setBuffer(null, 0);
//assert false: "could not convert sample rate";
}
}
}
}
private final static Logger logger = Logger.getLogger(SoundSystem.class);
private final LinkedList<SystemOutput> mSystemOutputs = new LinkedList<SystemOutput>();
private final LinkedList<MixerOutput> mMixerOutputs = new LinkedList<MixerOutput>();
private SystemOutput mMixSystemOutput = null;
private Mixer mSystemMixer = null;
private Time mBufferDuration = null;
private final AtomicBoolean mSystemIsRunning = new AtomicBoolean(false);
private final AtomicBoolean mUseDynamicLoadScaling = new AtomicBoolean(false);
float[] mMixBuffer = null;
private final int mMaxNumLines;
public SoundSystem(AudioFormat audioFormat, Time bufferDuration, int useMaxMixerLines) throws SoundSystemException
public SoundSystem(AudioFormat audioFormat, Time bufferDuration, int useMaxMixerLines)
{
assert audioFormat != null;
assert bufferDuration != null;
if(audioFormat == null)
throw new IllegalArgumentException("audioFormat argument must not be null");
if(bufferDuration == null)
throw new IllegalArgumentException("bufferDuration argument must not be null");
mMixer = tryToFindMixer(audioFormat);
logger.info("opening sound system and trying to find optimal system mixer device / output line");
mSystemMixer = tryToFindMixer(audioFormat);
mBufferDuration = bufferDuration;
mMaxNumLines = useMaxMixerLines;
if(mMixer == null)
if(mSystemMixer == null)
{
logger.warn("cannot find a system mixer device. manual mixing will be the only option");
DataLine.Info datalineInfo = new DataLine.Info(SourceDataLine.class, audioFormat, AudioSystem.NOT_SPECIFIED);
try
{
mMixOutput.mLine = (SourceDataLine)AudioSystem.getLine(datalineInfo);
if(!AudioSystem.isLineSupported(datalineInfo))
throw new SoundSystemException("line is not supported - " + audioFormat.toString());
mMixOutput.mAudioFormat = audioFormat;
mMixOutput.mLine.open();
SourceDataLine line = (SourceDataLine)AudioSystem.getLine(datalineInfo);
line.open();
mMixSystemOutput = new SystemOutput(line);
}
catch(LineUnavailableException exception)
catch(LineUnavailableException e)
{
mMixOutput.mLine.close();
throw new SoundSystemException("line is unavailable - " + exception.toString());
logger.warn("cannot open output line for manual mixing of audio data: \"" + e + "\"");
}
}
else
@ -168,26 +309,30 @@ public class SoundSystem extends Thread
try
{
mMixOutput.mLine = (SourceDataLine)mMixer.getLine(info);
mMixOutput.mAudioFormat = audioFormat;
mMixOutput.mLine.open();
SourceDataLine line = (SourceDataLine)mSystemMixer.getLine(info);
line.open();
mMixSystemOutput = new SystemOutput(line);
}
catch(LineUnavailableException e)
{
mMixOutput.mLine.close();
mMixOutput.mLine = null;
mMixOutput.mAudioFormat = null;
}
{
logger.warn("cannot open output line for manual mixing of audio data: \"" + e + "\"");
}
}
}
public SoundSystem(Mixer mixer, AudioFormat audioFormat, Time bufferDuration, int useMaxMixerLines)
{
assert audioFormat != null;
assert mixer != null;
assert bufferDuration != null;
if(mixer == null)
throw new IllegalArgumentException("mixer argument must not be null");
if(audioFormat == null)
throw new IllegalArgumentException("audioFormat argument must not be null");
if(bufferDuration == null)
throw new IllegalArgumentException("bufferDuration argument must not be null");
mMixer = mixer;
logger.info("opening sound system using specified system mixer device");
mSystemMixer = mixer;
mBufferDuration = bufferDuration;
mMaxNumLines = useMaxMixerLines;
@ -195,23 +340,29 @@ public class SoundSystem extends Thread
try
{
mMixOutput.mLine = (SourceDataLine)mMixer.getLine(info);
mMixOutput.mAudioFormat = audioFormat;
mMixOutput.mLine.open();
SourceDataLine line = (SourceDataLine)mSystemMixer.getLine(info);
line.open();
mMixSystemOutput = new SystemOutput(line);
}
catch(LineUnavailableException e)
{
mMixOutput.mLine.close();
mMixOutput.mLine = null;
mMixOutput.mAudioFormat = null;
logger.warn("cannot open output line for manual mixing of audio data: \"" + e + "\"");
}
}
public SoundSystem(SourceDataLine outputLine, Time bufferDuration) throws SoundSystemException
public SoundSystem(SourceDataLine outputLine, Time bufferDuration)
{
assert outputLine != null;
assert bufferDuration != null;
if(outputLine == null)
throw new IllegalArgumentException("outputLine argument must not be null");
if(bufferDuration == null)
throw new IllegalArgumentException("bufferDuration argument must not be null");
logger.info("opening sound system");
mMaxNumLines = 0;
mBufferDuration = bufferDuration;
if(!outputLine.isOpen())
{
try
@ -220,97 +371,97 @@ public class SoundSystem extends Thread
}
catch(LineUnavailableException e)
{
throw new SoundSystemException(e.toString());
logger.warn("cannot open output line for manual mixing of audio data: \"" + e + "\"");
return;
}
}
mMaxNumLines = 0;
mMixOutput.mLine = outputLine;
mMixOutput.mAudioFormat = outputLine.getFormat();
mMixOutput.mNumChannels = mMixOutput.mAudioFormat.getChannels();
mMixOutput.mSampleRate = (int)mMixOutput.mAudioFormat.getSampleRate();
mBufferDuration = bufferDuration;
mMixSystemOutput = new SystemOutput(outputLine);
}
public Output openOutput(AudioFormat audioFormat)
public Output openOutput(AudioFormat audioFormat)
{
Output output = new Output();
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat, AudioSystem.NOT_SPECIFIED);
boolean lineUnavailable = false;
if(mMixer != null && mMixer.isLineSupported(info) && mOutputs.size() < mMaxNumLines)
if(audioFormat == null)
throw new IllegalArgumentException("audioFormat argument must not be null");
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat, AudioSystem.NOT_SPECIFIED);
if(mSystemMixer != null && mSystemMixer.isLineSupported(info) && mSystemOutputs.size() < mMaxNumLines)
{
try
{
output.mLine = (SourceDataLine)mMixer.getLine(info);
output.mAudioFormat = audioFormat;
output.mLine.open();
SourceDataLine line = (SourceDataLine)mSystemMixer.getLine(info);
line.open();
SystemOutput output = new SystemOutput(line);
synchronized(mSystemOutputs)
{
mSystemOutputs.add(output);
}
logger.debug("opening a system output (using a system mixer device)");
return output;
}
catch(LineUnavailableException e)
{
output.mLine.close();
output.mLine = null;
output.mAudioFormat = null;
lineUnavailable = true;
}
}
else
{
lineUnavailable = true;
catch(LineUnavailableException e) { }
}
if(lineUnavailable && mMixOutput.isLineUsable())
output.mAudioFormat = mMixOutput.mAudioFormat;
if(mMixSystemOutput != null)
{
MixerOutput output = new MixerOutput(mMixSystemOutput.getAudioFormat());
synchronized(this)
{
mOutputs.add(output);
}
return output;
}
synchronized(mMixerOutputs)
{
mMixerOutputs.add(output);
}
logger.debug("opening a mixer output (using manual mixing)");
return output;
}
logger.debug("opening a dummy output (no output line could be received)");
return new DummyOutput();
}
public synchronized void closeOutput(Output output)
{
if(output.isLineUsable())
{
output.mLine.close();
output.mLine = null;
}
if(output != null)
{
output.disconnect();
output.clearData();
output.disconnect();
if(output instanceof SystemOutput)
{
logger.debug("closing a system output");
mOutputs.remove(output);
SystemOutput systemOutput = (SystemOutput)output;
systemOutput.close();
mSystemOutputs.remove(systemOutput);
}
if(output instanceof MixerOutput)
{
logger.debug("closing a mixer output");
MixerOutput mixerOutput = (MixerOutput)output;
mMixerOutputs.remove(mixerOutput);
}
}
}
public synchronized void closeAllOutputs()
{
for(Output output: mOutputs)
{
if(output.isLineUsable())
{
output.mLine.close();
output.mLine = null;
}
logger.debug("closing all outputs excluding the output for manual mixing");
for(SystemOutput output: mSystemOutputs)
output.close();
output.clearData();
output.disconnect();
}
mOutputs.clear();
if(mMixOutput.isLineUsable())
{
mMixOutput.mLine.stop();
mMixOutput.mLine.close();
mMixOutput.mLine = null;
mMixOutput.clearData();
}
mSystemOutputs.clear();
mMixerOutputs.clear();
}
public void close()
public void exit()
{
logger.info("stopping sound system");
mSystemIsRunning.set(false);
}
@ -320,21 +471,14 @@ public class SoundSystem extends Thread
mSystemIsRunning.set(true);
double averageTimeToProcessSound = mBufferDuration.getInNanoSeconds();
double multiplicator = 0.995;
double multiplicator = 0.990;
while(mSystemIsRunning.get())
{
long duration = System.nanoTime();
try
{
processOutputs();
}
catch(RuntimeException e)
{
logger.warn(e, e);
}
processOutputs();
duration = System.nanoTime() - duration;
averageTimeToProcessSound = (averageTimeToProcessSound + duration) / 2.0;
@ -349,134 +493,82 @@ public class SoundSystem extends Thread
}
closeAllOutputs();
closeOutput(mMixSystemOutput);
}
private int min(int a, int b, int c)
{
return Math.min(a, Math.min(b,c));
}
private void prepareManualMixing()
{
int numBytesPerSample = mMixOutput.mAudioFormat.getSampleSizeInBits() / 8;
int numChannels = mMixOutput.mAudioFormat.getChannels();
int sampleRate = (int)mMixOutput.mAudioFormat.getSampleRate();
int numSamples = (int)mBufferDuration.getInSamples(sampleRate);
@SuppressWarnings("unchecked")
private void processOutputs()
{
LinkedList<SystemOutput> sysOutputs;
LinkedList<MixerOutput> mixOutputs;
if(mMixOutput.mUniformPCM == null || mMixOutput.mUniformPCM.length < (numSamples * numChannels))
mMixOutput.mUniformPCM = new float[numSamples * numChannels];
synchronized(mSystemOutputs)
{
sysOutputs = (LinkedList<SystemOutput>)mSystemOutputs.clone();
}
mMixOutput.mNumSamples = numSamples;
mMixOutput.mRemainingBytesToWritePerCycle = numSamples * numChannels * numBytesPerSample;
Arrays.fill(mMixOutput.mUniformPCM, 0, numSamples, 0.0f);
}
private void processOutputs()
{
LinkedList<Output> outputsWithUsableLines = new LinkedList<Output>();
synchronized(mMixerOutputs)
{
mixOutputs = (LinkedList<MixerOutput>)mMixerOutputs.clone();
}
if(mMixOutput.isLineUsable())
{
prepareManualMixing();
outputsWithUsableLines.add(mMixOutput);
}
if(mMixSystemOutput != null)
sysOutputs.add(mMixSystemOutput);
synchronized(this)
{
for(Output output: mOutputs)
{
/* happens for example if the audio device is in use by an other applicatin
if(output.mAudioFormat == null)
continue; //*/
for(SystemOutput output: sysOutputs)
{
int sampleRate = output.getSampleRate();
int numBytesPerSample = output.getAudioFormat().getSampleSizeInBits() / 8;
int numChannels = output.getNumChannels();
int numBytesToWrite = (int)(mBufferDuration.getInSamples(sampleRate) * numChannels * numBytesPerSample);
int numBytesPerSample = output.mAudioFormat.getSampleSizeInBits() / 8;
int numChannels = output.mAudioFormat.getChannels();
int sampleRate = (int)output.mAudioFormat.getSampleRate();
output.setNumBytesToWrite(numBytesToWrite);
}
if(output.isLineUsable())
{
output.mRemainingBytesToWritePerCycle =
(int)mBufferDuration.getInSamples(sampleRate) * numChannels * numBytesPerSample;
if(mMixSystemOutput != null)
{
int numSamples = mMixSystemOutput.getNumBytesToWrite() / mMixSystemOutput.getNumBytesPerSample();
mMixBuffer = Field.expand(mMixBuffer, numSamples, false);
Arrays.fill(mMixBuffer, 0, numSamples, 0.0f);
outputsWithUsableLines.add(output);
}
else if(mMixOutput.isLineUsable())
{
int remainingSamplesToMix = mMixOutput.mNumSamples;
int numSmplesMixed = 0;
for(MixerOutput output: mixOutputs)
output.mix(mMixBuffer, numSamples);
while(remainingSamplesToMix > 0)
{
if(!output.receivedData())
output.request(); // if we got no sound data we request it
mMixSystemOutput.setBuffer(mMixBuffer, numSamples);
}
while(!sysOutputs.isEmpty())
{
boolean buffersAreFull = true;
if(output.receivedData())
{
int numSamplesToMix = Math.min(output.getRemainingSamplesToMix(), remainingSamplesToMix);
int count = output.mixToOutput(mMixOutput, numSmplesMixed, numSamplesToMix);
for(Iterator<SystemOutput> iOutput = sysOutputs.iterator(); iOutput.hasNext(); )
{
SystemOutput output = iOutput.next();
remainingSamplesToMix -= count;
numSmplesMixed += count;
if(output.isOpen())
{
if(!output.receivedData())
output.request(); // if we got no sound data we request it
if(output.whereAllSamplesMixed())
output.clearData();
}
else break;
}
}
}
}
if(output.receivedData())
{
if(!output.isConverted())
output.convert();
while(!outputsWithUsableLines.isEmpty())
{
boolean lineBuffersAreFull = true;
buffersAreFull = buffersAreFull && (output.available() == 0);
for(Iterator<Output> iOutput = outputsWithUsableLines.iterator(); iOutput.hasNext(); )
{
Output output = iOutput.next();
if(output.write(256))
continue;
}
}
if(!output.receivedData())
output.request(); // if we got no sound data we request it
iOutput.remove();
}
if(output.receivedData())
{
if(!output.mLine.isRunning())
output.mLine.start();
if(!output.wasPCMConverted())
output.convertPCM();
assert output.getRemainingBytesToWrite() != 0;
assert output.mRemainingBytesToWritePerCycle != 0;
lineBuffersAreFull = lineBuffersAreFull && (output.mLine.available() == 0);
int numBytesWritable =
min(output.mLine.available(), output.getRemainingBytesToWrite(), output.mRemainingBytesToWritePerCycle);
output.mRemainingBytesToWritePerCycle -= output.writeToLine(numBytesWritable);
if(output.wasAllDataWritten())
output.clearData();
if(output.mRemainingBytesToWritePerCycle <= 0)
iOutput.remove();
}
else
{
// if after we requested data we still don't have any, we quit processing of this output
iOutput.remove();
break;
}
}
if(lineBuffersAreFull)
{
if(!mUseDynamicLoadScaling.get())
suspendThread(50);
}
}
}
if(buffersAreFull && !mUseDynamicLoadScaling.get())
suspendThread(50);
}
}
@Override
public String toString()
@ -567,11 +659,9 @@ public class SoundSystem extends Thread
private static byte[] convertUniformPCM(byte[] pcmBuffer, float[] uniformSamples, int numUniformSamples, int numBytesPerSample)
{
int numBitsPerSample = numBytesPerSample * 8;
long maxValue = (long)((Math.pow(2, numBitsPerSample) - 1.0) / 2.0);
if(pcmBuffer == null || pcmBuffer.length < (numBytesPerSample * numUniformSamples))
pcmBuffer = new byte[numBytesPerSample * numUniformSamples];
long maxValue = (long)((Math.pow(2, numBitsPerSample) - 1.0) / 2.0);
pcmBuffer = Field.expand(pcmBuffer, (numBytesPerSample * numUniformSamples), false);
for(int i=0; i<numUniformSamples; ++i)
{
@ -586,7 +676,7 @@ public class SoundSystem extends Thread
}
return pcmBuffer;
}
}
private static void mixUniformPCM(float[] result, int rBegin, float[] data, int dBegin, int numSamples)
{
@ -599,7 +689,7 @@ public class SoundSystem extends Thread
}
}
private static boolean convertChannels(Output output)
private static float[] convertChannels(float[] buffer, int numFrames, int numChannels, int numRequiredChannels)
{
/* Assignments for audio channels
* channel 0: Front left
@ -612,51 +702,49 @@ public class SoundSystem extends Thread
* channel 7: Surround back right - ## NEEDS CONFIRMATION ##
*/
if(!output.receivedData() || output.mNumChannels == output.mAudioFormat.getChannels())
return true;
int numRequiredChannels = output.mAudioFormat.getChannels();
if(numChannels == numRequiredChannels)
return buffer;
// we have to reduce the number of channels
if(output.mNumChannels > numRequiredChannels)
if(numChannels > numRequiredChannels)
{
// stereo/multichannel to mono - maybe this won't work properly for more than 2 channels
// - ## NEEDS CONFIRMATION ##
if(numRequiredChannels == 1)
{
for(int i=0; i<output.mNumSamples; ++i)
for(int i=0; i<numFrames; ++i)
{
int index = i * output.mNumChannels;
int index = i * numChannels;
float value = 0.0f;
for(int c=0; c<output.mNumChannels; ++c)
value += output.mUniformPCM[index + c];
for(int c=0; c<numChannels; ++c)
value += buffer[index + c];
output.mUniformPCM[i] = value / (float)output.mNumChannels;
buffer[i] = value / (float)numChannels;
}
}
else
{
// not implemented yet
return false;
buffer = null;
}
}
else // we have to increase the number of channels
{
// mono to stereo/multichannel
if(output.mNumChannels == 1)
if(numChannels == 1)
{
float[] newUniformPCM = new float[output.mNumSamples * numRequiredChannels];
float[] newUniformPCM = new float[numFrames * numRequiredChannels];
for(int i=0; i<output.mNumSamples; ++i)
for(int i=0; i<numFrames; ++i)
{
int index = i * numRequiredChannels;
for(int c=0; c<numRequiredChannels; ++c)
newUniformPCM[index + c] = output.mUniformPCM[i];
newUniformPCM[index + c] = buffer[i];
}
output.mUniformPCM = newUniformPCM;
buffer = newUniformPCM;
}
else // stereo/multichannel to multichannel
{
@ -669,20 +757,19 @@ public class SoundSystem extends Thread
*/
// not implemented yet
return false;
buffer = null;
}
}
output.mNumChannels = numRequiredChannels;
return true;
return buffer;
}
private static boolean convertSampleRate(Output output)
private static float[] convertSampleRate(float[] buffer, int numSamples, int numChannels, int sampleRate, int toSampleRate)
{
if(!output.receivedData() || output.mSampleRate == (int)output.mAudioFormat.getSampleRate())
return true;
if(sampleRate == toSampleRate)
return buffer;
// not implemented yet
return false;
return null;
}
}

View file

@ -7,6 +7,7 @@ package games.stendhal.client.sound.system.processors;
import games.stendhal.common.math.Algebra;
import games.stendhal.client.sound.system.SignalProcessor;
import games.stendhal.common.memory.Field;
/**
*
@ -14,10 +15,11 @@ import games.stendhal.client.sound.system.SignalProcessor;
*/
public class DirectedSound extends SignalProcessor
{
private final float[] mUpVector = { 0.0f, 1.0f, 0.0f };
private float mIntensity = 1.0f;
private float mLVolume = 1.0f;
private float mRVolume = 1.0f;
private final float[] mUpVector = { 0.0f, 1.0f, 0.0f };
private float mIntensity = 1.0f;
private float mLVolume = 1.0f;
private float mRVolume = 1.0f;
private float[] mOutputBuffer = null;
public DirectedSound() { }
@ -95,15 +97,15 @@ public class DirectedSound extends SignalProcessor
if(channels == 1)
{
float[] newData = new float[samples * 2];
mOutputBuffer = Field.expand(mOutputBuffer, (samples * 2), false);
for(int i=0; i<samples; ++i)
{
newData[i*2 + 0] = data[i] * mLVolume; // multiply with the positional volume
newData[i*2 + 1] = data[i] * mRVolume; // do the same for the right channel
mOutputBuffer[i*2 + 0] = data[i] * mLVolume; // multiply with the positional volume
mOutputBuffer[i*2 + 1] = data[i] * mRVolume; // do the same for the right channel
}
data = newData;
data = mOutputBuffer;
channels = 2;
}
else // if(channels == 2)

View file

@ -10,6 +10,7 @@ import java.io.InputStream;
import java.io.IOException;
import com.jcraft.jogg.*;
import com.jcraft.jorbis.*;
import games.stendhal.common.memory.Field;
/**
*
* @author silvio
@ -23,7 +24,7 @@ public class OggVorbisDecoder extends SignalProcessor
private Comment mVorbisComment = null;
private Info mVorbisInfo = null;
private float[][][] mUniformPCMData = null;
private float[][][] mUniformPCMData = new float[1][][];
int[] mPCMIndex = null;
private float[] mOutputBuffer = null;
private byte[] mInputBuffer = null;
@ -57,9 +58,8 @@ public class OggVorbisDecoder extends SignalProcessor
// IMPORTANT: read the header first before setting up the buffers
// or else getNumChannels() will not return the right number
mUniformPCMData = new float[1][][];
mPCMIndex = new int[getNumChannels()];
mOutputBuffer = new float[outputNumSamplesPerChannel * getNumChannels()];
mPCMIndex = Field.expand(mPCMIndex , getNumChannels() , false);
mOutputBuffer = Field.expand(mOutputBuffer, (outputNumSamplesPerChannel * getNumChannels()), false);
mDecoderIsOpened = true;
}
@ -287,7 +287,6 @@ public class OggVorbisDecoder extends SignalProcessor
assert false: exception.toString();
}
mOutputBuffer = null;
mIStream = null;
mDecoderIsOpened = false;
mEndOfStream = true;

View file

@ -6,6 +6,7 @@
package games.stendhal.client.sound.system.processors;
import games.stendhal.client.sound.system.SignalProcessor;
import games.stendhal.common.memory.Field;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.sampled.AudioFormat;
@ -18,8 +19,8 @@ public class PCMStreamConverter extends SignalProcessor
{
private InputStream mInputStream;
private AudioFormat mAudioFormat;
private float[] mOutputBuffer;
private byte[] mInputBuffer;
private float[] mOutputBuffer = null;
private byte[] mInputBuffer = null;
private int mNumSamplesToBuffer;
private boolean mEndOfStream;
private boolean mConverterIsOpened;
@ -45,18 +46,19 @@ public class PCMStreamConverter extends SignalProcessor
mEndOfStream = true;
mConverterIsOpened = false;
try
{
if (mInputStream != null) {
mInputStream.close();
}
}
catch(IOException exception)
{
assert false: exception.toString();
}
if(mInputStream != null)
{
try
{
mInputStream.close();
}
catch(IOException exception)
{
assert false: exception.toString();
}
mInputStream = null;
mInputStream = null;
}
}
private void init(InputStream stream, AudioFormat format, int outputNumSamplesPerChannel)
@ -69,12 +71,8 @@ public class PCMStreamConverter extends SignalProcessor
int outputBufferSize = outputNumSamplesPerChannel * format.getChannels();
int inputBufferSize = outputNumSamplesPerChannel * format.getFrameSize();
if(mInputBuffer == null || mInputBuffer.length < inputBufferSize)
mInputBuffer = new byte[inputBufferSize];
if(mOutputBuffer == null || mOutputBuffer.length < outputBufferSize)
mOutputBuffer = new float[outputBufferSize];
mInputBuffer = Field.expand(mInputBuffer , inputBufferSize , false);
mOutputBuffer = Field.expand(mOutputBuffer, outputBufferSize, false);
mInputStream = stream;
mAudioFormat = format;
mNumSamplesToBuffer = outputNumSamplesPerChannel;

View file

@ -6,6 +6,7 @@
package games.stendhal.client.sound.system.processors;
import games.stendhal.client.sound.system.SignalProcessor;
import games.stendhal.common.memory.Field;
/**
*
@ -13,33 +14,62 @@ import games.stendhal.client.sound.system.SignalProcessor;
*/
public class Recorder extends SignalProcessor
{
public class Player extends SignalProcessor
{
private float[] mOutputBuffer;
private int mNumSamplesRead = 0;
private Player(int outputNumSamples)
{
mOutputBuffer = new float[outputNumSamples * mNumChannels];
}
public synchronized void restart () { mNumSamplesRead = 0; }
public synchronized boolean reachedEndOfStream() { return mNumSamplesRead >= mNumSamplesBuffered; }
public synchronized int getNumChannels () { return mNumChannels; }
public synchronized int getSampleRate () { return mSampleRate; }
public synchronized int getNumSamples () { return mNumSamplesBuffered / mNumChannels; }
@Override
protected synchronized boolean generate()
{
if(reachedEndOfStream())
{
super.quit();
return false;
}
int outputBufferSize = mOutputBuffer.length;
int numSamplesAvailable = mNumSamplesBuffered - mNumSamplesRead;
int numSamples = Math.min(outputBufferSize, numSamplesAvailable);
System.arraycopy(mData, mNumSamplesRead, mOutputBuffer, 0, numSamples);
mNumSamplesRead += numSamples;
super.propagate(mOutputBuffer, (numSamples / mNumChannels), mNumChannels, mSampleRate);
return true;
}
}
private float[] mData = null;
private float[] mOutputBuffer = null;
private int mNumChannels = 1;
private int mSampleRate = 0;
private int mNumSamplesBuffered = 0;
private int mNumSamplesRead = 0;
private int mOutputNumSamples;
public Recorder(int outputNumSamplesPerChannel)
{
mOutputNumSamples = outputNumSamplesPerChannel;
}
public Player createPlayer(int outputNumSamplesPerChannel)
{
return new Player(outputNumSamplesPerChannel);
}
public synchronized void clear()
{
mNumSamplesBuffered = 0;
mNumSamplesRead = 0;
mData = null;
mOutputBuffer = null;
}
public synchronized void restart () { mNumSamplesRead = 0; }
public synchronized boolean reachedEndOfStream() { return mNumSamplesRead >= mNumSamplesBuffered; }
public synchronized int getNumChannels () { return mNumChannels; }
public synchronized int getSampleRate () { return mSampleRate; }
public synchronized int getNumSamples () { return mNumSamplesBuffered / mNumChannels; }
public synchronized int getNumChannels() { return mNumChannels; }
public synchronized int getSampleRate () { return mSampleRate; }
public synchronized int getNumSamples () { return mNumSamplesBuffered / mNumChannels; }
public synchronized void trim()
{
@ -78,38 +108,11 @@ public class Recorder extends SignalProcessor
// if the buffer is to small to hold the whole data, we expand it
if(remainingBufferSpace < requiredBufferSpace)
{
float[] newData = new float[(mNumSamplesBuffered + requiredBufferSpace) * 2];
System.arraycopy(mData, 0, newData, 0, mNumSamplesBuffered);
mData = newData;
}
mData = Field.expand(mData, (mNumSamplesBuffered + requiredBufferSpace) * 2, true);
System.arraycopy(data, 0, mData, mNumSamplesBuffered, requiredBufferSpace);
mNumSamplesBuffered += requiredBufferSpace;
super.propagate(data, samples, channels, rate);
}
@Override
protected boolean generate()
{
if(reachedEndOfStream())
{
super.quit();
return false;
}
int numSamplesToRead = mOutputNumSamples * mNumChannels;
int numSamplesAvailable = mNumSamplesBuffered - mNumSamplesRead;
int numSamples = Math.min(numSamplesToRead, numSamplesAvailable);
if(mOutputBuffer == null)
mOutputBuffer = new float[numSamplesToRead];
System.arraycopy(mData, mNumSamplesRead, mOutputBuffer, 0, numSamples);
mNumSamplesRead += numSamples;
super.propagate(mOutputBuffer, (numSamples / mNumChannels), mNumChannels, mSampleRate);
return true;
}
}

View file

@ -206,6 +206,7 @@ public class SpriteStore {
// avoid infinite loop and stack overflow in case of missing
// failsafe icon
if (ref.equals(FAILSAFE_ICON_REF)) {
logger.error("Cannot find FAILSAFE_ICON_REF: " + FAILSAFE_ICON_REF);
return null;
} else {
return getFailsafe();
@ -215,8 +216,14 @@ public class SpriteStore {
// use ImageIO to read the image in
sourceImage = ImageIO.read(url);
} catch (final IOException e) {
logger.error("Failed to load: " + ref, e);
return null;
logger.error("Failed to read: " + ref, e);
if (ref.equals(FAILSAFE_ICON_REF)) {
logger.error("Cannot read FAILSAFE_ICON_REF: " + FAILSAFE_ICON_REF);
return null;
} else {
return getFailsafe();
}
}
// create an accelerated image of the right size to store our sprite in

View file

@ -110,7 +110,7 @@ public class stendhal {
logger.info("Java-VM: " + System.getProperty("java.vm.vendor") + " "
+ System.getProperty("java.vm.name") + " "
+ System.getProperty("java.vm.version"));
LogUncaughtExceptionHandler.setup();
}
/**

View file

@ -10,7 +10,7 @@ package games.stendhal.client.update;
public class Version {
/** Version Number. */
public static final String VERSION = "0.80.5";
public static final String VERSION = "0.81.3";
/**
* Extract the specified number of parts from a version-string.

View file

@ -25,7 +25,7 @@ public interface Debug {
/** server version. */
// Note: This line is updated by build.xml using a regexp so be sure to adjust it in case you modify this line.
String VERSION = "0.80.5";
String VERSION = "0.81.3";
/**

View file

@ -0,0 +1,94 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package games.stendhal.common.memory;
/**
*
* @author silvio
*/
public class Field
{
/**
* This method will return a new allocated array of size newSize if newSize
* is greater than array.length.
* Tt will return the same array otherwise
* Only if keepData is set to true, the data of the array will be copied to the
* new allocated array
*
* @param array
* @param newSize
* @param keepData
* @return - new or old array
*/
public static byte[] expand(byte[] array, int newSize, boolean keepData)
{
if(array == null || array.length < newSize)
{
byte[] newArray = new byte[newSize];
if(keepData && array != null)
System.arraycopy(array, 0, newArray, 0, array.length);
return newArray;
}
return array;
}
/**
* This method will return a new allocated array of size newSize if newSize
* is greater than array.length.
* Tt will return the same array otherwise
* Only if keepData is set to true, the data of the array will be copied to the
* new allocated array
*
* @param array
* @param newSize
* @param keepData
* @return - new or old array
*/
public static int[] expand(int[] array, int newSize, boolean keepData)
{
if(array == null || array.length < newSize)
{
int[] newArray = new int[newSize];
if(keepData && array != null)
System.arraycopy(array, 0, newArray, 0, array.length);
return newArray;
}
return array;
}
/**
* This method will return a new allocated array of size newSize if newSize
* is greater than array.length.
* Tt will return the same array otherwise
* Only if keepData is set to true, the data of the array will be copied to the
* new allocated array
*
* @param array
* @param newSize
* @param keepData
* @return - new or old array
*/
public static float[] expand(float[] array, int newSize, boolean keepData)
{
if(array == null || array.length < newSize)
{
float[] newArray = new float[newSize];
if(keepData && array != null)
System.arraycopy(array, 0, newArray, 0, array.length);
return newArray;
}
return array;
}
}

View file

@ -55,11 +55,18 @@ public class ResourceManager extends ResourceLocator
}
}
private class CPSchemeLocator implements ResourceLocator.Locator
private interface SchemeLocator
{
void addSearchPath(String path);
InputStream locate (URI uri);
boolean isLocatable (URI uri);
}
private class CPSchemeLocator implements ResourceLocator.Locator, SchemeLocator
{
final ArrayList<String> mPaths = new ArrayList<String>();
void addSearchPath(String path)
public void addSearchPath(String path)
{
if(path.endsWith("/"))
path = path.substring(0, path.length() - 1);
@ -87,25 +94,27 @@ public class ResourceManager extends ResourceLocator
{
InputStream stream = locate(uri);
try
if(stream != null)
{
stream.close();
try
{
stream.close();
}
catch(IOException exception)
{
assert false: exception;
stream = null;
}
}
catch(IOException exception)
{
assert false: exception;
stream = null;
}
return stream != null;
}
}
private class SchemeLocator implements ResourceLocator.Locator
private class FSSchemeLocator implements ResourceLocator.Locator, SchemeLocator
{
final ArrayList<File> mDirectories = new ArrayList<File>();
void addSearchPath(String path)
public void addSearchPath(String path)
{
File directory = new File(mRootDirectory, path);
@ -113,7 +122,7 @@ public class ResourceManager extends ResourceLocator
mDirectories.add(directory);
}
File locateFile(URI uri)
public File locateFile(URI uri)
{
for(File directory: mDirectories)
{
@ -146,7 +155,7 @@ public class ResourceManager extends ResourceLocator
}
}
private final HashMap<String,CPSchemeLocator> mSchemeLocators = new HashMap<String,CPSchemeLocator>();
private final HashMap<String,SchemeLocator> mSchemeLocators = new HashMap<String,SchemeLocator>();
private File mRootDirectory = null;
public void addScheme(String name, String ...searchPaths)
@ -156,10 +165,10 @@ public class ResourceManager extends ResourceLocator
if(!mSchemeLocators.containsKey(name))
{
CPSchemeLocator locator = new CPSchemeLocator();
SchemeLocator locator = new CPSchemeLocator();
mSchemeLocators.put(name, locator);
setLocator(name, locator);
setLocator(name, (Locator)locator);
for(String path: searchPaths)
locator.addSearchPath(path);
@ -168,7 +177,7 @@ public class ResourceManager extends ResourceLocator
public void addSearchPath(String scheme, String path)
{
CPSchemeLocator locator = mSchemeLocators.get(scheme);
SchemeLocator locator = mSchemeLocators.get(scheme);
assert locator != null;
assert path != null;

View file

@ -233,7 +233,7 @@ public class GenerateINI {
out.println();
out.println("server_typeGame=" + gameName);
out.println("server_name=" + gameName + " Marauroa server");
out.println("server_version=0.80.5");
out.println("server_version=0.81.3");
out.println("server_contact=https://sourceforge.net/tracker/?atid=514826&group_id=66537&func=browse");
out.println();
out.println("# Extensions configured on the server. Enable at will.");

View file

@ -36,6 +36,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@ -210,15 +211,18 @@ public class StendhalRPRuleProcessor implements IRPRuleProcessor {
*
* @param entity
* The entity to check.
* @return true if the given entity has been killed this turn.
* @return pair of killed, killer if the specified entity did logout while dying, <code>null</code> otherwise.
*/
private boolean wasKilled(final RPEntity entity) {
for (final Pair<RPEntity, Entity> entry : entityToKill) {
private Pair<RPEntity, Entity> removedKilled(final RPEntity entity) {
Iterator<Pair<RPEntity, Entity>> itr = entityToKill.iterator();
while (itr.hasNext()) {
Pair<RPEntity, Entity> entry = itr.next();
if (entity.equals(entry.first())) {
return true;
itr.remove();
return entry;
}
}
return false;
return null;
}
/**
@ -384,14 +388,22 @@ public class StendhalRPRuleProcessor implements IRPRuleProcessor {
if (adminNames == null) {
adminNames = new LinkedList<String>();
final String adminFilename = "data/conf/admins.list";
String adminFilename = "data/conf/admins.txt";
final String adminFilenameAlt = "data/conf/admins.list";
try {
final InputStream is = player.getClass().getClassLoader().getResourceAsStream(
InputStream is = player.getClass().getClassLoader().getResourceAsStream(
adminFilename);
if (is == null) {
logger.info("data/conf/admins.list does not exist.");
// backwards compatibility for those who used to have admins.list
is = player.getClass().getClassLoader().getResourceAsStream(
adminFilenameAlt);
if (is == null) {
logger.info("Neither " + adminFilename + " nor " + adminFilenameAlt + " exist.");
}
// update filename for any error messages
adminFilename = adminFilenameAlt;
} else {
final BufferedReader in = new BufferedReader(
@ -495,10 +507,13 @@ public class StendhalRPRuleProcessor implements IRPRuleProcessor {
public synchronized boolean onExit(final RPObject object) {
try {
final Player player = (Player) object;
if (wasKilled(player)) {
logger.info("Logged out shortly before death: Killing it now :)");
player.onDead(killerOf(player));
Pair<RPEntity, Entity> entry = removedKilled(player);
if (entry != null) {
logger.info(object.get("name") + " logged out shortly before death: Killing it now :)");
entry.first().onDead(entry.second());
}
if (!player.isGhost()) {
notifyOnlineStatus(false, player.getName());
}

View file

@ -1,6 +1,7 @@
package games.stendhal.server.entity.npc.behaviour.adder;
import games.stendhal.common.Grammar;
import games.stendhal.common.constants.SoundLayer;
import games.stendhal.server.entity.npc.ChatAction;
import games.stendhal.server.entity.npc.ChatCondition;
import games.stendhal.server.entity.npc.ConversationPhrases;
@ -126,7 +127,7 @@ public class BuyerAdder {
boolean success = behaviour.transactAgreedDeal(engine, player);
if (success) {
engine.addEvent(new SoundEvent("coins-1"));
engine.addEvent(new SoundEvent("coins-1", SoundLayer.CREATURE_NOISE));
}
}
});

View file

@ -1,6 +1,7 @@
package games.stendhal.server.entity.npc.behaviour.adder;
import games.stendhal.common.Grammar;
import games.stendhal.common.constants.SoundLayer;
import games.stendhal.server.entity.npc.ChatAction;
import games.stendhal.server.entity.npc.ChatCondition;
import games.stendhal.server.entity.npc.ConversationPhrases;
@ -125,7 +126,7 @@ public class SellerAdder {
boolean success = behaviour.transactAgreedDeal(engine, player);
if (success) {
engine.addEvent(new SoundEvent("coins-1"));
engine.addEvent(new SoundEvent("coins-1", SoundLayer.CREATURE_NOISE));
}
}
});

View file

@ -17,7 +17,10 @@ import java.util.List;
import marauroa.common.game.RPObject;
import marauroa.common.game.RPSlot;
import org.apache.log4j.Logger;
public abstract class UpdateConverter {
private static Logger logger = Logger.getLogger(UpdateConverter.class);
private static final List<String> ITEM_NAMES_OLD = Arrays.asList(
"flail_+2", "leather_armor_+1", "leather_cuirass_+1",
@ -212,6 +215,12 @@ public abstract class UpdateConverter {
// port to 0.66
transformKillSlot(object);
// port to 0.81 because of a bug in 0.80 which allowed 0 hp by double killing on logout during dying
if (object.getInt("hp") <= 0) {
logger.warn("Setting hp to 1 for player " + object);
object.put("hp", 1);
}
}
/**

View file

@ -30,9 +30,10 @@ public class SoundEvent extends RPEvent {
*
* @param sound name of sound to play
*/
public SoundEvent(final String sound) {
public SoundEvent(final String sound, SoundLayer layer) {
super(Events.SOUND);
put("sound", sound);
put("layer", layer.ordinal());
}

View file

@ -22,7 +22,7 @@ Some of these people are NPC, they will give you tasks to accomplish and hints t
</security>
<resources>
<j2se href="http://java.sun.com/products/autodl/j2se" version="1.5+" /> <!-- max-heap-size="120" -->
<jar href="http://arianne.sourceforge.net/jws/stendhal-starter-0.80.5.jar" download="eager" main="true" />
<jar href="http://arianne.sourceforge.net/jws/stendhal-starter-0.81.3.jar" download="eager" main="true" />
</resources>
<application-desc/>
</jnlp>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE map SYSTEM "http://mapeditor.org/dtd/1.0/map.dtd">
<map version="1.0" orientation="orthogonal" width="64" height="64" tilewidth="32" tileheight="32">
<?xml version="1.0"?>
<map version="0.99b" orientation="orthogonal" width="64" height="64" tilewidth="32" tileheight="32">
<tileset name="../../tileset/ground/ground.png" firstgid="1" tilewidth="32" tileheight="32">
<image source="../../tileset/ground/ground.png" trans="ffffff"/>
</tileset>
@ -191,12 +190,12 @@
</layer>
<layer name="1_terrain" width="64" height="64">
<data encoding="base64" compression="gzip">
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
</data>
</layer>
<layer name="2_object" width="64" height="64">
<data encoding="base64" compression="gzip">
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
</data>
</layer>
<layer name="3_roof" width="64" height="64">
@ -209,7 +208,7 @@
H4sIAAAAAAAAAO3ZSwrCMBSFYc1WVFDQFfjW+q5Dceb+tyA48IZS6MBIU01Ik/+DMxPqIaE3xk4HrmTKHG2rzIlB1rBHbP3vFn1uka3/TrI35GDoGVP/o+RkyJn+H9Hf7/d0Rfe/SHJDrgn0T3n+A0Bb5JW00T/mxlQVCVF1broyU0VQn8/zyrPr71m2mt53hGYuPRaSZSR9bK2k91qyqfTvyb7rSwYB7z+grlBnvC/MeAAIn+n3RSznbXxns/7lZ9kbadLr/1Du17/8nybVc3TZn3M0kB59H/ZyeB82lPfKKOF3y1i6TxLuDwAAAAAAAAD4zRs+ikUJAEAAAA==
</data>
</layer>
<layer name="objects" width="64" height="64" visible="0">
<layer name="objects" width="64" height="64">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAAAO3ZQQ6CMBAFUMJFdaEmHsY7uzCEDSlBpmacvpd0SzrDp03pNAH/6jp/BuP2YcnAqPX3Mmo/ZQmgj/u8DgCIYE8hwlaOWtmSO8ijwv+bvRpeBWpsqfAOz9irv3p/WvX5RwsQ62JNJaEj5+vMZ/Fb4rnRT8V1NfN3BsA5FfctvveQByCZ5S4g651A1nn9wtZdzTOoH1HPyWL0nACwcgbv5w0mQTIgAEAAAA==
</data>