Compare commits

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

17 commits

Author SHA1 Message Date
Hendrik Brummermann
dfaa1b7b74 increased version number 2010-09-19 18:46:03 +00:00
Hendrik Brummermann
0e2d8f82f1 updated marauroa 2010-09-19 18:36:03 +00:00
Katie Russell
60d1ee90b0 always set release number 2010-09-15 19:52:11 +00:00
Katie Russell
a733c41ef7 Backported: Dont teleport back to town hall on minor releases - ID: 3066625 2010-09-15 19:48:12 +00:00
Hendrik Brummermann
96ee933ea5 increased version number 2010-09-14 19:27:01 +00:00
Kimmo Rundelin
f4f27c420d Revoved acquiring the drawing semaphore before the draw requests. They won't be processed in the main thread. Hopefully fixes [3065851] "0.87 Bad performance on windows" 2010-09-14 13:11:49 +00:00
Kimmo Rundelin
5dbc56da74 Reverted the previous fix attempt because it made problems with map changes 2010-09-14 13:07:58 +00:00
Kimmo Rundelin
cbda433bb2 Non-locking drawing. Hopefully fixes [3065851] "0.87 Bad performance on windows" 2010-09-14 10:49:10 +00:00
Hendrik Brummermann
8191b96967 deleted achievement code 2010-09-13 21:48:51 +00:00
Hendrik Brummermann
4426de1f3e deleted more broken code 2010-09-13 20:11:58 +00:00
Hendrik Brummermann
7742eaa5d9 deleted broken code 2010-09-13 20:10:06 +00:00
Hendrik Brummermann
612bff65b5 base version for update is 0.86.1 not 0.86 which was signed with the old key 2010-09-12 14:18:13 +00:00
Hendrik Brummermann
e7b6488411 added a warning on old clients so that we can get some estimate about the non automatic update this time 2010-09-12 14:10:09 +00:00
Hendrik Brummermann
6f3e2deb77 backported logging code by kymara 2010-09-08 19:15:53 +00:00
Hendrik Brummermann
80309069be use full form for internal names of achievements (fixes #3061510) 2010-09-07 21:36:06 +00:00
Hendrik Brummermann
77138c75ac disabled notification (madmetzger) 2010-09-07 21:05:50 +00:00
Hendrik Brummermann
5a62128a64 updated version 2010-09-07 20:40:36 +00:00
35 changed files with 158 additions and 962 deletions

View file

@ -72,8 +72,8 @@ updates_server = http://arianne.sourceforge.net/stendhal/updates
version_server = http://arianne.sourceforge.net/stendhal.version
# current version of stendhal
version.old = 0.86
version = 0.86.5
version.old = 0.87
version = 0.87.2
# javac options
javac.deprecation = true

Binary file not shown.

View file

@ -1,3 +1,3 @@
set STENDHAL_VERSION=0.86.5
set STENDHAL_VERSION=0.87.2
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,5 +1,5 @@
#!/bin/sh
STENDHAL_VERSION="0.86.5"
STENDHAL_VERSION="0.87.2"
LOCALCLASSPATH=.:data/script/:data/conf/:stendhal-server-$STENDHAL_VERSION.jar:marauroa.jar:mysql-connector.jar:log4j.jar:commons-lang.jar:h2.jar

View file

@ -555,17 +555,12 @@ public class j2DClient implements UserInterface {
lastuser = user;
}
}
if (client.tryAcquireDrawingSemaphore()) {
try {
if (mainFrame.getMainFrame().getState() != Frame.ICONIFIED) {
logger.debug("Draw screen");
screen.draw();
minimap.refresh();
containerPanel.repaintChildren();
}
} finally {
client.releaseDrawingSemaphore();
}
if (mainFrame.getMainFrame().getState() != Frame.ICONIFIED) {
logger.debug("Draw screen");
screen.draw();
minimap.refresh();
containerPanel.repaintChildren();
}
logger.debug("Query network");

View file

@ -10,7 +10,7 @@ package games.stendhal.client.update;
public class Version {
/** Version Number. */
public static final String VERSION = "0.86.5";
public static final String VERSION = "0.87.2";
/**
* 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.86.5";
String VERSION = "0.87.2";
/**

View file

@ -0,0 +1,120 @@
/* $Id$
*/
package games.stendhal.common;
/**
* Handles version numbers
*
* Actual number stored in games.stendhal.common.Debug.VERSION
* This file duplicates methods from games.stendhal.client.update.Version
* as the updater should not depend on anything outside games.stendhal.client.update
*
* @author hendrik
*/
public class Version {
/**
* Extract the specified number of parts from a version-string.
*
* @param version
* version-string
* @param parts
* number of parts to extract
* @return parts of the version-string
*/
public static String cut(final String version, final int parts) {
int pos = 0;
for (int i = 0; i < parts; i++) {
final int temp = version.indexOf(".", pos + 1);
if (temp < 0) {
pos = version.length();
break;
}
pos = temp;
}
return version.substring(0, pos);
}
/**
* Compares two versions.
*
* @param v1
* 1st version string
* @param v2
* 2nd version string
* @return see compare
*/
public static int compare(final String v1, final String v2) {
String version1 = v1;
String version2 = v2;
while (!version1.equals("") || !version2.equals("")) {
// split version string at the first dot into the current
// component and the rest of the version
String component1;
final int pos1 = version1.indexOf(".");
if (pos1 > -1) {
component1 = version1.substring(0, pos1);
version1 = version1.substring(pos1 + 1);
} else {
component1 = version1;
version1 = "";
}
if (component1.equals("")) {
component1 = "0";
}
String component2;
final int pos2 = version2.indexOf(".");
if (pos2 > -1) {
component2 = version2.substring(0, pos2);
version2 = version2.substring(pos2 + 1);
} else {
component2 = version2;
version2 = "";
}
if (component2.equals("")) {
component2 = "0";
}
// if the current component of both version is equal,
// we have to have a look at the next one. Otherwise
// we return the result of this comparison.
int res = 0;
try {
// try an integer comparison so that 2 < 13
final int componentInt1 = Integer.parseInt(component1.trim());
final int componentInt2 = Integer.parseInt(component2.trim());
res = componentInt1 - componentInt2;
} catch (final NumberFormatException e) {
// integer comparison failed because one component is not a
// number. Do a string comparison.
res = component1.compareTo(component2);
}
if (res != 0) {
return res;
}
}
return 0;
}
/**
* Checks whether these versions of stendhal are compatible.
*
* @param v1
* one version string
* @param v2
* another version string
* @return true, iff the first two components are equal
*/
public static boolean checkCompatibility(final String v1, final String v2) {
final String ev1 = cut(v1, 2);
final String ev2 = cut(v2, 2);
final boolean res = ev1.equals(ev2);
return res;
}
private Version() {
// hide constructor; this is a static class
}
}

View file

@ -234,7 +234,7 @@ public class GenerateINI {
out.println();
out.println("server_typeGame=" + gameName);
out.println("server_name=" + gameName + " Marauroa server");
out.println("server_version=0.86.5");
out.println("server_version=0.87.2");
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

@ -2,7 +2,6 @@ package games.stendhal.server.core.engine;
import games.stendhal.server.core.events.LoginNotifier;
import games.stendhal.server.core.events.TurnNotifier;
import games.stendhal.server.core.events.achievements.AchievementNotifier;
import games.stendhal.server.core.rp.StendhalQuestSystem;
import games.stendhal.server.core.rp.guilds.GuildList;
import games.stendhal.server.core.rp.guilds.GuildPermissionList;
@ -147,12 +146,6 @@ public class SingletonRepository {
return AthorFerry.get();
}
/**
* @return the AchievementNotifier instance
*/
public static AchievementNotifier getAchievementNotifier() {
return AchievementNotifier.get();
}
/**
* @return the actual EntityManager instance

View file

@ -1,6 +1,5 @@
package games.stendhal.server.core.engine;
import games.stendhal.server.core.engine.db.AchievementDAO;
import games.stendhal.server.core.engine.db.CidDAO;
import games.stendhal.server.core.engine.db.PostmanDAO;
import games.stendhal.server.core.engine.db.StendhalBuddyDAO;
@ -79,6 +78,5 @@ public class StendhalPlayerDatabase {
DAORegister.get().register(StendhalKillLogDAO.class, new StendhalKillLogDAO ());
DAORegister.get().register(StendhalNPCDAO.class, new StendhalNPCDAO());
DAORegister.get().register(StendhalWebsiteDAO.class, new StendhalWebsiteDAO());
DAORegister.get().register(AchievementDAO.class, new AchievementDAO());
}
}

View file

@ -169,14 +169,17 @@ public class StendhalRPRuleProcessor implements IRPRuleProcessor {
public boolean checkGameVersion(final String game, final String version) {
try {
if (game.equals(Configuration.getConfiguration().get("server_typeGame", "stendhal"))) {
return true;
if (!game.equals(Configuration.getConfiguration().get("server_typeGame", "stendhal"))) {
return false;
}
if (Debug.VERSION.compareTo(version) > 0) {
logger.warn("Client version: " + version);
}
return true;
} catch (IOException e) {
logger.error(e, e);
return false;
}
return false;
}
/**

View file

@ -151,7 +151,6 @@ public class StendhalRPWorld extends RPWorld {
loader.load();
validatePortals();
SingletonRepository.getAchievementNotifier().initialize();
SingletonRepository.getGagManager();
SingletonRepository.getJail();
} catch (final Exception e) {

View file

@ -1,195 +0,0 @@
package games.stendhal.server.core.engine.db;
import games.stendhal.server.core.events.achievements.Achievement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import marauroa.server.db.DBTransaction;
import marauroa.server.db.TransactionPool;
/**
* DAO to handle achievements for the stendhal website
* @author madmetzger
*
*/
public class AchievementDAO {
/**
* logs a reached achievement into the database
*
* @param achievementId
* @param playerName
* @throws SQLException
*/
public void saveReachedAchievement(Integer achievementId, String playerName) throws SQLException {
DBTransaction transaction = TransactionPool.get().beginWork();
saveReachedAchievement(achievementId, playerName, transaction);
TransactionPool.get().commit(transaction);
}
/**
* logs a reached achievement into the database
*
* @param achievementId
* @param playerName
* @param transaction
* @throws SQLException
*/
public void saveReachedAchievement(Integer achievementId, String playerName, DBTransaction transaction) throws SQLException {
String query = "INSERT INTO reached_achievement " +
"(charname, achievement_id) VALUES" +
"('[charname]','[achievement_id]');";
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("charname", playerName);
parameters.put("achievement_id", achievementId);
transaction.execute(query, parameters);
}
/**
* Saves the base data of an achievement
*
* @param achievement Achievement to save
* @return the id of the stored achievement
* @throws SQLException
*/
public int saveAchievement(Achievement achievement) throws SQLException {
DBTransaction transaction = TransactionPool.get().beginWork();
int achievementId = saveAchievement(achievement, transaction);
TransactionPool.get().commit(transaction);
return achievementId;
}
/**
* Saves the base data of an achievement
*
* @param achievement Achievement to save
* @param transaction a database transaction to execute the save operation in
* @return the id of the stored achievement
* @throws SQLException
*/
public int saveAchievement(Achievement achievement,
DBTransaction transaction) throws SQLException {
int achievementId = 0;
String query = "INSERT INTO achievement " +
"(identifier, title, category, description, base_score) VALUES " +
"('[identifier]','[title]','[category]', '[description]', [base_score])";
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("identifier", achievement.getIdentifier());
parameters.put("title", achievement.getTitle());
parameters.put("category", achievement.getCategory().toString());
parameters.put("description", achievement.getDescription());
parameters.put("base_score", achievement.getBaseScore());
transaction.execute(query, parameters);
achievementId = transaction.getLastInsertId("achievement", "id");
return achievementId;
}
/**
* Updates the achievement with the given id
*
* @param id
* @param achievement
* @throws SQLException
*/
public void updateAchievement(Integer id, Achievement achievement) throws SQLException {
DBTransaction transaction = TransactionPool.get().beginWork();
updateAchievement(id, achievement, transaction);
TransactionPool.get().commit(transaction);
}
/**
* Updates the achievement with the given id
*
* @param id
* @param achievement
* @param transaction
* @throws SQLException
*/
public void updateAchievement(Integer id, Achievement achievement,
DBTransaction transaction) throws SQLException {
String query = "UPDATE achievement SET " +
"identifier='[identifier]', " +
"title='[title]', " +
"category = '[category]', " +
"description = '[description]', " +
"base_score=[base_score] " +
"WHERE id = [id];";
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("identifier", achievement.getIdentifier());
parameters.put("title", achievement.getTitle());
parameters.put("category", achievement.getCategory().toString());
parameters.put("description", achievement.getDescription());
parameters.put("base_score", achievement.getBaseScore());
parameters.put("id", id);
transaction.execute(query, parameters);
}
/**
* Loads a map from achievement identifier to database serial
*
* @return map with key identifier string and value database id
* @throws SQLException
*/
public Map<String, Integer> loadIdentifierIdPairs() throws SQLException {
DBTransaction transaction = TransactionPool.get().beginWork();
Map<String, Integer> map = loadIdentifierIdPairs(transaction);;
TransactionPool.get().commit(transaction);
return map;
}
/**
* Loads a map from achievement identifier to database serial
*
* @param transaction
* @return map with key identifier string and value database id
* @throws SQLException
*/
public Map<String, Integer> loadIdentifierIdPairs(DBTransaction transaction) throws SQLException {
Map<String, Integer> map = new HashMap<String, Integer>();
String query = "SELECT identifier, id FROM achievement;";
ResultSet set = transaction.query(query, new HashMap<String, Object>());
while (set.next()) {
String identifier = set.getString("identifier");
Integer id = set.getInt("id");
map.put(identifier, id);
}
return map;
}
/**
* Loads all achievements a player has reached
* @param playerName
* @return set identifiers of achievements reached by playerName
* @throws SQLException
*/
public Set<String> loadAllReachedAchievementsOfPlayer(String playerName) throws SQLException {
DBTransaction transaction = TransactionPool.get().beginWork();
Set<String> set = loadAllReachedAchievementsOfPlayer(playerName, transaction);
TransactionPool.get().commit(transaction);
return set;
}
/**
* Loads all achievements a player has reached
* @param playerName
* @param transaction
* @return set identifiers of achievements reached by playerName
* @throws SQLException
*/
public Set<String> loadAllReachedAchievementsOfPlayer(String playerName, DBTransaction transaction) throws SQLException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("playername", playerName);
String query = "SELECT identifier FROM achievement a JOIN reached_achievement ra ON ra.achievement_id = a.id WHERE ra.charname = '[playername]';";
ResultSet resultSet = transaction.query(query, params);
Set<String> identifiers = new HashSet<String>();
while(resultSet.next()) {
identifiers.add(resultSet.getString(1));
}
return identifiers;
}
}

View file

@ -1,41 +0,0 @@
package games.stendhal.server.core.engine.dbcommand;
import games.stendhal.server.core.engine.db.AchievementDAO;
import games.stendhal.server.entity.player.Player;
import java.io.IOException;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
import marauroa.server.db.DBTransaction;
import marauroa.server.db.command.AbstractDBCommand;
import marauroa.server.game.db.DAORegister;
public class ReadAchievementsForPlayerCommand extends AbstractDBCommand {
private Set<String> identifiers = new HashSet<String>();
private final Player player;
/**
* @param player the player whose achievements should be read
*/
public ReadAchievementsForPlayerCommand(Player player) {
this.player = player;
}
@Override
public void execute(DBTransaction transaction) throws SQLException,
IOException {
identifiers = DAORegister.get().get(AchievementDAO.class).loadAllReachedAchievementsOfPlayer(getPlayer().getName(), transaction);
}
public Set<String> getIdentifiers() {
return identifiers;
}
public Player getPlayer() {
return player;
}
}

View file

@ -1,39 +0,0 @@
package games.stendhal.server.core.engine.dbcommand;
import games.stendhal.server.core.engine.db.AchievementDAO;
import games.stendhal.server.core.events.achievements.Category;
import java.io.IOException;
import java.sql.SQLException;
import marauroa.server.db.DBTransaction;
import marauroa.server.db.command.AbstractDBCommand;
import marauroa.server.game.db.DAORegister;
/**
* command to log a reached achievement to the database
*
* @author madmetzger
*/
public class WriteReachedAchievementCommand extends AbstractDBCommand {
private final Integer id;
private final String playerName;
/**
* create a new command
* @param id database id of the achievement
* @param playerName name of player who has reached it
*/
public WriteReachedAchievementCommand(Integer id, String title, Category category, String playerName) {
this.id = id;
this.playerName = playerName;
}
@Override
public void execute(DBTransaction transaction) throws SQLException,
IOException {
AchievementDAO dao = DAORegister.get().get(AchievementDAO.class);
dao.saveReachedAchievement(id, playerName, transaction);
}
}

View file

@ -4,6 +4,7 @@ import static games.stendhal.common.constants.Actions.AWAY;
import static games.stendhal.common.constants.Actions.GRUMPY;
import games.stendhal.common.Debug;
import games.stendhal.common.FeatureList;
import games.stendhal.common.Version;
import games.stendhal.server.core.engine.ItemLogger;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
@ -170,14 +171,14 @@ public class PlayerTransformer implements Transformer {
try {
if (object.has("zoneid") && object.has("x") && object.has("y")) {
if (object.get("release").equals(Debug.VERSION)) {
if (Version.checkCompatibility(object.get("release"),Debug.VERSION)) {
zone = SingletonRepository.getRPWorld().getZone(object.get("zoneid"));
} else {
player.put("release", Debug.VERSION);
if (player.getLevel() >= 2) {
TutorialNotifier.newrelease(player);
}
}
player.put("release", Debug.VERSION);
}
} catch (final RuntimeException e) {
// If placing the player at its last position

View file

@ -1,99 +0,0 @@
package games.stendhal.server.core.events.achievements;
import games.stendhal.server.entity.npc.ChatCondition;
import games.stendhal.server.entity.player.Player;
/**
* An Achievement a player can reach while playing the game.
* Achievements are given for example for doing a certain number of quests or killing a number of special creatures
*
* @author madmetzger
*/
public class Achievement {
public static final int EASY_BASE_SCORE = 10;
public static final int MEDIUM_BASE_SCORE = 50;
public static final int HARD_BASE_SCORE = 1000;
private final String identifier;
private final String title;
private final Category category;
private final String description;
private final int baseScore;
private final ChatCondition condition;
/**
* create a new achievement
*
* @param identifier
* @param title
* @param category
* @param condition
*/
public Achievement(String identifier, String title, Category category, String description, int baseScore, ChatCondition condition) {
this.identifier = identifier;
this.title = title;
this.category = category;
this.condition = condition;
this.description = description;
this.baseScore = baseScore;
}
/**
* @return the category of this achievement
*/
public Category getCategory() {
return category;
}
/**
* @return the identifying string
*/
public String getIdentifier() {
return identifier;
}
/**
* @return the title a player gets awarded for this achievement
*/
public String getTitle() {
return title;
}
/**
* @return the description of what to do to get this achievement
*/
public String getDescription() {
return description;
}
/**
* @return the base score for this achievement
*/
public int getBaseScore() {
return this.baseScore;
}
/**
* Check if a player has fullfilled this achievement
* @param p the player to check
* @return true iff this achievement's condtion evalutates to true
*/
public boolean isFulfilled(Player p) {
boolean fullfilled = condition.fire(p, null, null);
return fullfilled;
}
@Override
public String toString() {
return "Achievement<id: "+identifier+", title: "+title+">";
}
}

View file

@ -1,38 +0,0 @@
package games.stendhal.server.core.events.achievements;
import games.stendhal.server.entity.npc.ChatCondition;
import java.util.Collection;
/**
* Factory class for achievements creation with a fixed category
*
* @author madmetzger
*/
public abstract class AchievementFactory {
/**
* @return the category the factory should use
*/
protected abstract Category getCategory();
/**
* Creates a collection of achievements
*
* @return the achievments
*/
public abstract Collection<Achievement> createAchievements();
/**
* Creates a single achievement
* @param identifier
* @param title
* @param description
* @param score
* @param condition
* @return the new Achievement
*/
protected Achievement createAchievement(String identifier, String title, String description, int score, ChatCondition condition) {
return new Achievement(identifier, title, getCategory(), description, score, condition);
}
}

View file

@ -1,268 +0,0 @@
package games.stendhal.server.core.events.achievements;
import games.stendhal.common.Grammar;
import games.stendhal.server.core.engine.GameEvent;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.db.AchievementDAO;
import games.stendhal.server.core.engine.dbcommand.WriteReachedAchievementCommand;
import games.stendhal.server.entity.player.Player;
import games.stendhal.server.entity.player.ReadAchievementsOnLogin;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import marauroa.server.db.command.DBCommandQueue;
import marauroa.server.game.db.DAORegister;
import org.apache.log4j.Logger;
/**
* Checks for reached achievements and marks them as reached for a player if he has fullfilled them
*
* @author madmetzger
*/
public class AchievementNotifier {
private static final Logger logger = Logger.getLogger(AchievementNotifier.class);
private static AchievementNotifier instance;
private Map<Category, List<Achievement>> achievements;
private Map<String, Integer> identifiersToIds;
private AchievementNotifier() {
achievements = new HashMap<Category, List<Achievement>>();
identifiersToIds = new HashMap<String, Integer>();
}
/**
* singleton accessor method
*
* @return the AchievementNotifier
*/
public static AchievementNotifier get() {
if(instance == null) {
instance = new AchievementNotifier();
}
return instance;
}
/**
* initializes the achievements that are available and registers the login listener
* new added achievements are added to the achievements table
*/
public void initialize() {
//read all configured achievements and put them into the categorized map
Map<String, Achievement> allAchievements = createAchievements();
for(Achievement a : allAchievements.values()) {
if(!achievements.containsKey(a.getCategory())) {
achievements.put(a.getCategory(), new LinkedList<Achievement>());
}
achievements.get(a.getCategory()).add(a);
}
//collect all identifiers from database
Map<String, Integer> allIdentifiersInDatabase = collectAllIdentifiersFromDatabase();
//update stored data with configured achievements
identifiersToIds.putAll(allIdentifiersInDatabase);
for(String identifier : allIdentifiersInDatabase.keySet()) {
Achievement achievement = allAchievements.get(identifier);
try {
// this happens if an achievement is not configured anymore but already in the database
// in that case we should keep it as players could have reached it
// useful to stop checking for a certain achievement but keep results
if(achievement != null) {
DAORegister.get().get(AchievementDAO.class).updateAchievement(allIdentifiersInDatabase.get(identifier), achievement);
}
} catch (SQLException e) {
logger.error("Error while updating exisiting achievement "+achievement.getTitle(), e);
}
}
// remove already stored achievements before saving them
for(String identifier : allIdentifiersInDatabase.keySet()) {
allAchievements.remove(identifier);
}
//save new achievements and add their identifier and id to the identifierToId map
for (Achievement a : allAchievements.values()) {
Integer id;
try {
id = DAORegister.get().get(AchievementDAO.class).saveAchievement(a);
identifiersToIds.put(a.getIdentifier(), id);
} catch (SQLException e) {
logger.error("Error while saving new achievement "+a.getTitle(), e);
}
}
// register the login notifier that checks for each player the reached achievements on login
SingletonRepository.getLoginNotifier().addListener(new ReadAchievementsOnLogin());
}
/**
* collects all identifiers from the database
*
* @return a set of all identifier strings
*/
private Map<String, Integer> collectAllIdentifiersFromDatabase() {
Map<String, Integer> mapFromDB = new HashMap<String, Integer>();
try {
mapFromDB = DAORegister.get().get(AchievementDAO.class).loadIdentifierIdPairs();
} catch (SQLException e) {
logger.error("Error while loading Identifier to id map for achievements.", e);
}
return mapFromDB;
}
/**
* checks all for level change relevant achievements for a player
*
* @param player
*/
public void onLevelChange(Player player) {
getAndCheckAchievementsInCategory(player, Category.EXPERIENCE);
}
/**
* checks all achievements for a player that should be checked when a player kills sth
*
* @param player
*/
public void onKill(Player player) {
getAndCheckAchievementsInCategory(player, Category.FIGHTING);
}
/**
* check all achievements for a player that are relevant on finishing a quest
*
* @param player
*/
public void onFinishQuest(Player player) {
getAndCheckAchievementsInCategory(player, Category.QUEST);
}
/**
* Checks on login of a player which achievements the player has reached and gives a summarizing message
*
* @param player
*/
public void onLogin(Player player) {
List<Achievement> toCheck = new ArrayList<Achievement>();
Collection<List<Achievement>> values = achievements.values();
for (List<Achievement> list : values) {
toCheck.addAll(list);
}
List<Achievement> reached = checkAchievements(player, toCheck);
// only send notice if actually a new added achievement was reached by doing nothing
if(!reached.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("You have reached ");
sb.append(Integer.valueOf(reached.size()));
sb.append(" new "+Grammar.plnoun(reached.size(), "achievement")+". Please check #http://stendhalgame.org for details.");
player.sendPrivateText(sb.toString());
}
}
/**
* retrieve all achievements for a category and check if player has reached each of the found achievements
*
* @param player
* @param category
*/
private void getAndCheckAchievementsInCategory(Player player, Category category) {
if(achievements.containsKey(category)) {
List<Achievement> toCheck = achievements.get(category);
List<Achievement> reached = checkAchievements(player, toCheck);
notifyPlayerAboutReachedAchievements(player, reached);
}
}
/**
* checks for each achievement if the player has reached it. in case of reaching
* an achievement it starts logging and notifying about reaching
*
* @param player
* @param toCheck
*/
private List<Achievement> checkAchievements(Player player,
List<Achievement> toCheck) {
List<Achievement> reached = new ArrayList<Achievement>();
for (Achievement achievement : toCheck) {
if(achievement.isFulfilled(player) && !player.hasReachedAchievement(achievement.getIdentifier())) {
logReachingOfAnAchievement(player, achievement);
reached.add(achievement);
}
}
//check for meta achievements and add them to the reached list
if(!reached.isEmpty()) {
if(achievements.containsKey(Category.META)) {
reached.addAll(checkAchievements(player, achievements.get(Category.META)));
}
}
return reached;
}
/**
* Notifies a player about reached achievements via private message
*
* @param player
* @param achievements
*/
private void notifyPlayerAboutReachedAchievements(Player player, List<Achievement> achievements) {
for (Achievement achievement : achievements) {
notifyPlayerAboutReachedAchievement(player, achievement);
}
}
/**
* logs reached achievement to gameEvents table and reached_achievment table
*
* @param player
* @param achievement
*/
private void logReachingOfAnAchievement(Player player, Achievement achievement) {
String identifier = achievement.getIdentifier();
String title = achievement.getTitle();
Category category = achievement.getCategory();
String playerName = player.getName();
DBCommandQueue.get().enqueue(new WriteReachedAchievementCommand(identifiersToIds.get(identifier), title, category, playerName));
player.addReachedAchievement(achievement.getIdentifier());
new GameEvent(playerName, "reach-achievement", category.toString(), title, identifier).raise();
}
/**
* notifies the player about reaching an achievement
*
* @param player
* @param achievement
*/
private void notifyPlayerAboutReachedAchievement(Player player,
Achievement achievement) {
player.sendPrivateText("Congratulations! You have reached the "+achievement.getTitle()+" achievement!");
}
/**
* creates all available achievements
*
* @return map with key identifier and value the identified achievement
*/
private Map<String, Achievement> createAchievements() {
Map<String, Achievement> achievementMap = new HashMap<String, Achievement>();
for(Achievement a : new ExperienceAchievementFactory().createAchievements()) {
achievementMap.put(a.getIdentifier(), a);
}
for(Achievement a : new FightingAchievementFactory().createAchievements()) {
achievementMap.put(a.getIdentifier(), a);
}
for(Achievement a : new QuestAchievementFactory().createAchievements()) {
achievementMap.put(a.getIdentifier(), a);
}
for(Achievement a : new MetaAchievementFactory().createAchievements()) {
achievementMap.put(a.getIdentifier(), a);
}
return achievementMap;
}
}

View file

@ -1,12 +0,0 @@
/**
*
*/
package games.stendhal.server.core.events.achievements;
/**
* categories of achievements
*
* @author madmetzger
*/
public enum Category {
EXPERIENCE, FIGHTING, QUEST, META
}

View file

@ -1,42 +0,0 @@
package games.stendhal.server.core.events.achievements;
import games.stendhal.server.entity.npc.condition.LevelGreaterThanCondition;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Factory for experience achievements
*
* @author madmetzger
*/
public class ExperienceAchievementFactory extends AchievementFactory {
@Override
protected Category getCategory() {
return Category.EXPERIENCE;
}
@Override
public Collection<Achievement> createAchievements() {
List<Achievement> xpAchievements = new LinkedList<Achievement>();
xpAchievements.add(createAchievement("xp.lvl.10", "Greenhorn", "Reach level 10", Achievement.EASY_BASE_SCORE,
new LevelGreaterThanCondition(9)));
xpAchievements.add(createAchievement("xp.lvl.50", "Novice", "Reach level 50", Achievement.EASY_BASE_SCORE,
new LevelGreaterThanCondition(49)));
xpAchievements.add(createAchievement("xp.lvl.100", "Apprentice", "Reach level 100", Achievement.EASY_BASE_SCORE,
new LevelGreaterThanCondition(99)));
xpAchievements.add(createAchievement("xp.lvl.200", "Adventurer", "Reach level 200", Achievement.MEDIUM_BASE_SCORE,
new LevelGreaterThanCondition(199)));
xpAchievements.add(createAchievement("xp.lvl.300", "Experienced Adventurer", "Reach level 100", Achievement.MEDIUM_BASE_SCORE,
new LevelGreaterThanCondition(299)));
xpAchievements.add(createAchievement("xp.lvl.400", "Master Adventurer", "Reach level 400", Achievement.MEDIUM_BASE_SCORE,
new LevelGreaterThanCondition(399)));
xpAchievements.add(createAchievement("xp.lvl.500", "Stendhal Master", "Reach level 500", Achievement.HARD_BASE_SCORE,
new LevelGreaterThanCondition(499)));
xpAchievements.add(createAchievement("xp.lvl.597", "Stendhal High Master", "Reach level 597", Achievement.HARD_BASE_SCORE,
new LevelGreaterThanCondition(596)));
return xpAchievements;
}
}

View file

@ -1,30 +0,0 @@
package games.stendhal.server.core.events.achievements;
import games.stendhal.server.entity.npc.condition.PlayerHasKilledNumberOfCreaturesCondition;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Factory for fighting achievements
*
* @author madmetzger
*/
public class FightingAchievementFactory extends AchievementFactory {
@Override
public Collection<Achievement> createAchievements() {
List<Achievement> fightingAchievements = new LinkedList<Achievement>();
fightingAchievements.add(createAchievement("fight.general.rats", "Rat Hunter", "Kill 15 rats", Achievement.EASY_BASE_SCORE,
new PlayerHasKilledNumberOfCreaturesCondition("rat", 15)));
fightingAchievements.add(createAchievement("fight.general.exterminator", "Exterminator", "Kill 10 rats of each kind", Achievement.MEDIUM_BASE_SCORE,
new PlayerHasKilledNumberOfCreaturesCondition(10, "rat", "caverat", "venomrat", "zombie rat", "venom rat", "giantrat", "ratman", "ratwoman", "archrat")));
return fightingAchievements;
}
@Override
protected Category getCategory() {
return Category.FIGHTING;
}
}

View file

@ -1,29 +0,0 @@
package games.stendhal.server.core.events.achievements;
import games.stendhal.server.entity.npc.condition.PlayerHasCompletedAchievementsCondition;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Factory for meta achievements
*
* @author madmetzger
*/
public class MetaAchievementFactory extends AchievementFactory {
@Override
protected Category getCategory() {
return Category.META;
}
@Override
public Collection<Achievement> createAchievements() {
List<Achievement> achievements = new LinkedList<Achievement>();
achievements.add(createAchievement("meta.quest.daily-weekly", "Conscientuous Comrade",
"Complete all achievements for daily item quest, daily monster quest and weekly item quest",
Achievement.HARD_BASE_SCORE, new PlayerHasCompletedAchievementsCondition("quest.special.diq.500", "quest.special.wiq.5", "quest.special.dmq.500")));
return achievements;
}
}

View file

@ -1,58 +0,0 @@
package games.stendhal.server.core.events.achievements;
import games.stendhal.server.entity.npc.condition.QuestCompletedCondition;
import games.stendhal.server.entity.npc.condition.QuestStateGreaterThanCondition;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Factory for quest achievements
*
* @author madmetzger
*/
public class QuestAchievementFactory extends AchievementFactory {
@Override
public Collection<Achievement> createAchievements() {
List<Achievement> questAchievements = new LinkedList<Achievement>();
//daily monster quest achievements
questAchievements.add(createAchievement("quest.special.dmq.10", "Semos' Protector", "Finish daily monster quest 10 times",
Achievement.EASY_BASE_SCORE, new QuestStateGreaterThanCondition("daily", 2, 9)));
questAchievements.add(createAchievement("quest.special.dmq.50", "Semos' Guardian", "Finish daily monster quest 50 times",
Achievement.EASY_BASE_SCORE, new QuestStateGreaterThanCondition("daily", 2, 49)));
questAchievements.add(createAchievement("quest.special.dmq.100", "Semos' Hero", "Finish daily monster quest 100 times",
Achievement.MEDIUM_BASE_SCORE, new QuestStateGreaterThanCondition("daily", 2, 99)));
questAchievements.add(createAchievement("quest.special.dmq.250", "Semos' Champion", "Finish daily monster quest 250 times",
Achievement.MEDIUM_BASE_SCORE, new QuestStateGreaterThanCondition("daily", 2, 249)));
questAchievements.add(createAchievement("quest.special.dmq.500", "Semos' Vanquisher", "Finish daily monster quest 500 times",
Achievement.HARD_BASE_SCORE, new QuestStateGreaterThanCondition("daily", 2, 499)));
//daily item quest achievements
questAchievements.add(createAchievement("quest.special.diq.10", "Ados' Supporter", "Finish daily item quest 10 times",
Achievement.EASY_BASE_SCORE, new QuestStateGreaterThanCondition("daily_item", 2, 9)));
questAchievements.add(createAchievement("quest.special.diq.50", "Ados' Provider", "Finish daily item quest 50 times",
Achievement.EASY_BASE_SCORE, new QuestStateGreaterThanCondition("daily_item", 2, 49)));
questAchievements.add(createAchievement("quest.special.diq.100", "Ados' Supplier", "Finish daily item quest 100 times",
Achievement.MEDIUM_BASE_SCORE, new QuestStateGreaterThanCondition("daily_item", 2, 99)));
questAchievements.add(createAchievement("quest.special.diq.250", "Ados' Stockpiler", "Finish daily item quest 250 times",
Achievement.MEDIUM_BASE_SCORE, new QuestStateGreaterThanCondition("daily_item", 2, 249)));
questAchievements.add(createAchievement("quest.special.diq.500", "Ados' Hoarder", "Finish daily item quest 500 times",
Achievement.HARD_BASE_SCORE, new QuestStateGreaterThanCondition("daily_item", 2, 499)));
//weekly item quest achievement
questAchievements.add(createAchievement("quest.special.wiq.5", "Archaeologist", "Finish weekly item quest 5 times",
Achievement.HARD_BASE_SCORE, new QuestStateGreaterThanCondition("weekly_item", 2, 4)));
//elf princess quest achievement
questAchievements.add(createAchievement("quest.special.rhosyd.25", "Faiumoni's Casanova", "Finish elf princess quest 25 times",
Achievement.MEDIUM_BASE_SCORE, new QuestStateGreaterThanCondition("elf_princess", 2, 24)));
//ultimate collector quest achievement
questAchievements.add(createAchievement("quest.special.collector", "Ultimate Collector", "Finish ultimate collector quest",
Achievement.HARD_BASE_SCORE, new QuestCompletedCondition("ultimate_collector")));
return questAchievements;
}
@Override
protected Category getCategory() {
return Category.QUEST;
}
}

View file

@ -1135,7 +1135,6 @@ public abstract class RPEntity extends GuidedEntity {
}
killer.addXP(reward);
SingletonRepository.getAchievementNotifier().onKill(killer);
// For some quests etc., it is required that the player kills a
// certain creature without the help of others.
// Find out if the player killed this RPEntity on his own, but

View file

@ -1,15 +0,0 @@
package games.stendhal.server.entity.npc.action;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.entity.npc.ChatAction;
import games.stendhal.server.entity.npc.EventRaiser;
import games.stendhal.server.entity.npc.parser.Sentence;
import games.stendhal.server.entity.player.Player;
public class CheckForReachedQuestAchievementsAction implements ChatAction {
public void fire(Player player, Sentence sentence, EventRaiser npc) {
SingletonRepository.getAchievementNotifier().onFinishQuest(player);
}
}

View file

@ -29,7 +29,6 @@ import games.stendhal.common.constants.Nature;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
import games.stendhal.server.core.events.TutorialNotifier;
import games.stendhal.server.core.events.achievements.AchievementNotifier;
import games.stendhal.server.core.rp.StendhalRPAction;
import games.stendhal.server.entity.Entity;
import games.stendhal.server.entity.Outfit;
@ -1999,12 +1998,6 @@ public class Player extends RPEntity {
remove("buddies", name);
}
@Override
public void setLevel(int level) {
super.setLevel(level);
AchievementNotifier.get().onLevelChange(this);
}
/**
* Adds the identifier of an achievement to the reached achievements
*

View file

@ -1,40 +0,0 @@
package games.stendhal.server.entity.player;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.dbcommand.ReadAchievementsForPlayerCommand;
import games.stendhal.server.core.events.LoginListener;
import games.stendhal.server.core.events.TurnListener;
import games.stendhal.server.core.events.TurnListenerDecorator;
import games.stendhal.server.core.events.TurnNotifier;
import java.util.Set;
import marauroa.server.db.command.DBCommand;
import marauroa.server.db.command.DBCommandQueue;
import marauroa.server.db.command.ResultHandle;
public class ReadAchievementsOnLogin implements LoginListener, TurnListener {
private ResultHandle handle = new ResultHandle();
public void onLoggedIn(Player player) {
DBCommand command = new ReadAchievementsForPlayerCommand(player);
DBCommandQueue.get().enqueueAndAwaitResult(command, handle);
TurnNotifier.get().notifyInTurns(1, new TurnListenerDecorator(this));
}
public void onTurnReached(int currentTurn) {
ReadAchievementsForPlayerCommand command = DBCommandQueue.get().getOneResult(ReadAchievementsForPlayerCommand.class, handle);
if (command == null) {
TurnNotifier.get().notifyInTurns(0, new TurnListenerDecorator(this));
return;
}
Player p = command.getPlayer();
Set<String> identifiers = command.getIdentifiers();
for (String identifier : identifiers) {
p.addReachedAchievement(identifier);
}
SingletonRepository.getAchievementNotifier().onLogin(command.getPlayer());
}
}

View file

@ -5,17 +5,16 @@ import games.stendhal.server.entity.npc.ChatAction;
import games.stendhal.server.entity.npc.ConversationPhrases;
import games.stendhal.server.entity.npc.ConversationStates;
import games.stendhal.server.entity.npc.SpeakerNPC;
import games.stendhal.server.entity.npc.action.CheckForReachedQuestAchievementsAction;
import games.stendhal.server.entity.npc.action.DropRecordedItemAction;
import games.stendhal.server.entity.npc.action.IncreaseKarmaAction;
import games.stendhal.server.entity.npc.action.IncreaseXPDependentOnLevelAction;
import games.stendhal.server.entity.npc.action.IncrementQuestAction;
import games.stendhal.server.entity.npc.action.MultipleActions;
import games.stendhal.server.entity.npc.action.SayRequiredItemAction;
import games.stendhal.server.entity.npc.action.SayTimeRemainingAction;
import games.stendhal.server.entity.npc.action.SetQuestAction;
import games.stendhal.server.entity.npc.action.SetQuestToTimeStampAction;
import games.stendhal.server.entity.npc.action.StartRecordingRandomItemCollectionAction;
import games.stendhal.server.entity.npc.action.SayRequiredItemAction;
import games.stendhal.server.entity.npc.action.SayTimeRemainingAction;
import games.stendhal.server.entity.npc.condition.AndCondition;
import games.stendhal.server.entity.npc.condition.NotCondition;
import games.stendhal.server.entity.npc.condition.OrCondition;
@ -248,7 +247,6 @@ public class DailyItemQuest extends AbstractQuest {
actions.add(new SetQuestAction(QUEST_SLOT, 0, "done"));
actions.add(new IncreaseXPDependentOnLevelAction(8, 90.0));
actions.add(new IncreaseKarmaAction(10.0));
actions.add(new CheckForReachedQuestAchievementsAction());
npc.add(ConversationStates.ATTENDING,
ConversationPhrases.FINISH_MESSAGES,

View file

@ -10,7 +10,6 @@ import games.stendhal.server.entity.npc.ConversationPhrases;
import games.stendhal.server.entity.npc.ConversationStates;
import games.stendhal.server.entity.npc.EventRaiser;
import games.stendhal.server.entity.npc.SpeakerNPC;
import games.stendhal.server.entity.npc.action.CheckForReachedQuestAchievementsAction;
import games.stendhal.server.entity.npc.action.IncreaseXPDependentOnLevelAction;
import games.stendhal.server.entity.npc.action.MultipleActions;
import games.stendhal.server.entity.npc.action.SayTimeRemainingAction;
@ -441,7 +440,7 @@ public class DailyMonsterQuest extends AbstractQuest {
new KilledForQuestCondition(QUEST_SLOT, 0)),
ConversationStates.ATTENDING,
null,
new MultipleActions(new DailyQuestCompleteAction(), new CheckForReachedQuestAchievementsAction()));
new MultipleActions(new DailyQuestCompleteAction()));
}
/**

View file

@ -3,13 +3,12 @@ package games.stendhal.server.maps.quests;
import games.stendhal.server.entity.npc.ConversationPhrases;
import games.stendhal.server.entity.npc.ConversationStates;
import games.stendhal.server.entity.npc.SpeakerNPC;
import games.stendhal.server.entity.npc.action.CheckForReachedQuestAchievementsAction;
import games.stendhal.server.entity.npc.action.DropRecordedItemAction;
import games.stendhal.server.entity.npc.action.IncreaseXPAction;
import games.stendhal.server.entity.npc.action.MultipleActions;
import games.stendhal.server.entity.npc.action.SayRequiredItemAction;
import games.stendhal.server.entity.npc.action.SetQuestAction;
import games.stendhal.server.entity.npc.action.StartRecordingRandomItemCollectionAction;
import games.stendhal.server.entity.npc.action.SayRequiredItemAction;
import games.stendhal.server.entity.npc.condition.AndCondition;
import games.stendhal.server.entity.npc.condition.NotCondition;
import games.stendhal.server.entity.npc.condition.OrCondition;
@ -21,8 +20,8 @@ import games.stendhal.server.entity.npc.condition.QuestNotStartedCondition;
import games.stendhal.server.entity.player.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -245,7 +244,6 @@ public class UltimateCollector extends AbstractQuest {
"Wow, it's incredible to see this close up! Many thanks. Now, perhaps we can #deal together.",
new MultipleActions(new DropRecordedItemAction(QUEST_SLOT),
new SetQuestAction(QUEST_SLOT, "done"),
new CheckForReachedQuestAchievementsAction(),
new IncreaseXPAction(100000)));
npc.add(ConversationStates.QUEST_ITEM_QUESTION,

View file

@ -9,17 +9,16 @@ import games.stendhal.server.entity.npc.ConversationPhrases;
import games.stendhal.server.entity.npc.ConversationStates;
import games.stendhal.server.entity.npc.EventRaiser;
import games.stendhal.server.entity.npc.SpeakerNPC;
import games.stendhal.server.entity.npc.action.CheckForReachedQuestAchievementsAction;
import games.stendhal.server.entity.npc.action.DropRecordedItemAction;
import games.stendhal.server.entity.npc.action.IncreaseKarmaAction;
import games.stendhal.server.entity.npc.action.IncreaseXPDependentOnLevelAction;
import games.stendhal.server.entity.npc.action.IncrementQuestAction;
import games.stendhal.server.entity.npc.action.MultipleActions;
import games.stendhal.server.entity.npc.action.SayRequiredItemAction;
import games.stendhal.server.entity.npc.action.SayTimeRemainingAction;
import games.stendhal.server.entity.npc.action.SetQuestAction;
import games.stendhal.server.entity.npc.action.SetQuestToTimeStampAction;
import games.stendhal.server.entity.npc.action.StartRecordingRandomItemCollectionAction;
import games.stendhal.server.entity.npc.action.SayRequiredItemAction;
import games.stendhal.server.entity.npc.action.SayTimeRemainingAction;
import games.stendhal.server.entity.npc.condition.AndCondition;
import games.stendhal.server.entity.npc.condition.NotCondition;
import games.stendhal.server.entity.npc.condition.OrCondition;
@ -202,7 +201,6 @@ public class WeeklyItemQuest extends AbstractQuest {
actions.add(new SetQuestAction(QUEST_SLOT, 0, "done"));
actions.add(new IncreaseXPDependentOnLevelAction(5.0/3.0, 290.0));
actions.add(new IncreaseKarmaAction(10.0));
actions.add(new CheckForReachedQuestAchievementsAction());
actions.add(new ChatAction() {
public void fire(final Player player, final Sentence sentence, final EventRaiser raiser) {
int goldamount;

View file

@ -1,6 +1,7 @@
package games.stendhal.server.script;
import games.stendhal.common.Direction;
import games.stendhal.server.core.engine.GameEvent;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPWorld;
import games.stendhal.server.core.engine.StendhalRPZone;
@ -12,6 +13,8 @@ import games.stendhal.server.util.Area;
import java.util.List;
import org.apache.log4j.Logger;
/**
* Moves players away that spend to much time in an restricted area
*
@ -21,6 +24,8 @@ public class UnblockTradeTable extends ScriptImpl implements TurnListener {
private static final int CHECK_INTERVAL = 10;
private static final int GRACE_PERIOD_IN_TURNS = 200;
private static Logger logger = Logger.getLogger(UnblockTradeTable.class);
private StendhalRPZone zone;
private Area pathArea;
private Area tablePathArea;
@ -103,7 +108,10 @@ public class UnblockTradeTable extends ScriptImpl implements TurnListener {
// at the top left corner of the table, one tile to the right
// So that the player cannot just run down, but close to the left
// because player tend to put items on the ground.
logger.info("Teleported " + player.getName()
+ " away from trading table coordinates " + player.getX() + "," + player.getY());
player.teleport(zone, 36, 2, Direction.DOWN, player);
new GameEvent("trade table", "teleport", player.getName(), zone.getName(), "36", "2").raise();
}
}

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.86.5.jar" download="eager" main="true" />
<jar href="http://arianne.sourceforge.net/jws/stendhal-starter-0.87.2.jar" download="eager" main="true" />
</resources>
<application-desc/>
</jnlp>