From c74e30d5ba06bb68839475db61004a0f1def194c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20BENO=C3=8ET?= <tseeker@nocternity.net>
Date: Sat, 4 Jan 2025 15:28:26 +0100
Subject: [PATCH 01/94] Manually import single change from old staging repo

---
 .../db-structure/parts/functions/190-admin-functions.sql      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
index fdf8c79..b5bbf6a 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
@@ -874,7 +874,7 @@ BEGIN
 					|| 's' ) :: INTERVAL;
 	g_time := ( floor(sys.get_constant('accounts.warnings.grace')) || 's' )::INTERVAL;
 	UPDATE admin.warnings SET last_received = now( ) - g_time , warnings = warnings - 1
-		WHERE now() - last_received >= e_time AND warnings > 0;
+		WHERE now() - last_received >= g_time AND warnings > 0;
 END;
 $$ LANGUAGE plpgsql;
 
@@ -934,4 +934,4 @@ CREATE VIEW admin.users_list
 				LEFT OUTER JOIN admin.warnings w ON w.credentials_id = av.id
 			ORDER BY av.address;
 
-GRANT SELECT ON admin.users_list TO :dbuser;
\ No newline at end of file
+GRANT SELECT ON admin.users_list TO :dbuser;

From 0665a760de80594d40648f63a82881d5876d5f8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Fri, 9 Dec 2011 08:07:33 +0100
Subject: [PATCH 02/94] Project: * Clean-up (Eclipse cruft, unused files,
 etc...) * Git-specific changes * Maven POMs clean-up and changes for the
 build system * Version set to 1.0.0-0 in the development branches * Maven
 plug-ins updated to latest versions * Very partial dev. documentation added

---
 .gitignore                                    |   4 +
 .project                                      |  17 -
 build-tools/BUILD.sh                          |  51 ---
 build-tools/deploy.sh                         |  34 --
 legacyworlds-server-DIST/pom.xml              |  53 +++
 legacyworlds-server-DIST/src/server.xml       |  91 +++++
 .../pom.xml                                   |  10 +-
 .../lw/beans/acm/AccountCleanupBean.java      |   0
 .../lw/beans/acm/AccountCleanupTask.java      |   0
 .../lw/beans/acm/AccountManagementBean.java   |   0
 .../lw/beans/acm/QuitProcessorBean.java       |   0
 .../lw/beans/acm/QuitProcessorTask.java       |   0
 .../lw/beans/acm/RequestsExpirationBean.java  |   0
 .../lw/beans/acm/RequestsExpirationTask.java  |   0
 .../lw/beans/acm/UserSessionDAOBean.java      |   0
 .../deepclone/lw/beans/acm/UsersDAOBean.java  |   0
 .../lw/beans/acm/VacationProcessorBean.java   |   0
 .../lw/beans/acm/VacationProcessorTask.java   |   0
 .../lw/beans/admin/AdminDAOBean.java          |   0
 .../lw/beans/admin/AdminRecapBean.java        |   0
 .../lw/beans/admin/AdminRecapTask.java        |   0
 .../lw/beans/admin/AdministrationBean.java    |   0
 .../deepclone/lw/beans/admin/BansDAOBean.java |   0
 .../lw/beans/admin/BansProcessorBean.java     |   0
 .../lw/beans/admin/BansProcessorTask.java     |   0
 .../deepclone/lw/beans/admin/IpBanBean.java   |   0
 .../lw/beans/prefs/BoolPreferenceType.java    |   0
 .../beans/prefs/DefaultPreferencesBean.java   |   0
 .../lw/beans/prefs/EnumPreferenceType.java    |   0
 .../lw/beans/prefs/IntPreferenceType.java     |   0
 .../prefs/PreferenceDefinitionsBean.java      |   0
 .../lw/beans/prefs/PreferenceTypeImpl.java    |   0
 .../prefs/PreferenceTypesRegistryBean.java    |   0
 .../lw/beans/prefs/PreferencesDAOBean.java    |   0
 .../lw/beans/prefs/StringPreferenceType.java  |   0
 .../configuration/accounts-beans.xml          |   0
 .../accounts/account-cleanup-bean.xml         |   0
 .../accounts/account-management-bean.xml      |   0
 .../configuration/accounts/admin-dao-bean.xml |   0
 .../accounts/admin-recap-bean.xml             |   0
 .../accounts/administration-bean.xml          |   0
 .../configuration/accounts/bans-dao-bean.xml  |   0
 .../accounts/bans-processor-bean.xml          |   0
 .../accounts/default-preferences-bean.xml     |   0
 .../configuration/accounts/ip-ban-bean.xml    |   0
 .../accounts/preference-definitions-bean.xml  |   0
 .../accounts/preferences-dao-bean.xml         |   0
 .../accounts/quit-processor-bean.xml          |   0
 .../accounts/requests-expiration-bean.xml     |   0
 .../accounts/user-session-dao-bean.xml        |   0
 .../configuration/accounts/users-dao-bean.xml |   0
 .../accounts/vacation-processor-bean.xml      |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  12 +-
 .../deepclone/lw/beans/bt/AdminBugsBean.java  |   0
 .../deepclone/lw/beans/bt/BugsDAOBean.java    |   0
 .../lw/beans/bt/EmpireSummaryBean.java        |   0
 .../deepclone/lw/beans/bt/PlayerBugsBean.java |   0
 .../beans/bt/esdata/AccountInformation.java   |   0
 .../beans/bt/esdata/AllianceInformation.java  |   0
 .../beans/bt/esdata/BuildingsInformation.java |   0
 .../lw/beans/bt/esdata/DebugInformation.java  |   0
 .../lw/beans/bt/esdata/EmpireInformation.java |   0
 .../lw/beans/bt/esdata/FleetInformation.java  |   0
 .../beans/bt/esdata/MovementInformation.java  |   0
 .../lw/beans/bt/esdata/PlanetInformation.java |   0
 .../lw/beans/bt/esdata/QueueInformation.java  |   0
 .../beans/bt/esdata/QueueItemInformation.java |   0
 .../beans/bt/esdata/ResearchInformation.java  |   0
 .../lw/beans/bt/esdata/ShipsInformation.java  |   0
 .../lw/beans/bt/esdata/SystemInformation.java |   0
 .../main/resources/configuration/bt-beans.xml |   0
 .../configuration/bt/admin-bugs-bean.xml      |   0
 .../configuration/bt/bugs-dao-bean.xml        |   0
 .../configuration/bt/empire-summary-bean.xml  |   0
 .../configuration/bt/player-bugs-bean.xml     |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  10 +-
 .../lw/beans/eventlog/AdminErrorMailBean.java |   0
 .../lw/beans/eventlog/AdminErrorMailTask.java |   0
 .../lw/beans/eventlog/EntryQueueItem.java     |   0
 .../lw/beans/eventlog/LogCleanerBean.java     |   0
 .../lw/beans/eventlog/LogCleanerTask.java     |   0
 .../lw/beans/eventlog/LogReaderBean.java      |   0
 .../lw/beans/eventlog/LogWriterBean.java      |   0
 .../lw/beans/eventlog/LogWriterTask.java      |   0
 .../lw/beans/eventlog/LoggerBean.java         |   0
 .../lw/beans/eventlog/SystemLoggerImpl.java   |   0
 .../configuration/eventlog-beans.xml          |   0
 .../eventlog/admin-error-mail-bean.xml        |   0
 .../eventlog/log-cleaner-bean.xml             |   0
 .../eventlog/log-reader-bean.xml              |   0
 .../eventlog/log-writer-bean.xml              |   0
 .../configuration/eventlog/logger-bean.xml    |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 legacyworlds-server-beans-i18n/pom.xml        |  16 +
 .../lw/beans/i18n/I18NAdministrationImpl.java |   0
 .../com/deepclone/lw/beans/i18n/I18NData.java |   0
 .../lw/beans/i18n/I18NManagerBean.java        |   0
 .../lw/beans/i18n/LanguageStore.java          |   0
 .../lw/beans/i18n/LoaderTransaction.java      |   0
 .../lw/beans/i18n/TranslatorBean.java         |   0
 .../resources/configuration/i18n-beans.xml    |   0
 .../configuration/i18n/i18n-manager-bean.xml  |   0
 .../i18n/i18n-translator-bean.xml             |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  17 +-
 .../lw/beans/mailer/MailDataImpl.java         |   0
 .../lw/beans/mailer/MailQueueHandler.java     |   0
 .../lw/beans/mailer/MailQueueItem.java        |   0
 .../lw/beans/mailer/MailQueueTask.java        |   0
 .../lw/beans/mailer/MailQueueTerminator.java  |   0
 .../deepclone/lw/beans/mailer/MailerBean.java |   0
 .../deepclone/lw/beans/mailer/QueuedMail.java |   0
 .../resources/configuration/mailer-beans.xml  |   0
 .../configuration/mailer/mailer-bean.xml      |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  10 +-
 .../lw/beans/naming/NamesManagerBean.java     |   0
 .../lw/beans/naming/NamingDAOBean.java        |   0
 .../resources/configuration/naming-beans.xml  |   0
 .../naming/names-manager-bean.xml             |   0
 .../configuration/naming/naming-dao-bean.xml  |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 legacyworlds-server-beans-simple/pom.xml      |  14 +
 .../lw/beans/empire/AllianceDAOBean.java      |   0
 .../beans/empire/AllianceManagementBean.java  |   0
 .../lw/beans/empire/EmpireDAOBean.java        |   0
 .../lw/beans/empire/EmpireManagementBean.java |   0
 .../lw/beans/empire/PlanetListMapper.java     |   0
 .../lw/beans/fleets/BattleViewerBean.java     |   0
 .../lw/beans/fleets/BattlesCacheBean.java     |   0
 .../lw/beans/fleets/BattlesDAOBean.java       |   0
 .../lw/beans/fleets/FleetManagementBean.java  |   0
 .../lw/beans/fleets/FleetsDAOBean.java        |   0
 .../deepclone/lw/beans/map/MapViewerBean.java |   0
 .../deepclone/lw/beans/map/PlanetDAOBean.java |   0
 .../lw/beans/map/PlanetsManagementBean.java   |   0
 .../lw/beans/map/UniverseDAOBean.java         |   0
 .../lw/beans/map/UniverseGeneratorBean.java   |   0
 .../lw/beans/map/UniverseGeneratorTask.java   |   0
 .../lw/beans/msgs/AdminMessagesBean.java      |   0
 .../lw/beans/msgs/EmpireMessagesBean.java     |   0
 .../deepclone/lw/beans/msgs/MailTaskBase.java |   0
 .../lw/beans/msgs/MessageBoxDAOBean.java      |   0
 .../lw/beans/msgs/MessageCleanerBean.java     |   0
 .../beans/msgs/MessageContentCacheBean.java   |   0
 .../beans/msgs/MessageFormatRegistryBean.java |   0
 .../beans/msgs/MessageFormatWiringBean.java   |   0
 .../lw/beans/msgs/MessageRecordsDAOBean.java  |   0
 .../lw/beans/msgs/MessageTasksBean.java       |   0
 .../lw/beans/msgs/NotificationsDAOBean.java   |   0
 .../lw/beans/msgs/NotificationsTask.java      |   0
 .../lw/beans/msgs/RecapitulationTask.java     |   0
 .../msgs/fmt/AbandonMessageFormatterBean.java |   0
 .../beans/msgs/fmt/AdminMessageExtractor.java |   0
 .../msgs/fmt/AdminMessageFormatterBean.java   |   0
 ...AllianceDisbandedMessageFormatterBean.java |   0
 .../msgs/fmt/AllianceMessageExtractor.java    |   0
 .../msgs/fmt/BattleMessageFormatterBean.java  |   0
 .../msgs/fmt/BugMessageFormatterBean.java     |   0
 .../msgs/fmt/DebtMessageFormatterBean.java    |   0
 .../msgs/fmt/ExternalMessageFormatBean.java   |   0
 .../beans/msgs/fmt/FleetMessageExtractor.java |   0
 .../msgs/fmt/FleetMessageFormatterBean.java   |   0
 .../msgs/fmt/KickedMessageFormatterBean.java  |   0
 .../fmt/LeadershipMessageFormatterBean.java   |   0
 .../fmt/LeftAllianceMessageFormatterBean.java |   0
 .../fmt/LostPlanetMessageFormatterBean.java   |   0
 .../PendingRequestMessageFormatterBean.java   |   0
 .../msgs/fmt/PlanetMessageExtractor.java      |   0
 .../msgs/fmt/QueueMessageFormatterBean.java   |   0
 .../RequestResultMessageFormatterBean.java    |   0
 .../msgs/fmt/StrikeMessageFormatterBean.java  |   0
 .../fmt/TakenPlanetMessageFormatterBean.java  |   0
 .../msgs/fmt/TechMessageFormatterBean.java    |   0
 .../lw/beans/updates/GameUpdateBean.java      |   0
 .../lw/beans/updates/UpdatesDAOBean.java      |   0
 .../resources/configuration/simple-beans.xml  |   0
 .../simple/alliance-dao-bean.xml              |   0
 .../simple/alliance-management-bean.xml       |   0
 .../simple/battle-data-beans.xml              |   0
 .../configuration/simple/empire-dao-bean.xml  |   0
 .../simple/empire-management-bean.xml         |   0
 .../simple/fleet-management-bean.xml          |   0
 .../configuration/simple/fleets-dao-bean.xml  |   0
 .../configuration/simple/game-update-bean.xml |   0
 .../configuration/simple/map-viewer-bean.xml  |   0
 .../configuration/simple/message-beans.xml    |   0
 .../configuration/simple/planet-dao-bean.xml  |   0
 .../simple/planets-management-bean.xml        |   0
 .../simple/universe-dao-bean.xml              |   0
 .../simple/universe-generator-bean.xml        |   0
 .../configuration/simple/updates-dao-bean.xml |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  12 +-
 .../sys/ConstantsAdministrationImpl.java      |   0
 .../deepclone/lw/beans/sys/ConstantsData.java |   0
 .../lw/beans/sys/ConstantsManagerBean.java    |   0
 .../lw/beans/sys/ConstantsRegistrarBean.java  |   0
 .../lw/beans/sys/ServerSessionData.java       |   0
 .../lw/beans/sys/SessionManagerBean.java      |   0
 .../lw/beans/sys/SystemStatusBean.java        |   0
 .../deepclone/lw/beans/sys/TickerBean.java    |   0
 .../lw/beans/sys/TickerManagerBean.java       |   0
 .../deepclone/lw/beans/sys/TickerTask.java    |   0
 .../lw/beans/sys/TickerTaskStatusHandler.java |   0
 .../deepclone/lw/beans/sys/TickerThread.java  |   0
 .../resources/configuration/system-beans.xml  |   0
 .../system/constants-manager-bean.xml         |   0
 .../system/constants-registrar-bean.xml       |   0
 .../system/session-manager-bean.xml           |   0
 .../system/system-status-bean.xml             |   0
 .../configuration/system/ticker-bean.xml      |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  10 +-
 .../beans/user/ObjectNameValidatorBean.java   |   0
 .../user/abst/AutowiredCommandDelegate.java   |   0
 .../user/abst/AutowiredSubTypeDelegate.java   |   0
 .../user/abst/SessionCommandDelegate.java     |   0
 .../user/abst/SessionCommandHandler.java      |   0
 .../user/abst/SessionCommandWiringBean.java   |   0
 .../user/abst/SessionSubTypeDelegate.java     |   0
 .../user/abst/SessionSubTypeWiringBean.java   |   0
 .../user/abst/StatefulSessionTypeDefiner.java |   0
 .../user/admin/AdminSessionDefinerBean.java   |   0
 .../beans/user/admin/AdminSessionSubType.java |   0
 .../user/admin/common/AdminOperation.java     |   0
 .../user/admin/common/CommonCommandsBean.java |   0
 ...reateAuthChallengeCommandDelegateBean.java |   0
 .../NoOperationCommandDelegateBean.java       |   0
 .../SetPasswordCommandDelegateBean.java       |   0
 .../user/admin/main/AdminCommandsBean.java    |   0
 .../AdminOverviewCommandDelegateBean.java     |   0
 .../bans/BansSummaryCommandDelegateBean.java  |   0
 .../bans/ConfirmBanCommandDelegateBean.java   |   0
 .../main/bans/LiftBanCommandDelegateBean.java |   0
 .../bans/ListBansCommandDelegateBean.java     |   0
 .../bans/RejectBanCommandDelegateBean.java    |   0
 .../bans/RequestBanCommandDelegateBean.java   |   0
 .../bt/BugsSummaryCommandDelegateBean.java    |   0
 .../bt/GetSnapshotCommandDelegateBean.java    |   0
 .../main/bt/ListBugsCommandDelegateBean.java  |   0
 .../bt/MergeReportsCommandDelegateBean.java   |   0
 .../ModerateCommentCommandDelegateBean.java   |   0
 .../bt/PostCommentCommandDelegateBean.java    |   0
 .../main/bt/ReportBugCommandDelegateBean.java |   0
 .../bt/ReportStatusCommandDelegateBean.java   |   0
 .../ReportVisibilityCommandDelegateBean.java  |   0
 .../bt/ValidateReportCommandDelegateBean.java |   0
 .../main/bt/ViewBugCommandDelegateBean.java   |   0
 .../cnst/GetConstantsCommandDelegateBean.java |   0
 .../cnst/SetConstantCommandDelegateBean.java  |   0
 .../ChangeLanguageCommandDelegateBean.java    |   0
 .../i18n/GetLanguageCommandDelegateBean.java  |   0
 .../i18n/SetStringCommandDelegateBean.java    |   0
 .../ViewLanguagesCommandDelegateBean.java     |   0
 .../logs/GetEntryCommandDelegateBean.java     |   0
 .../logs/ViewLogsCommandDelegateBean.java     |   0
 .../EnableMaintenanceCommandDelegateBean.java |   0
 .../EndMaintenanceCommandDelegateBean.java    |   0
 .../ExtendMaintenanceCommandDelegateBean.java |   0
 .../MaintenanceStatusCommandDelegateBean.java |   0
 .../ComposeMessageCommandDelegateBean.java    |   0
 .../msgs/GetMessagesCommandDelegateBean.java  |   0
 .../msgs/MessageBoxCommandDelegateBean.java   |   0
 .../PrepareMessageCommandDelegateBean.java    |   0
 .../msgs/ReadMessageCommandDelegateBean.java  |   0
 .../msgs/SendSpamCommandDelegateBean.java     |   0
 .../names/GetNamesCommandDelegateBean.java    |   0
 .../names/NamesActionCommandDelegateBean.java |   0
 .../NamesSummaryCommandDelegateBean.java      |   0
 .../GetPrefDefaultsCommandDelegateBean.java   |   0
 .../SetPrefDefaultCommandDelegateBean.java    |   0
 .../AddAdministratorCommandDelegateBean.java  |   0
 ...ListAdministratorsCommandDelegateBean.java |   0
 ...ResetAdminPasswordCommandDelegateBean.java |   0
 .../admin/main/su/SUExistingOperation.java    |   0
 .../su/SetPrivilegesCommandDelegateBean.java  |   0
 .../admin/main/su/SuperUserOperation.java     |   0
 .../ViewAdministratorCommandDelegateBean.java |   0
 .../SetTaskStatusCommandDelegateBean.java     |   0
 .../tick/TickerStatusCommandDelegateBean.java |   0
 .../tick/ToggleTickerCommandDelegateBean.java |   0
 .../users/GiveCreditsCommandDelegateBean.java |   0
 .../ListAccountsCommandDelegateBean.java      |   0
 .../ListSessionsCommandDelegateBean.java      |   0
 .../users/ViewAccountCommandDelegateBean.java |   0
 .../AccountCreationCommandDelegateBean.java   |   0
 .../ConfPwdRecoveryCommandDelegateBean.java   |   0
 .../user/ext/ExternalSessionDefinerBean.java  |   0
 .../beans/user/ext/LanguageListRequired.java  |   0
 .../ext/ListLanguagesCommandDelegateBean.java |   0
 .../ReqPwdRecoveryCommandDelegateBean.java    |   0
 .../player/BanDetailsCommandDelegateBean.java |   0
 .../beans/user/player/BannedSubTypeBean.java  |   0
 .../user/player/DisabledSubTypeBean.java      |   0
 .../lw/beans/user/player/GameSubTypeBean.java |   0
 .../GetLanguageCommandDelegateBean.java       |   0
 .../user/player/PlayerCommonCommandsBean.java |   0
 .../user/player/PlayerSessionDefinerBean.java |   0
 .../user/player/PlayerSessionSubType.java     |   0
 .../player/ReactivateCommandDelegateBean.java |   0
 .../player/ValidationCommandDelegateBean.java |   0
 .../user/player/ValidationSubTypeBean.java    |   0
 .../CancelQuitCommandDelegateBean.java        |   0
 ...reateAuthChallengeCommandDelegateBean.java |   0
 .../GetAccountCommandDelegateBean.java        |   0
 .../account/QuitGameCommandDelegateBean.java  |   0
 .../SetAddressCommandDelegateBean.java        |   0
 .../SetLanguageCommandDelegateBean.java       |   0
 .../SetPasswordCommandDelegateBean.java       |   0
 .../SetPreferencesCommandDelegateBean.java    |   0
 .../ToggleVacationCommandDelegateBean.java    |   0
 ...ValidateSetAddressCommandDelegateBean.java |   0
 .../bt/ListBugsCommandDelegateBean.java       |   0
 .../bt/PostCommentCommandDelegateBean.java    |   0
 .../bt/ReportBugCommandDelegateBean.java      |   0
 .../player/bt/ViewBugCommandDelegateBean.java |   0
 .../game/GetBattleCommandDelegateBean.java    |   0
 .../game/GetNewPlanetCommandDelegateBean.java |   0
 .../ImplementTechCommandDelegateBean.java     |   0
 .../game/ListBattlesCommandDelegateBean.java  |   0
 .../game/ListPlanetsCommandDelegateBean.java  |   0
 .../game/OverviewCommandDelegateBean.java     |   0
 .../game/ViewMapCommandDelegateBean.java      |   0
 .../AllianceStatusCommandDelegateBean.java    |   0
 .../CancelJoinCommandDelegateBean.java        |   0
 .../CreateAllianceCommandDelegateBean.java    |   0
 .../JoinAllianceCommandDelegateBean.java      |   0
 .../KickMembersCommandDelegateBean.java       |   0
 .../LeaveAllianceCommandDelegateBean.java     |   0
 .../ManageRequestsCommandDelegateBean.java    |   0
 ...TransferLeadershipCommandDelegateBean.java |   0
 .../ViewAllianceCommandDelegateBean.java      |   0
 .../elist/AddEnemyCommandDelegateBean.java    |   0
 .../elist/EnemyListCommandDelegateBean.java   |   0
 .../RemoveEnemiesCommandDelegateBean.java     |   0
 .../DisbandFleetsCommandDelegateBean.java     |   0
 .../MergeFleetsCommandDelegateBean.java       |   0
 .../fleets/MoveFleetsCommandDelegateBean.java |   0
 .../RenameFleetsCommandDelegateBean.java      |   0
 .../SetFleetsModeCommandDelegateBean.java     |   0
 .../fleets/SplitFleetCommandDelegateBean.java |   0
 .../fleets/ViewFleetsCommandDelegateBean.java |   0
 .../AbandonPlanetCommandDelegateBean.java     |   0
 .../BuildingActionCommandDelegateBean.java    |   0
 .../FlushQueueCommandDelegateBean.java        |   0
 .../RenamePlanetCommandDelegateBean.java      |   0
 .../ShipBuildingCommandDelegateBean.java      |   0
 .../ViewPlanetCommandDelegateBean.java        |   0
 .../ComposeMessageCommandDelegateBean.java    |   0
 .../msgs/GetMessagesCommandDelegateBean.java  |   0
 .../msgs/ListTargetsCommandDelegateBean.java  |   0
 .../msgs/MessageBoxCommandDelegateBean.java   |   0
 .../PrepareMessageCommandDelegateBean.java    |   0
 .../msgs/ReadMessageCommandDelegateBean.java  |   0
 .../resources/configuration/user-beans.xml    |   0
 .../user/admin-session-definer-bean.xml       |   0
 .../user/external-session-definer-bean.xml    |   0
 .../user/object-name-validator-bean.xml       |   0
 .../user/player-session-definer-bean.xml      |   0
 .../user/session-command-wiring-bean.xml      |   0
 .../user/session-subtype-wiring-bean.xml      |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  26 +-
 .../db-structure/database.sql                 |   0
 .../db-structure/db-config.txt                |   0
 .../db-structure/parts/000-schemas.sql        |   0
 .../db-structure/parts/010-data.sql           |   0
 .../db-structure/parts/020-functions.sql      |   0
 .../db-structure/parts/030-updates.sql        |   0
 .../db-structure/parts/data/000-typedefs.sql  |   0
 .../db-structure/parts/data/010-i18n-data.sql |   0
 .../parts/data/020-prefs-data.sql             |   0
 .../parts/data/030-users-data.sql             |   0
 .../parts/data/035-session-data.sql           |   0
 .../parts/data/040-admin-data.sql             |   0
 .../parts/data/050-accounts-data.sql          |   0
 .../db-structure/parts/data/055-bugs-data.sql |   0
 .../parts/data/060-naming-data.sql            |   0
 .../parts/data/070-constants-data.sql         |   0
 .../parts/data/080-techs-data.sql             |   0
 .../parts/data/090-buildables-data.sql        |   0
 .../parts/data/100-universe-data.sql          |   0
 .../parts/data/110-empires-data.sql           |   0
 .../parts/data/120-construction-data.sql      |   0
 .../parts/data/130-fleets-data.sql            |   0
 .../parts/data/140-status-data.sql            |   0
 .../db-structure/parts/data/150-logs-data.sql |   0
 .../parts/data/160-battle-data.sql            |   0
 .../parts/data/170-events-data.sql            |   0
 .../parts/data/180-messages-data.sql          |   0
 .../parts/functions/000-defs-functions.sql    |   0
 .../parts/functions/002-sys-functions.sql     |   0
 .../parts/functions/005-logs-functions.sql    |   0
 .../functions/010-constants-functions.sql     |   0
 .../parts/functions/020-naming-functions.sql  |   0
 .../parts/functions/030-tech-functions.sql    |   0
 .../parts/functions/035-users-view.sql        |   0
 .../parts/functions/040-empire-functions.sql  |   0
 .../functions/050-computation-functions.sql   |   0
 .../functions/060-universe-functions.sql      |   0
 .../parts/functions/070-users-functions.sql   |   0
 .../parts/functions/075-session-functions.sql |   0
 .../functions/080-buildings-functions.sql     |   0
 .../parts/functions/100-status-functions.sql  |   0
 .../parts/functions/110-prefs-functions.sql   |   0
 .../parts/functions/120-map-functions.sql     |   0
 .../parts/functions/140-planets-functions.sql |   0
 .../parts/functions/150-battle-functions.sql  |   0
 .../parts/functions/160-battle-views.sql      |   0
 .../functions/163-alliance-functions.sql      |   0
 .../parts/functions/165-fleets-functions.sql  |   0
 .../parts/functions/167-planet-list.sql       |   0
 .../parts/functions/170-event-functions.sql   |   0
 .../functions/180-messages-functions.sql      |   0
 .../parts/functions/190-admin-functions.sql   |   0
 .../parts/functions/200-bugs-functions.sql    |   0
 .../parts/functions/210-admin-overview.sql    |   0
 .../parts/updates/000-updates-ctrl.sql        |   0
 .../parts/updates/010-empire-money.sql        |   0
 .../parts/updates/020-empire-research.sql     |   0
 .../parts/updates/025-empire-debt.sql         |   0
 .../parts/updates/030-fleet-arrivals.sql      |   0
 .../parts/updates/040-fleet-movements.sql     |   0
 .../parts/updates/050-fleet-status.sql        |   0
 .../parts/updates/060-planet-battle.sql       |   0
 .../parts/updates/070-planet-abandon.sql      |   0
 .../parts/updates/080-planet-construction.sql |   0
 .../parts/updates/090-planet-military.sql     |   0
 .../parts/updates/100-planet-population.sql   |   0
 .../parts/updates/110-planet-money.sql        |   0
 .../pom.xml                                   |  11 +-
 .../deepclone/lw/sqld/accounts/Account.java   |   0
 .../sqld/accounts/AccountOperationResult.java |   0
 .../lw/sqld/accounts/QuittingAccount.java     |   0
 .../lw/sqld/accounts/ValidationResult.java    |   0
 .../lw/sqld/admin/AdminConnection.java        |   0
 .../deepclone/lw/sqld/admin/AdminRecord.java  |   0
 .../lw/sqld/game/AllianceMembership.java      |   0
 .../lw/sqld/game/BuildingOutputType.java      |   0
 .../lw/sqld/game/EmpireTechLine.java          |   0
 .../lw/sqld/game/EmpireTechnology.java        |   0
 .../lw/sqld/game/GeneralInformation.java      |   0
 .../com/deepclone/lw/sqld/game/MapData.java   |   0
 .../deepclone/lw/sqld/game/PlanetData.java    |   0
 .../deepclone/lw/sqld/game/RawFleetOwner.java |   0
 .../deepclone/lw/sqld/game/RawFleetShip.java  |   0
 .../lw/sqld/game/RawStaticFleet.java          |   0
 .../lw/sqld/game/battle/BattleListRecord.java |   0
 .../game/battle/BuildingHistoryRecord.java    |   0
 .../sqld/game/battle/EmpireBattleRecord.java  |   0
 .../lw/sqld/game/battle/EventItemRecord.java  |   0
 .../lw/sqld/game/battle/EventRecord.java      |   0
 .../lw/sqld/game/battle/PresenceRecord.java   |   0
 .../sqld/game/battle/ProtagonistRecord.java   |   0
 .../sqld/game/battle/ShipHistoryRecord.java   |   0
 .../deepclone/lw/sqld/i18n/Translation.java   |   0
 .../lw/sqld/msgs/AdminEventRecord.java        |   0
 .../lw/sqld/msgs/AllianceEventRecord.java     |   0
 .../lw/sqld/msgs/BugEventRecord.java          |   0
 .../lw/sqld/msgs/EmpireEventRecord.java       |   0
 .../deepclone/lw/sqld/msgs/EventRecord.java   |   0
 .../com/deepclone/lw/sqld/msgs/EventType.java |   0
 .../lw/sqld/msgs/EventTypeRecord.java         |   0
 .../lw/sqld/msgs/FleetEventFleet.java         |   0
 .../lw/sqld/msgs/FleetEventRecord.java        |   0
 .../deepclone/lw/sqld/msgs/FormatType.java    |   0
 .../deepclone/lw/sqld/msgs/InboxRecord.java   |   0
 .../lw/sqld/msgs/MessageDataRecord.java       |   0
 .../lw/sqld/msgs/NotificationsRecord.java     |   0
 .../lw/sqld/msgs/PlanetEventRecord.java       |   0
 .../lw/sqld/msgs/QueueEventLocation.java      |   0
 .../lw/sqld/msgs/QueueEventRecord.java        |   0
 .../lw/sqld/msgs/TextMessageRecord.java       |   0
 .../com/deepclone/lw/sqld/sys/Constant.java   |   0
 .../deepclone/lw/sqld/sys/ExceptionLog.java   |   0
 .../deepclone/lw/sqld/sys/StackTraceLog.java  |   0
 .../com/deepclone/lw/sqld/sys/Status.java     |   0
 .../deepclone/lw/sqld/sys/SystemLogEntry.java |   0
 .../lw/sqld/sys/TickerTaskRecord.java         |   0
 .../configuration/transaction-bean.xml        |   0
 legacyworlds-server-data/src/test/java/.empty |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  12 +-
 .../interfaces/acm/AccountMailException.java  |   0
 .../lw/interfaces/acm/AccountManagement.java  |   0
 .../lw/interfaces/acm/AccountSession.java     |   0
 .../interfaces/acm/EmailChangeException.java  |   0
 .../lw/interfaces/acm/JoinGameException.java  |   0
 .../acm/PasswordProhibitedException.java      |   0
 .../acm/PasswordRecoveryException.java        |   0
 .../acm/PermanentlyDisabledException.java     |   0
 .../lw/interfaces/acm/UserSessionDAO.java     |   0
 .../deepclone/lw/interfaces/acm/UsersDAO.java |   0
 .../lw/interfaces/admin/AdminDAO.java         |   0
 .../interfaces/admin/AdminDAOException.java   |   0
 .../lw/interfaces/admin/Administration.java   |   0
 .../lw/interfaces/admin/BanMailData.java      |   0
 .../lw/interfaces/admin/BansDAO.java          |   0
 .../deepclone/lw/interfaces/admin/IpBan.java  |   0
 .../deepclone/lw/interfaces/bt/AdminBugs.java |   0
 .../deepclone/lw/interfaces/bt/BugsDAO.java   |   0
 .../lw/interfaces/bt/EmpireSummary.java       |   0
 .../lw/interfaces/bt/PlayerBugs.java          |   0
 .../interfaces/eventlog/ExtendedLogEntry.java |   0
 .../lw/interfaces/eventlog/LogReader.java     |   0
 .../lw/interfaces/eventlog/LogWriter.java     |   0
 .../lw/interfaces/eventlog/Logger.java        |   0
 .../lw/interfaces/eventlog/SystemLogger.java  |   0
 .../lw/interfaces/game/AllianceDAO.java       |   0
 .../interfaces/game/AllianceManagement.java   |   0
 .../lw/interfaces/game/BattleViewer.java      |   0
 .../lw/interfaces/game/BattlesCache.java      |   0
 .../lw/interfaces/game/BattlesDAO.java        |   0
 .../lw/interfaces/game/EmpireDAO.java         |   0
 .../lw/interfaces/game/EmpireManagement.java  |   0
 .../lw/interfaces/game/FleetManagement.java   |   0
 .../lw/interfaces/game/FleetsDAO.java         |   0
 .../lw/interfaces/game/MapViewParameters.java |   0
 .../lw/interfaces/game/MapViewer.java         |   0
 .../lw/interfaces/game/PlanetDAO.java         |   0
 .../lw/interfaces/game/PlanetsManagement.java |   0
 .../lw/interfaces/game/UniverseDAO.java       |   0
 .../lw/interfaces/game/UpdatesDAO.java        |   0
 .../i18n/DuplicateLanguageException.java      |   0
 .../i18n/DuplicateStringException.java        |   0
 .../interfaces/i18n/I18NAdministration.java   |   0
 .../lw/interfaces/i18n/I18NException.java     |   0
 .../lw/interfaces/i18n/I18NManager.java       |   0
 .../i18n/InvalidUpdateException.java          |   0
 .../interfaces/i18n/TranslationException.java |   0
 .../lw/interfaces/i18n/Translator.java        |   0
 .../i18n/UnknownLanguageException.java        |   0
 .../i18n/UnknownStringException.java          |   0
 .../mailer/AlreadySentException.java          |   0
 .../lw/interfaces/mailer/MailData.java        |   0
 .../lw/interfaces/mailer/Mailer.java          |   0
 .../lw/interfaces/mailer/MailerException.java |   0
 .../mailer/MissingDataException.java          |   0
 .../interfaces/mailer/NotSentException.java   |   0
 .../lw/interfaces/msg/AdminMessages.java      |   0
 .../lw/interfaces/msg/EmpireMessages.java     |   0
 .../lw/interfaces/msg/MessageBoxDAO.java      |   0
 .../interfaces/msg/MessageContentCache.java   |   0
 .../lw/interfaces/msg/MessageExtractor.java   |   0
 .../interfaces/msg/MessageFormatRegistry.java |   0
 .../lw/interfaces/msg/MessageFormatter.java   |   0
 .../lw/interfaces/msg/MessageRecordsDAO.java  |   0
 .../lw/interfaces/msg/NotificationsDAO.java   |   0
 .../naming/EmpireNameException.java           |   0
 .../interfaces/naming/MapNameException.java   |   0
 .../lw/interfaces/naming/NamesManager.java    |   0
 .../lw/interfaces/naming/NamingDAO.java       |   0
 .../interfaces/prefs/AccountPreferences.java  |   0
 .../lw/interfaces/prefs/Preference.java       |   0
 .../prefs/PreferenceDefinitionException.java  |   0
 .../prefs/PreferenceDefinitions.java          |   0
 .../lw/interfaces/prefs/PreferenceGroup.java  |   0
 .../lw/interfaces/prefs/PreferenceType.java   |   0
 .../prefs/PreferenceTypesRegistry.java        |   0
 .../lw/interfaces/prefs/PreferencesDAO.java   |   0
 .../lw/interfaces/session/ServerSession.java  |   0
 .../lw/interfaces/session/SessionManager.java |   0
 .../session/SessionTypeDefiner.java           |   0
 .../lw/interfaces/sys/ConstantDefinition.java |   0
 .../sys/ConstantsAdministration.java          |   0
 .../lw/interfaces/sys/ConstantsManager.java   |   0
 .../lw/interfaces/sys/ConstantsUser.java      |   0
 .../sys/EndAutowiredTransaction.java          |   0
 .../interfaces/sys/InvalidConstantValue.java  |   0
 .../lw/interfaces/sys/MaintenanceData.java    |   0
 .../sys/MaintenanceStatusException.java       |   0
 .../lw/interfaces/sys/SystemStatus.java       |   0
 .../interfaces/sys/TickStatusException.java   |   0
 .../deepclone/lw/interfaces/sys/Ticker.java   |   0
 .../lw/interfaces/sys/TickerManager.java      |   0
 .../interfaces/sys/UnknownConstantError.java  |   0
 .../lw/interfaces/sys/WiringException.java    |   0
 .../src/main/resources/.empty                 |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../data-source.xml                           |   3 +-
 .../data/addressChangeMail-en.txt             |   0
 .../data/addressChangeMail-fr.txt             |   0
 .../data/adminErrorMail.txt                   |   0
 .../data/adminRecapMail.txt                   |   0
 .../data/banLiftedMail-en.txt                 |   0
 .../data/banLiftedMail-fr.txt                 |   0
 .../data/bannedMail-en.txt                    |   0
 .../data/bannedMail-fr.txt                    |   0
 .../data/buildables-test.xml                  |   0
 .../data/buildables.xml                       |   0
 .../data/buildables.xsd                       |   0
 .../data/i18n-text.xml                        |   0
 .../data/i18n-text.xsd                        |   0
 .../data/inactivityQuitMail-en.txt            |   0
 .../data/inactivityQuitMail-fr.txt            |   0
 .../data/inactivityWarningMail-en.txt         |   0
 .../data/inactivityWarningMail-fr.txt         |   0
 .../data/messageMail-en.txt                   |   0
 .../data/messageMail-fr.txt                   |   0
 .../data/passwordRecoveryMail-en.txt          |   0
 .../data/passwordRecoveryMail-fr.txt          |   0
 .../data/quitMail-en.txt                      |   0
 .../data/quitMail-fr.txt                      |   0
 .../data/reactivationMail-en.txt              |   0
 .../data/reactivationMail-fr.txt              |   0
 .../data/recapMail-en.txt                     |   0
 .../data/recapMail-fr.txt                     |   0
 .../data/registrationMail-en.txt              |   0
 .../data/registrationMail-fr.txt              |   0
 .../data/techs-test.xml                       |   0
 .../data/techs.xml                            |   0
 .../data/techs.xsd                            |   0
 .../pom.xml                                   |  42 +--
 .../src/main/java/com/deepclone/lw/Main.java  |   0
 .../java/com/deepclone/lw/cli/CLITool.java    |   0
 .../com/deepclone/lw/cli/CreateSuperuser.java |   0
 .../java/com/deepclone/lw/cli/CreateUser.java |   0
 .../java/com/deepclone/lw/cli/ExportDB.java   |   0
 .../deepclone/lw/cli/ImportBuildables.java    |   0
 .../com/deepclone/lw/cli/ImportTechs.java     |   0
 .../java/com/deepclone/lw/cli/ImportText.java |   0
 .../main/java/com/deepclone/lw/cli/Stop.java  |   0
 .../main/java/com/deepclone/lw/cli/Tick.java  |   0
 .../java/com/deepclone/lw/cli/ToolBase.java   |   0
 .../lw/cli/dbexport/Administrator.java        |   0
 .../deepclone/lw/cli/dbexport/BREComment.java |   0
 .../lw/cli/dbexport/BREInitialReport.java     |   0
 .../deepclone/lw/cli/dbexport/BREMerger.java  |   0
 .../lw/cli/dbexport/BREStatusChange.java      |   0
 .../lw/cli/dbexport/BREVisibilityChange.java  |   0
 .../lw/cli/dbexport/BugEventMapper.java       |   0
 .../deepclone/lw/cli/dbexport/BugGroup.java   |   0
 .../lw/cli/dbexport/BugReportEvent.java       |   0
 .../deepclone/lw/cli/dbexport/BugReports.java |   0
 .../lw/cli/dbexport/BugSubmitter.java         |   0
 .../lw/cli/dbexport/LegacyWorldsDB.java       |   0
 .../com/deepclone/lw/cli/dbexport/User.java   |   0
 .../deepclone/lw/cli/dbexport/UserMapper.java |   0
 .../deepclone/lw/cli/dbexport/Warnings.java   |   0
 .../com/deepclone/lw/srv/LogAppender.java     |   0
 .../java/com/deepclone/lw/srv/Server.java     |   0
 .../deepclone/lw/srv/ServerTerminator.java    |   0
 .../lw/srv/ServerTerminatorBean.java          |   0
 .../configuration/context-configuration.xml   |   0
 .../src/main/resources/log4j.properties       |   0
 .../src/main/resources/lw-server.xml          |   0
 legacyworlds-server-main/src/test/java/.empty |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  19 +-
 .../pom.xml                                   |  11 +-
 .../deepclone/lw/utils/Base64Serializer.java  |   0
 .../com/deepclone/lw/utils/EmailAddress.java  |   0
 .../java/com/deepclone/lw/utils/Password.java |   0
 .../lw/utils/RandomStringGenerator.java       |   0
 .../com/deepclone/lw/utils/StoredProc.java    |   0
 .../src/main/resources/.empty                 |   0
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 legacyworlds-server/.project                  |  17 -
 .../legacyworlds-server-beans/.project        |  17 -
 .../.classpath                                |  10 -
 .../.project                                  |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../legacyworlds-server-beans-bt/.classpath   |  10 -
 .../legacyworlds-server-beans-bt/.project     |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../.classpath                                |  10 -
 .../.project                                  |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../legacyworlds-server-beans-i18n/.classpath |  10 -
 .../legacyworlds-server-beans-i18n/.project   |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../legacyworlds-server-beans-i18n/pom.xml    |  16 -
 .../.classpath                                |  10 -
 .../legacyworlds-server-beans-mailer/.project |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.classpath                                |  10 -
 .../legacyworlds-server-beans-naming/.project |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../.classpath                                |  10 -
 .../legacyworlds-server-beans-simple/.project |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../legacyworlds-server-beans-simple/pom.xml  |  13 -
 .../.classpath                                |  10 -
 .../legacyworlds-server-beans-system/.project |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../legacyworlds-server-beans-user/.classpath |  10 -
 .../legacyworlds-server-beans-user/.project   |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../legacyworlds-server-data/.classpath       |  10 -
 .../legacyworlds-server-data/.project         |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../db-structure/test-mode.sql                |   7 -
 .../legacyworlds-server-interfaces/.classpath |  10 -
 .../legacyworlds-server-interfaces/.project   |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../legacyworlds-server-main/.classpath       |  10 -
 .../legacyworlds-server-main/.project         |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../legacyworlds-server-main/hibernate.xml    |  25 --
 .../legacyworlds-server-tests/.classpath      |  10 -
 .../legacyworlds-server-tests/.project        |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../legacyworlds-server-utils/.classpath      |  10 -
 .../legacyworlds-server-utils/.project        |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 legacyworlds-server/pom.xml                   |  39 +-
 legacyworlds-session/.classpath               |  10 -
 legacyworlds-session/.project                 |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 legacyworlds-session/pom.xml                  |  10 +-
 .../src/main/resources/.empty                 |   0
 legacyworlds-session/src/test/java/.empty     |   0
 .../src/test/resources/.empty                 |   0
 legacyworlds-utils/.classpath                 |  10 -
 legacyworlds-utils/.project                   |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 legacyworlds-utils/pom.xml                    |  12 +-
 legacyworlds-utils/src/main/resources/.empty  |   0
 legacyworlds-utils/src/test/java/.empty       |   0
 legacyworlds-utils/src/test/resources/.empty  |   0
 .../Content/Filtered/fm/version.ftl           |   2 +
 .../Content/Raw}/META-INF/MANIFEST.MF         |   0
 .../Content/Raw}/WEB-INF/admin-servlet.xml    |   0
 .../Content/Raw}/WEB-INF/fm/ROOT.ftl          |   0
 .../Raw}/WEB-INF/fm/containers/external.ftl   |   0
 .../Raw}/WEB-INF/fm/containers/internal.ftl   |   0
 .../Raw}/WEB-INF/fm/layout/columns.ftl        |   0
 .../Raw}/WEB-INF/fm/layout/datatable.ftl      |   0
 .../Content/Raw}/WEB-INF/fm/layout/fields.ftl |   0
 .../Content/Raw}/WEB-INF/fm/layout/form.ftl   |   0
 .../Content/Raw}/WEB-INF/fm/layout/lists.ftl  |   0
 .../Content/Raw}/WEB-INF/fm/layout/tabs.ftl   |   0
 .../Raw}/WEB-INF/fm/types/addAdmin.ftl        |   0
 .../Content/Raw}/WEB-INF/fm/types/admins.ftl  |   0
 .../Raw}/WEB-INF/fm/types/banReject.ftl       |   0
 .../Raw}/WEB-INF/fm/types/banRequest.ftl      |   0
 .../Content/Raw}/WEB-INF/fm/types/bans.ftl    |   0
 .../Raw}/WEB-INF/fm/types/bansSummary.ftl     |   0
 .../Raw}/WEB-INF/fm/types/bugsList.ftl        |   0
 .../Raw}/WEB-INF/fm/types/bugsReport.ftl      |   0
 .../Raw}/WEB-INF/fm/types/bugsSummary.ftl     |   0
 .../Raw}/WEB-INF/fm/types/bugsTabs.ftl        |   0
 .../Raw}/WEB-INF/fm/types/bugsView.ftl        |   0
 .../Raw}/WEB-INF/fm/types/changePassword.ftl  |   0
 .../Raw}/WEB-INF/fm/types/constants.ftl       |   0
 .../Raw}/WEB-INF/fm/types/language.ftl        |   0
 .../Raw}/WEB-INF/fm/types/languages.ftl       |   0
 .../Raw}/WEB-INF/fm/types/logEntry.ftl        |   0
 .../Content/Raw}/WEB-INF/fm/types/login.ftl   |   0
 .../Content/Raw}/WEB-INF/fm/types/logs.ftl    |   0
 .../Content/Raw}/WEB-INF/fm/types/main.ftl    |   0
 .../Raw}/WEB-INF/fm/types/maintenance.ftl     |   0
 .../Content/Raw}/WEB-INF/fm/types/message.ftl |   0
 .../Raw}/WEB-INF/fm/types/messageBox.ftl      |   0
 .../Raw}/WEB-INF/fm/types/messageTabs.ftl     |   0
 .../Raw}/WEB-INF/fm/types/messageWriter.ftl   |   0
 .../Content/Raw}/WEB-INF/fm/types/names.ftl   |   0
 .../Raw}/WEB-INF/fm/types/namesSummary.ftl    |   0
 .../Content/Raw}/WEB-INF/fm/types/offline.ftl |   0
 .../Raw}/WEB-INF/fm/types/preferences.ftl     |   0
 .../Raw}/WEB-INF/fm/types/resetAdmin.ftl      |   0
 .../Content/Raw}/WEB-INF/fm/types/spam.ftl    |   0
 .../Content/Raw}/WEB-INF/fm/types/ticker.ftl  |   0
 .../Content/Raw}/WEB-INF/fm/types/user.ftl    |   0
 .../Raw}/WEB-INF/fm/types/userSessions.ftl    |   0
 .../Content/Raw}/WEB-INF/fm/types/users.ftl   |   0
 .../Raw}/WEB-INF/fm/types/viewAdmin.ftl       |   0
 .../Content/Raw}/WEB-INF/web.xml              |   0
 .../Content/Raw}/css/main.css                 |   0
 legacyworlds-web-admin/pom.xml                |  51 +++
 .../lw/web/admin/BanhammerPages.java          |   0
 .../lw/web/admin/BugTrackerPages.java         |   0
 .../lw/web/admin/ConstantsPages.java          |   0
 .../lw/web/admin/ErrorHandlerBean.java        |   0
 .../com/deepclone/lw/web/admin/I18NPages.java |   0
 .../com/deepclone/lw/web/admin/LogPages.java  |   0
 .../com/deepclone/lw/web/admin/LoginPage.java |   0
 .../lw/web/admin/MaintenancePages.java        |   0
 .../lw/web/admin/MessageBoxView.java          |   0
 .../deepclone/lw/web/admin/MessagesPages.java |   0
 .../deepclone/lw/web/admin/NamesPages.java    |   0
 .../deepclone/lw/web/admin/PasswordPages.java |   0
 .../lw/web/admin/PreferencesPages.java        |   0
 .../deepclone/lw/web/admin/SessionPages.java  |   0
 .../com/deepclone/lw/web/admin/SpamPages.java |   0
 .../lw/web/admin/SuperUserPages.java          |   0
 .../deepclone/lw/web/admin/TickerPages.java   |   0
 .../deepclone/lw/web/admin/UsersPages.java    |   0
 .../lw/web/admin/i18ne/LanguageExport.java    |   0
 .../lw/web/admin/i18ne/StringExport.java      |   0
 .../src/main/resources/log4j.properties       |   0
 legacyworlds-web-admin/src/test/java/.empty   |   0
 .../src/test/resources/.empty                 |   0
 .../pom.xml                                   |  20 +-
 .../beans/intercept/IEContentTypeBean.java    |   0
 .../intercept/LanguageInterceptorBean.java    |   0
 .../intercept/SessionInterceptorBean.java     |   0
 .../beans/intercept/SessionRequirement.java   |   0
 .../lw/web/beans/msgs/MessageFormatter.java   |   0
 .../web/beans/msgs/MessageFormatterBean.java  |   0
 .../beans/session/ClientSessionReference.java |   0
 .../web/beans/session/MaintenanceStatus.java  |   0
 .../beans/session/MaintenanceStatusBean.java  |   0
 .../lw/web/beans/session/Session.java         |   1 -
 .../lw/web/beans/session/SessionClient.java   |   0
 .../web/beans/session/SessionClientBean.java  |   0
 .../session/SessionMaintenanceException.java  |   0
 .../beans/session/SessionServerException.java |   0
 .../lw/web/beans/session/SessionType.java     |   0
 .../lw/web/beans/view/BugTrackerBase.java     |   0
 .../com/deepclone/lw/web/beans/view/Page.java |   0
 .../lw/web/beans/view/PageControllerBase.java |   0
 .../deepclone/lw/web/csess/AdminSession.java  |   0
 .../lw/web/csess/ExternalSession.java         |   0
 .../deepclone/lw/web/csess/PlayerSession.java |   0
 .../src/main/resources/.empty                 |   0
 legacyworlds-web-beans/src/test/java/.empty   |   0
 .../src/test/resources/.empty                 |   0
 .../Content/Filtered/fm/version.ftl           |   2 +
 .../Content/Raw}/META-INF/MANIFEST.MF         |   0
 .../Content/Raw}/WEB-INF/fm/ROOT.ftl          |   0
 .../Raw}/WEB-INF/fm/en/containers/chat.ftl    |   0
 .../WEB-INF/fm/en/containers/external.ftl     |   0
 .../Raw}/WEB-INF/fm/en/containers/game.ftl    |   0
 .../Raw}/WEB-INF/fm/en/containers/offline.ftl |   0
 .../WEB-INF/fm/en/containers/restricted.ftl   |   0
 .../Content/Raw}/WEB-INF/fm/en/game.ftl       |   0
 .../Raw}/WEB-INF/fm/en/static/home.ftl        |   0
 .../Raw}/WEB-INF/fm/en/static/loggedOut.ftl   |   0
 .../Raw}/WEB-INF/fm/en/static/loginFailed.ftl |   0
 .../Raw}/WEB-INF/fm/en/static/noSession.ftl   |   0
 .../fm/en/static/passwordRecoveryOk.ftl       |   0
 .../Raw}/WEB-INF/fm/en/static/reactivate.ftl  |   0
 .../Raw}/WEB-INF/fm/en/static/rules.ftl       |   0
 .../Raw}/WEB-INF/fm/en/static/scope.ftl       |   0
 .../Raw}/WEB-INF/fm/en/types/account.ftl      |   0
 .../Raw}/WEB-INF/fm/en/types/alliance.ftl     |   0
 .../Raw}/WEB-INF/fm/en/types/banned.ftl       |   0
 .../Raw}/WEB-INF/fm/en/types/battle.ftl       |   0
 .../Raw}/WEB-INF/fm/en/types/battles.ftl      |   0
 .../Raw}/WEB-INF/fm/en/types/bugsList.ftl     |   0
 .../Raw}/WEB-INF/fm/en/types/bugsReport.ftl   |   0
 .../Raw}/WEB-INF/fm/en/types/bugsTabs.ftl     |   0
 .../Raw}/WEB-INF/fm/en/types/bugsView.ftl     |   0
 .../Content/Raw}/WEB-INF/fm/en/types/chat.ftl |   0
 .../Raw}/WEB-INF/fm/en/types/enemies.ftl      |   0
 .../Raw}/WEB-INF/fm/en/types/fleets.ftl       |   0
 .../WEB-INF/fm/en/types/fleetsCommand.ftl     |   0
 .../Raw}/WEB-INF/fm/en/types/getNewPlanet.ftl |   0
 .../Raw}/WEB-INF/fm/en/types/maintenance.ftl  |   0
 .../Content/Raw}/WEB-INF/fm/en/types/map.ftl  |   0
 .../Raw}/WEB-INF/fm/en/types/message.ftl      |   0
 .../Raw}/WEB-INF/fm/en/types/messageBox.ftl   |   0
 .../Raw}/WEB-INF/fm/en/types/messageTabs.ftl  |   0
 .../WEB-INF/fm/en/types/messageTargets.ftl    |   0
 .../WEB-INF/fm/en/types/messageWriter.ftl     |   0
 .../Raw}/WEB-INF/fm/en/types/offline.ftl      |   0
 .../Raw}/WEB-INF/fm/en/types/overview.ftl     |   0
 .../WEB-INF/fm/en/types/passwordRecovery.ftl  |   0
 .../Raw}/WEB-INF/fm/en/types/planet.ftl       |   0
 .../Raw}/WEB-INF/fm/en/types/planets.ftl      |   0
 .../Raw}/WEB-INF/fm/en/types/reactivation.ftl |   0
 .../Raw}/WEB-INF/fm/en/types/register.ftl     |   0
 .../Raw}/WEB-INF/fm/en/types/registered.ftl   |   0
 .../Raw}/WEB-INF/fm/en/types/splitFleet.ftl   |   0
 .../Raw}/WEB-INF/fm/en/types/static.ftl       |   0
 .../Raw}/WEB-INF/fm/en/types/validation.ftl   |   0
 .../Raw}/WEB-INF/fm/fr/containers/chat.ftl    |   0
 .../WEB-INF/fm/fr/containers/external.ftl     |   0
 .../Raw}/WEB-INF/fm/fr/containers/game.ftl    |   0
 .../Raw}/WEB-INF/fm/fr/containers/offline.ftl |   0
 .../WEB-INF/fm/fr/containers/restricted.ftl   |   0
 .../Content/Raw}/WEB-INF/fm/fr/game.ftl       |   0
 .../Raw}/WEB-INF/fm/fr/static/home.ftl        |   0
 .../Raw}/WEB-INF/fm/fr/static/loggedOut.ftl   |   0
 .../Raw}/WEB-INF/fm/fr/static/loginFailed.ftl |   0
 .../Raw}/WEB-INF/fm/fr/static/noSession.ftl   |   0
 .../fm/fr/static/passwordRecoveryOk.ftl       |   0
 .../Raw}/WEB-INF/fm/fr/static/reactivate.ftl  |   0
 .../Raw}/WEB-INF/fm/fr/static/rules.ftl       |   0
 .../Raw}/WEB-INF/fm/fr/static/scope.ftl       |   0
 .../Raw}/WEB-INF/fm/fr/types/account.ftl      |   0
 .../Raw}/WEB-INF/fm/fr/types/alliance.ftl     |   0
 .../Raw}/WEB-INF/fm/fr/types/banned.ftl       |   0
 .../Raw}/WEB-INF/fm/fr/types/battle.ftl       |   0
 .../Raw}/WEB-INF/fm/fr/types/battles.ftl      |   0
 .../Raw}/WEB-INF/fm/fr/types/bugsList.ftl     |   0
 .../Raw}/WEB-INF/fm/fr/types/bugsReport.ftl   |   0
 .../Raw}/WEB-INF/fm/fr/types/bugsTabs.ftl     |   0
 .../Raw}/WEB-INF/fm/fr/types/bugsView.ftl     |   0
 .../Content/Raw}/WEB-INF/fm/fr/types/chat.ftl |   0
 .../Raw}/WEB-INF/fm/fr/types/enemies.ftl      |   0
 .../Raw}/WEB-INF/fm/fr/types/fleets.ftl       |   0
 .../WEB-INF/fm/fr/types/fleetsCommand.ftl     |   0
 .../Raw}/WEB-INF/fm/fr/types/getNewPlanet.ftl |   0
 .../Raw}/WEB-INF/fm/fr/types/maintenance.ftl  |   0
 .../Content/Raw}/WEB-INF/fm/fr/types/map.ftl  |   0
 .../Raw}/WEB-INF/fm/fr/types/message.ftl      |   0
 .../Raw}/WEB-INF/fm/fr/types/messageBox.ftl   |   0
 .../Raw}/WEB-INF/fm/fr/types/messageTabs.ftl  |   0
 .../WEB-INF/fm/fr/types/messageTargets.ftl    |   0
 .../WEB-INF/fm/fr/types/messageWriter.ftl     |   0
 .../Raw}/WEB-INF/fm/fr/types/offline.ftl      |   0
 .../Raw}/WEB-INF/fm/fr/types/overview.ftl     |   0
 .../WEB-INF/fm/fr/types/passwordRecovery.ftl  |   0
 .../Raw}/WEB-INF/fm/fr/types/planet.ftl       |   0
 .../Raw}/WEB-INF/fm/fr/types/planets.ftl      |   0
 .../Raw}/WEB-INF/fm/fr/types/reactivation.ftl |   0
 .../Raw}/WEB-INF/fm/fr/types/register.ftl     |   0
 .../Raw}/WEB-INF/fm/fr/types/registered.ftl   |   0
 .../Raw}/WEB-INF/fm/fr/types/splitFleet.ftl   |   0
 .../Raw}/WEB-INF/fm/fr/types/static.ftl       |   0
 .../Raw}/WEB-INF/fm/fr/types/validation.ftl   |   0
 .../Raw}/WEB-INF/fm/layout/columns.ftl        |   0
 .../Raw}/WEB-INF/fm/layout/datatable.ftl      |   0
 .../Content/Raw}/WEB-INF/fm/layout/fields.ftl |   0
 .../Content/Raw}/WEB-INF/fm/layout/form.ftl   |   0
 .../Raw}/WEB-INF/fm/layout/happiness.ftl      |   0
 .../Content/Raw}/WEB-INF/fm/layout/lists.ftl  |   0
 .../Content/Raw}/WEB-INF/fm/layout/tabs.ftl   |   0
 .../Content/Raw}/WEB-INF/main-servlet.xml     |   0
 .../Content/Raw}/WEB-INF/web.xml              |   0
 .../Content/Raw}/css/main.css                 |   0
 .../Content/Raw}/img/background.jpg           | Bin
 .../Content/Raw}/img/button-0.png             | Bin
 .../Content/Raw}/img/button-1.png             | Bin
 .../Content/Raw}/img/button-2.png             | Bin
 .../Content/Raw}/img/button-3.png             | Bin
 .../Content/Raw}/img/button-4.png             | Bin
 .../Content/Raw}/img/button-5.png             | Bin
 .../Content/Raw}/img/button-6.png             | Bin
 .../Content/Raw}/img/pp/l/1.png               | Bin
 .../Content/Raw}/img/pp/l/10.png              | Bin
 .../Content/Raw}/img/pp/l/100.png             | Bin
 .../Content/Raw}/img/pp/l/101.png             | Bin
 .../Content/Raw}/img/pp/l/102.png             | Bin
 .../Content/Raw}/img/pp/l/103.png             | Bin
 .../Content/Raw}/img/pp/l/104.png             | Bin
 .../Content/Raw}/img/pp/l/105.png             | Bin
 .../Content/Raw}/img/pp/l/106.png             | Bin
 .../Content/Raw}/img/pp/l/107.png             | Bin
 .../Content/Raw}/img/pp/l/108.png             | Bin
 .../Content/Raw}/img/pp/l/109.png             | Bin
 .../Content/Raw}/img/pp/l/11.png              | Bin
 .../Content/Raw}/img/pp/l/110.png             | Bin
 .../Content/Raw}/img/pp/l/111.png             | Bin
 .../Content/Raw}/img/pp/l/112.png             | Bin
 .../Content/Raw}/img/pp/l/113.png             | Bin
 .../Content/Raw}/img/pp/l/114.png             | Bin
 .../Content/Raw}/img/pp/l/115.png             | Bin
 .../Content/Raw}/img/pp/l/116.png             | Bin
 .../Content/Raw}/img/pp/l/117.png             | Bin
 .../Content/Raw}/img/pp/l/118.png             | Bin
 .../Content/Raw}/img/pp/l/119.png             | Bin
 .../Content/Raw}/img/pp/l/12.png              | Bin
 .../Content/Raw}/img/pp/l/120.png             | Bin
 .../Content/Raw}/img/pp/l/121.png             | Bin
 .../Content/Raw}/img/pp/l/122.png             | Bin
 .../Content/Raw}/img/pp/l/123.png             | Bin
 .../Content/Raw}/img/pp/l/124.png             | Bin
 .../Content/Raw}/img/pp/l/125.png             | Bin
 .../Content/Raw}/img/pp/l/126.png             | Bin
 .../Content/Raw}/img/pp/l/127.png             | Bin
 .../Content/Raw}/img/pp/l/128.png             | Bin
 .../Content/Raw}/img/pp/l/129.png             | Bin
 .../Content/Raw}/img/pp/l/13.png              | Bin
 .../Content/Raw}/img/pp/l/130.png             | Bin
 .../Content/Raw}/img/pp/l/131.png             | Bin
 .../Content/Raw}/img/pp/l/132.png             | Bin
 .../Content/Raw}/img/pp/l/133.png             | Bin
 .../Content/Raw}/img/pp/l/134.png             | Bin
 .../Content/Raw}/img/pp/l/135.png             | Bin
 .../Content/Raw}/img/pp/l/136.png             | Bin
 .../Content/Raw}/img/pp/l/137.png             | Bin
 .../Content/Raw}/img/pp/l/138.png             | Bin
 .../Content/Raw}/img/pp/l/139.png             | Bin
 .../Content/Raw}/img/pp/l/14.png              | Bin
 .../Content/Raw}/img/pp/l/140.png             | Bin
 .../Content/Raw}/img/pp/l/141.png             | Bin
 .../Content/Raw}/img/pp/l/142.png             | Bin
 .../Content/Raw}/img/pp/l/143.png             | Bin
 .../Content/Raw}/img/pp/l/144.png             | Bin
 .../Content/Raw}/img/pp/l/145.png             | Bin
 .../Content/Raw}/img/pp/l/146.png             | Bin
 .../Content/Raw}/img/pp/l/147.png             | Bin
 .../Content/Raw}/img/pp/l/148.png             | Bin
 .../Content/Raw}/img/pp/l/149.png             | Bin
 .../Content/Raw}/img/pp/l/15.png              | Bin
 .../Content/Raw}/img/pp/l/150.png             | Bin
 .../Content/Raw}/img/pp/l/151.png             | Bin
 .../Content/Raw}/img/pp/l/152.png             | Bin
 .../Content/Raw}/img/pp/l/153.png             | Bin
 .../Content/Raw}/img/pp/l/154.png             | Bin
 .../Content/Raw}/img/pp/l/155.png             | Bin
 .../Content/Raw}/img/pp/l/156.png             | Bin
 .../Content/Raw}/img/pp/l/157.png             | Bin
 .../Content/Raw}/img/pp/l/158.png             | Bin
 .../Content/Raw}/img/pp/l/159.png             | Bin
 .../Content/Raw}/img/pp/l/16.png              | Bin
 .../Content/Raw}/img/pp/l/160.png             | Bin
 .../Content/Raw}/img/pp/l/161.png             | Bin
 .../Content/Raw}/img/pp/l/162.png             | Bin
 .../Content/Raw}/img/pp/l/163.png             | Bin
 .../Content/Raw}/img/pp/l/164.png             | Bin
 .../Content/Raw}/img/pp/l/165.png             | Bin
 .../Content/Raw}/img/pp/l/166.png             | Bin
 .../Content/Raw}/img/pp/l/167.png             | Bin
 .../Content/Raw}/img/pp/l/168.png             | Bin
 .../Content/Raw}/img/pp/l/169.png             | Bin
 .../Content/Raw}/img/pp/l/17.png              | Bin
 .../Content/Raw}/img/pp/l/170.png             | Bin
 .../Content/Raw}/img/pp/l/171.png             | Bin
 .../Content/Raw}/img/pp/l/172.png             | Bin
 .../Content/Raw}/img/pp/l/173.png             | Bin
 .../Content/Raw}/img/pp/l/174.png             | Bin
 .../Content/Raw}/img/pp/l/175.png             | Bin
 .../Content/Raw}/img/pp/l/176.png             | Bin
 .../Content/Raw}/img/pp/l/177.png             | Bin
 .../Content/Raw}/img/pp/l/178.png             | Bin
 .../Content/Raw}/img/pp/l/179.png             | Bin
 .../Content/Raw}/img/pp/l/18.png              | Bin
 .../Content/Raw}/img/pp/l/180.png             | Bin
 .../Content/Raw}/img/pp/l/181.png             | Bin
 .../Content/Raw}/img/pp/l/182.png             | Bin
 .../Content/Raw}/img/pp/l/183.png             | Bin
 .../Content/Raw}/img/pp/l/184.png             | Bin
 .../Content/Raw}/img/pp/l/185.png             | Bin
 .../Content/Raw}/img/pp/l/186.png             | Bin
 .../Content/Raw}/img/pp/l/187.png             | Bin
 .../Content/Raw}/img/pp/l/188.png             | Bin
 .../Content/Raw}/img/pp/l/189.png             | Bin
 .../Content/Raw}/img/pp/l/19.png              | Bin
 .../Content/Raw}/img/pp/l/190.png             | Bin
 .../Content/Raw}/img/pp/l/191.png             | Bin
 .../Content/Raw}/img/pp/l/192.png             | Bin
 .../Content/Raw}/img/pp/l/193.png             | Bin
 .../Content/Raw}/img/pp/l/194.png             | Bin
 .../Content/Raw}/img/pp/l/195.png             | Bin
 .../Content/Raw}/img/pp/l/196.png             | Bin
 .../Content/Raw}/img/pp/l/197.png             | Bin
 .../Content/Raw}/img/pp/l/198.png             | Bin
 .../Content/Raw}/img/pp/l/199.png             | Bin
 .../Content/Raw}/img/pp/l/2.png               | Bin
 .../Content/Raw}/img/pp/l/20.png              | Bin
 .../Content/Raw}/img/pp/l/200.png             | Bin
 .../Content/Raw}/img/pp/l/21.png              | Bin
 .../Content/Raw}/img/pp/l/22.png              | Bin
 .../Content/Raw}/img/pp/l/23.png              | Bin
 .../Content/Raw}/img/pp/l/24.png              | Bin
 .../Content/Raw}/img/pp/l/25.png              | Bin
 .../Content/Raw}/img/pp/l/26.png              | Bin
 .../Content/Raw}/img/pp/l/27.png              | Bin
 .../Content/Raw}/img/pp/l/28.png              | Bin
 .../Content/Raw}/img/pp/l/29.png              | Bin
 .../Content/Raw}/img/pp/l/3.png               | Bin
 .../Content/Raw}/img/pp/l/30.png              | Bin
 .../Content/Raw}/img/pp/l/31.png              | Bin
 .../Content/Raw}/img/pp/l/32.png              | Bin
 .../Content/Raw}/img/pp/l/33.png              | Bin
 .../Content/Raw}/img/pp/l/34.png              | Bin
 .../Content/Raw}/img/pp/l/35.png              | Bin
 .../Content/Raw}/img/pp/l/36.png              | Bin
 .../Content/Raw}/img/pp/l/37.png              | Bin
 .../Content/Raw}/img/pp/l/38.png              | Bin
 .../Content/Raw}/img/pp/l/39.png              | Bin
 .../Content/Raw}/img/pp/l/4.png               | Bin
 .../Content/Raw}/img/pp/l/40.png              | Bin
 .../Content/Raw}/img/pp/l/41.png              | Bin
 .../Content/Raw}/img/pp/l/42.png              | Bin
 .../Content/Raw}/img/pp/l/43.png              | Bin
 .../Content/Raw}/img/pp/l/44.png              | Bin
 .../Content/Raw}/img/pp/l/45.png              | Bin
 .../Content/Raw}/img/pp/l/46.png              | Bin
 .../Content/Raw}/img/pp/l/47.png              | Bin
 .../Content/Raw}/img/pp/l/48.png              | Bin
 .../Content/Raw}/img/pp/l/49.png              | Bin
 .../Content/Raw}/img/pp/l/5.png               | Bin
 .../Content/Raw}/img/pp/l/50.png              | Bin
 .../Content/Raw}/img/pp/l/51.png              | Bin
 .../Content/Raw}/img/pp/l/52.png              | Bin
 .../Content/Raw}/img/pp/l/53.png              | Bin
 .../Content/Raw}/img/pp/l/54.png              | Bin
 .../Content/Raw}/img/pp/l/55.png              | Bin
 .../Content/Raw}/img/pp/l/56.png              | Bin
 .../Content/Raw}/img/pp/l/57.png              | Bin
 .../Content/Raw}/img/pp/l/58.png              | Bin
 .../Content/Raw}/img/pp/l/59.png              | Bin
 .../Content/Raw}/img/pp/l/6.png               | Bin
 .../Content/Raw}/img/pp/l/60.png              | Bin
 .../Content/Raw}/img/pp/l/61.png              | Bin
 .../Content/Raw}/img/pp/l/62.png              | Bin
 .../Content/Raw}/img/pp/l/63.png              | Bin
 .../Content/Raw}/img/pp/l/64.png              | Bin
 .../Content/Raw}/img/pp/l/65.png              | Bin
 .../Content/Raw}/img/pp/l/66.png              | Bin
 .../Content/Raw}/img/pp/l/67.png              | Bin
 .../Content/Raw}/img/pp/l/68.png              | Bin
 .../Content/Raw}/img/pp/l/69.png              | Bin
 .../Content/Raw}/img/pp/l/7.png               | Bin
 .../Content/Raw}/img/pp/l/70.png              | Bin
 .../Content/Raw}/img/pp/l/71.png              | Bin
 .../Content/Raw}/img/pp/l/72.png              | Bin
 .../Content/Raw}/img/pp/l/73.png              | Bin
 .../Content/Raw}/img/pp/l/74.png              | Bin
 .../Content/Raw}/img/pp/l/75.png              | Bin
 .../Content/Raw}/img/pp/l/76.png              | Bin
 .../Content/Raw}/img/pp/l/77.png              | Bin
 .../Content/Raw}/img/pp/l/78.png              | Bin
 .../Content/Raw}/img/pp/l/79.png              | Bin
 .../Content/Raw}/img/pp/l/8.png               | Bin
 .../Content/Raw}/img/pp/l/80.png              | Bin
 .../Content/Raw}/img/pp/l/81.png              | Bin
 .../Content/Raw}/img/pp/l/82.png              | Bin
 .../Content/Raw}/img/pp/l/83.png              | Bin
 .../Content/Raw}/img/pp/l/84.png              | Bin
 .../Content/Raw}/img/pp/l/85.png              | Bin
 .../Content/Raw}/img/pp/l/86.png              | Bin
 .../Content/Raw}/img/pp/l/87.png              | Bin
 .../Content/Raw}/img/pp/l/88.png              | Bin
 .../Content/Raw}/img/pp/l/89.png              | Bin
 .../Content/Raw}/img/pp/l/9.png               | Bin
 .../Content/Raw}/img/pp/l/90.png              | Bin
 .../Content/Raw}/img/pp/l/91.png              | Bin
 .../Content/Raw}/img/pp/l/92.png              | Bin
 .../Content/Raw}/img/pp/l/93.png              | Bin
 .../Content/Raw}/img/pp/l/94.png              | Bin
 .../Content/Raw}/img/pp/l/95.png              | Bin
 .../Content/Raw}/img/pp/l/96.png              | Bin
 .../Content/Raw}/img/pp/l/97.png              | Bin
 .../Content/Raw}/img/pp/l/98.png              | Bin
 .../Content/Raw}/img/pp/l/99.png              | Bin
 .../Content/Raw}/img/pp/s/1.png               | Bin
 .../Content/Raw}/img/pp/s/10.png              | Bin
 .../Content/Raw}/img/pp/s/100.png             | Bin
 .../Content/Raw}/img/pp/s/101.png             | Bin
 .../Content/Raw}/img/pp/s/102.png             | Bin
 .../Content/Raw}/img/pp/s/103.png             | Bin
 .../Content/Raw}/img/pp/s/104.png             | Bin
 .../Content/Raw}/img/pp/s/105.png             | Bin
 .../Content/Raw}/img/pp/s/106.png             | Bin
 .../Content/Raw}/img/pp/s/107.png             | Bin
 .../Content/Raw}/img/pp/s/108.png             | Bin
 .../Content/Raw}/img/pp/s/109.png             | Bin
 .../Content/Raw}/img/pp/s/11.png              | Bin
 .../Content/Raw}/img/pp/s/110.png             | Bin
 .../Content/Raw}/img/pp/s/111.png             | Bin
 .../Content/Raw}/img/pp/s/112.png             | Bin
 .../Content/Raw}/img/pp/s/113.png             | Bin
 .../Content/Raw}/img/pp/s/114.png             | Bin
 .../Content/Raw}/img/pp/s/115.png             | Bin
 .../Content/Raw}/img/pp/s/116.png             | Bin
 .../Content/Raw}/img/pp/s/117.png             | Bin
 .../Content/Raw}/img/pp/s/118.png             | Bin
 .../Content/Raw}/img/pp/s/119.png             | Bin
 .../Content/Raw}/img/pp/s/12.png              | Bin
 .../Content/Raw}/img/pp/s/120.png             | Bin
 .../Content/Raw}/img/pp/s/121.png             | Bin
 .../Content/Raw}/img/pp/s/122.png             | Bin
 .../Content/Raw}/img/pp/s/123.png             | Bin
 .../Content/Raw}/img/pp/s/124.png             | Bin
 .../Content/Raw}/img/pp/s/125.png             | Bin
 .../Content/Raw}/img/pp/s/126.png             | Bin
 .../Content/Raw}/img/pp/s/127.png             | Bin
 .../Content/Raw}/img/pp/s/128.png             | Bin
 .../Content/Raw}/img/pp/s/129.png             | Bin
 .../Content/Raw}/img/pp/s/13.png              | Bin
 .../Content/Raw}/img/pp/s/130.png             | Bin
 .../Content/Raw}/img/pp/s/131.png             | Bin
 .../Content/Raw}/img/pp/s/132.png             | Bin
 .../Content/Raw}/img/pp/s/133.png             | Bin
 .../Content/Raw}/img/pp/s/134.png             | Bin
 .../Content/Raw}/img/pp/s/135.png             | Bin
 .../Content/Raw}/img/pp/s/136.png             | Bin
 .../Content/Raw}/img/pp/s/137.png             | Bin
 .../Content/Raw}/img/pp/s/138.png             | Bin
 .../Content/Raw}/img/pp/s/139.png             | Bin
 .../Content/Raw}/img/pp/s/14.png              | Bin
 .../Content/Raw}/img/pp/s/140.png             | Bin
 .../Content/Raw}/img/pp/s/141.png             | Bin
 .../Content/Raw}/img/pp/s/142.png             | Bin
 .../Content/Raw}/img/pp/s/143.png             | Bin
 .../Content/Raw}/img/pp/s/144.png             | Bin
 .../Content/Raw}/img/pp/s/145.png             | Bin
 .../Content/Raw}/img/pp/s/146.png             | Bin
 .../Content/Raw}/img/pp/s/147.png             | Bin
 .../Content/Raw}/img/pp/s/148.png             | Bin
 .../Content/Raw}/img/pp/s/149.png             | Bin
 .../Content/Raw}/img/pp/s/15.png              | Bin
 .../Content/Raw}/img/pp/s/150.png             | Bin
 .../Content/Raw}/img/pp/s/151.png             | Bin
 .../Content/Raw}/img/pp/s/152.png             | Bin
 .../Content/Raw}/img/pp/s/153.png             | Bin
 .../Content/Raw}/img/pp/s/154.png             | Bin
 .../Content/Raw}/img/pp/s/155.png             | Bin
 .../Content/Raw}/img/pp/s/156.png             | Bin
 .../Content/Raw}/img/pp/s/157.png             | Bin
 .../Content/Raw}/img/pp/s/158.png             | Bin
 .../Content/Raw}/img/pp/s/159.png             | Bin
 .../Content/Raw}/img/pp/s/16.png              | Bin
 .../Content/Raw}/img/pp/s/160.png             | Bin
 .../Content/Raw}/img/pp/s/161.png             | Bin
 .../Content/Raw}/img/pp/s/162.png             | Bin
 .../Content/Raw}/img/pp/s/163.png             | Bin
 .../Content/Raw}/img/pp/s/164.png             | Bin
 .../Content/Raw}/img/pp/s/165.png             | Bin
 .../Content/Raw}/img/pp/s/166.png             | Bin
 .../Content/Raw}/img/pp/s/167.png             | Bin
 .../Content/Raw}/img/pp/s/168.png             | Bin
 .../Content/Raw}/img/pp/s/169.png             | Bin
 .../Content/Raw}/img/pp/s/17.png              | Bin
 .../Content/Raw}/img/pp/s/170.png             | Bin
 .../Content/Raw}/img/pp/s/171.png             | Bin
 .../Content/Raw}/img/pp/s/172.png             | Bin
 .../Content/Raw}/img/pp/s/173.png             | Bin
 .../Content/Raw}/img/pp/s/174.png             | Bin
 .../Content/Raw}/img/pp/s/175.png             | Bin
 .../Content/Raw}/img/pp/s/176.png             | Bin
 .../Content/Raw}/img/pp/s/177.png             | Bin
 .../Content/Raw}/img/pp/s/178.png             | Bin
 .../Content/Raw}/img/pp/s/179.png             | Bin
 .../Content/Raw}/img/pp/s/18.png              | Bin
 .../Content/Raw}/img/pp/s/180.png             | Bin
 .../Content/Raw}/img/pp/s/181.png             | Bin
 .../Content/Raw}/img/pp/s/182.png             | Bin
 .../Content/Raw}/img/pp/s/183.png             | Bin
 .../Content/Raw}/img/pp/s/184.png             | Bin
 .../Content/Raw}/img/pp/s/185.png             | Bin
 .../Content/Raw}/img/pp/s/186.png             | Bin
 .../Content/Raw}/img/pp/s/187.png             | Bin
 .../Content/Raw}/img/pp/s/188.png             | Bin
 .../Content/Raw}/img/pp/s/189.png             | Bin
 .../Content/Raw}/img/pp/s/19.png              | Bin
 .../Content/Raw}/img/pp/s/190.png             | Bin
 .../Content/Raw}/img/pp/s/191.png             | Bin
 .../Content/Raw}/img/pp/s/192.png             | Bin
 .../Content/Raw}/img/pp/s/193.png             | Bin
 .../Content/Raw}/img/pp/s/194.png             | Bin
 .../Content/Raw}/img/pp/s/195.png             | Bin
 .../Content/Raw}/img/pp/s/196.png             | Bin
 .../Content/Raw}/img/pp/s/197.png             | Bin
 .../Content/Raw}/img/pp/s/198.png             | Bin
 .../Content/Raw}/img/pp/s/199.png             | Bin
 .../Content/Raw}/img/pp/s/2.png               | Bin
 .../Content/Raw}/img/pp/s/20.png              | Bin
 .../Content/Raw}/img/pp/s/200.png             | Bin
 .../Content/Raw}/img/pp/s/21.png              | Bin
 .../Content/Raw}/img/pp/s/22.png              | Bin
 .../Content/Raw}/img/pp/s/23.png              | Bin
 .../Content/Raw}/img/pp/s/24.png              | Bin
 .../Content/Raw}/img/pp/s/25.png              | Bin
 .../Content/Raw}/img/pp/s/26.png              | Bin
 .../Content/Raw}/img/pp/s/27.png              | Bin
 .../Content/Raw}/img/pp/s/28.png              | Bin
 .../Content/Raw}/img/pp/s/29.png              | Bin
 .../Content/Raw}/img/pp/s/3.png               | Bin
 .../Content/Raw}/img/pp/s/30.png              | Bin
 .../Content/Raw}/img/pp/s/31.png              | Bin
 .../Content/Raw}/img/pp/s/32.png              | Bin
 .../Content/Raw}/img/pp/s/33.png              | Bin
 .../Content/Raw}/img/pp/s/34.png              | Bin
 .../Content/Raw}/img/pp/s/35.png              | Bin
 .../Content/Raw}/img/pp/s/36.png              | Bin
 .../Content/Raw}/img/pp/s/37.png              | Bin
 .../Content/Raw}/img/pp/s/38.png              | Bin
 .../Content/Raw}/img/pp/s/39.png              | Bin
 .../Content/Raw}/img/pp/s/4.png               | Bin
 .../Content/Raw}/img/pp/s/40.png              | Bin
 .../Content/Raw}/img/pp/s/41.png              | Bin
 .../Content/Raw}/img/pp/s/42.png              | Bin
 .../Content/Raw}/img/pp/s/43.png              | Bin
 .../Content/Raw}/img/pp/s/44.png              | Bin
 .../Content/Raw}/img/pp/s/45.png              | Bin
 .../Content/Raw}/img/pp/s/46.png              | Bin
 .../Content/Raw}/img/pp/s/47.png              | Bin
 .../Content/Raw}/img/pp/s/48.png              | Bin
 .../Content/Raw}/img/pp/s/49.png              | Bin
 .../Content/Raw}/img/pp/s/5.png               | Bin
 .../Content/Raw}/img/pp/s/50.png              | Bin
 .../Content/Raw}/img/pp/s/51.png              | Bin
 .../Content/Raw}/img/pp/s/52.png              | Bin
 .../Content/Raw}/img/pp/s/53.png              | Bin
 .../Content/Raw}/img/pp/s/54.png              | Bin
 .../Content/Raw}/img/pp/s/55.png              | Bin
 .../Content/Raw}/img/pp/s/56.png              | Bin
 .../Content/Raw}/img/pp/s/57.png              | Bin
 .../Content/Raw}/img/pp/s/58.png              | Bin
 .../Content/Raw}/img/pp/s/59.png              | Bin
 .../Content/Raw}/img/pp/s/6.png               | Bin
 .../Content/Raw}/img/pp/s/60.png              | Bin
 .../Content/Raw}/img/pp/s/61.png              | Bin
 .../Content/Raw}/img/pp/s/62.png              | Bin
 .../Content/Raw}/img/pp/s/63.png              | Bin
 .../Content/Raw}/img/pp/s/64.png              | Bin
 .../Content/Raw}/img/pp/s/65.png              | Bin
 .../Content/Raw}/img/pp/s/66.png              | Bin
 .../Content/Raw}/img/pp/s/67.png              | Bin
 .../Content/Raw}/img/pp/s/68.png              | Bin
 .../Content/Raw}/img/pp/s/69.png              | Bin
 .../Content/Raw}/img/pp/s/7.png               | Bin
 .../Content/Raw}/img/pp/s/70.png              | Bin
 .../Content/Raw}/img/pp/s/71.png              | Bin
 .../Content/Raw}/img/pp/s/72.png              | Bin
 .../Content/Raw}/img/pp/s/73.png              | Bin
 .../Content/Raw}/img/pp/s/74.png              | Bin
 .../Content/Raw}/img/pp/s/75.png              | Bin
 .../Content/Raw}/img/pp/s/76.png              | Bin
 .../Content/Raw}/img/pp/s/77.png              | Bin
 .../Content/Raw}/img/pp/s/78.png              | Bin
 .../Content/Raw}/img/pp/s/79.png              | Bin
 .../Content/Raw}/img/pp/s/8.png               | Bin
 .../Content/Raw}/img/pp/s/80.png              | Bin
 .../Content/Raw}/img/pp/s/81.png              | Bin
 .../Content/Raw}/img/pp/s/82.png              | Bin
 .../Content/Raw}/img/pp/s/83.png              | Bin
 .../Content/Raw}/img/pp/s/84.png              | Bin
 .../Content/Raw}/img/pp/s/85.png              | Bin
 .../Content/Raw}/img/pp/s/86.png              | Bin
 .../Content/Raw}/img/pp/s/87.png              | Bin
 .../Content/Raw}/img/pp/s/88.png              | Bin
 .../Content/Raw}/img/pp/s/89.png              | Bin
 .../Content/Raw}/img/pp/s/9.png               | Bin
 .../Content/Raw}/img/pp/s/90.png              | Bin
 .../Content/Raw}/img/pp/s/91.png              | Bin
 .../Content/Raw}/img/pp/s/92.png              | Bin
 .../Content/Raw}/img/pp/s/93.png              | Bin
 .../Content/Raw}/img/pp/s/94.png              | Bin
 .../Content/Raw}/img/pp/s/95.png              | Bin
 .../Content/Raw}/img/pp/s/96.png              | Bin
 .../Content/Raw}/img/pp/s/97.png              | Bin
 .../Content/Raw}/img/pp/s/98.png              | Bin
 .../Content/Raw}/img/pp/s/99.png              | Bin
 .../Content/Raw}/js/jquery-1.4.2.min.js       |   0
 .../Content/Raw}/js/main.js                   |   0
 .../Content/Raw}/pjirc/IRCApplet.class        | Bin
 .../Content/Raw}/pjirc/background.gif         | Bin
 .../Content/Raw}/pjirc/english.lng            |   0
 .../Content/Raw}/pjirc/french.lng             |   0
 .../Content/Raw}/pjirc/irc.cab                | Bin
 .../Content/Raw}/pjirc/irc.jar                | Bin
 .../Content/Raw}/pjirc/pixx-english.lng       |   0
 .../Content/Raw}/pjirc/pixx-french.lng        |   0
 .../Content/Raw}/pjirc/pixx.cab               | Bin
 .../Content/Raw}/pjirc/pixx.jar               | Bin
 .../Content/Raw}/pjirc/securedirc.cab         | Bin
 legacyworlds-web-main/pom.xml                 |  45 +++
 .../com/deepclone/lw/web/main/BannedPage.java |   0
 .../deepclone/lw/web/main/CommonPages.java    |   0
 .../lw/web/main/ErrorHandlerBean.java         |   0
 .../deepclone/lw/web/main/ExternalPages.java  |   0
 .../com/deepclone/lw/web/main/LoginPages.java |   0
 .../deepclone/lw/web/main/LogoutPages.java    |   0
 .../lw/web/main/PasswordRecoveryPages.java    |   0
 .../lw/web/main/PlayerSessionRedirector.java  |   0
 .../lw/web/main/ReactivationPages.java        |   0
 .../lw/web/main/RegistrationPages.java        |   0
 .../lw/web/main/ValidationPages.java          |   0
 .../lw/web/main/game/AccountPage.java         |   0
 .../lw/web/main/game/AlliancePage.java        |   0
 .../lw/web/main/game/BattlePages.java         |   0
 .../lw/web/main/game/BugTrackerPages.java     |   0
 .../deepclone/lw/web/main/game/ChatPage.java  |   0
 .../lw/web/main/game/EnemiesPage.java         |   0
 .../lw/web/main/game/FleetsPage.java          |   0
 .../lw/web/main/game/GetPlanetPage.java       |   0
 .../deepclone/lw/web/main/game/MapPage.java   |   0
 .../lw/web/main/game/MessageBoxView.java      |   0
 .../lw/web/main/game/MessagePages.java        |   0
 .../lw/web/main/game/OverviewPage.java        |   0
 .../lw/web/main/game/PlanetListPage.java      |   0
 .../lw/web/main/game/PlanetPage.java          |   0
 .../src/main/resources/log4j.properties       |   0
 legacyworlds-web-main/src/test/java/.empty    |   0
 .../src/test/resources/.empty                 |   0
 legacyworlds-web/.project                     |  17 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../legacyworlds-web-admin/.classpath         |  25 --
 .../legacyworlds-web-admin/.project           |  37 --
 .../.settings/.jsdtscope                      |  11 -
 .../.settings/org.eclipse.jdt.core.prefs      |   8 -
 .../org.eclipse.wst.common.component          |  12 -
 ....eclipse.wst.common.project.facet.core.xml |   6 -
 ...rg.eclipse.wst.jsdt.ui.superType.container |   1 -
 .../org.eclipse.wst.jsdt.ui.superType.name    |   1 -
 .../org.eclipse.wst.ws.service.policy.prefs   |   3 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../WebContent/WEB-INF/fm/version.ftl         |   2 -
 .../legacyworlds-web-admin/pom.xml            |  95 -----
 .../legacyworlds-web-beans/.classpath         |  10 -
 .../legacyworlds-web-beans/.project           |  23 --
 .../.settings/org.eclipse.jdt.core.prefs      |   6 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../legacyworlds-web-main/.classpath          |  21 --
 .../legacyworlds-web-main/.project            |  37 --
 .../.settings/.jsdtscope                      |  11 -
 .../.settings/org.eclipse.jdt.core.prefs      |   8 -
 .../org.eclipse.wst.common.component          |  12 -
 ....eclipse.wst.common.project.facet.core.xml |   6 -
 ...rg.eclipse.wst.jsdt.ui.superType.container |   1 -
 .../org.eclipse.wst.jsdt.ui.superType.name    |   1 -
 .../.settings/org.maven.ide.eclipse.prefs     |   9 -
 .../WebContent/WEB-INF/fm/version.ftl         |   2 -
 .../legacyworlds-web-main/pom.xml             |  89 -----
 legacyworlds-web/pom.xml                      |  57 ++-
 legacyworlds/.gitignore                       |   1 +
 .../build-tools}/execute-clit.sh              |   0
 .../build-tools}/server-config-example.sh     |   0
 .../build-tools}/start-server.sh              |   0
 .../build-tools}/stop-server.sh               |   0
 legacyworlds/doc/Eclipse-import.txt           |  58 +++
 legacyworlds/doc/TODO.txt                     |  48 +++
 legacyworlds/doc/local-deployment.txt         |  86 +++++
 legacyworlds/eclipse-code-cleanup.xml         |  56 +++
 .../eclipse-code-format.xml                   |   0
 legacyworlds/pom.xml                          | 344 ++++++++++++++++++
 pom.xml                                       |  72 ----
 runsrv.sh                                     |   4 -
 runtool.sh                                    |   4 -
 1439 files changed, 1020 insertions(+), 1649 deletions(-)
 create mode 100644 .gitignore
 delete mode 100644 .project
 delete mode 100755 build-tools/BUILD.sh
 delete mode 100755 build-tools/deploy.sh
 create mode 100644 legacyworlds-server-DIST/pom.xml
 create mode 100644 legacyworlds-server-DIST/src/server.xml
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/pom.xml (67%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/AccountManagementBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/admin/AdminRecapBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/admin/AdminRecapTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/admin/AdministrationBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/admin/BansProcessorBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/admin/BansProcessorTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/admin/IpBanBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/BoolPreferenceType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/DefaultPreferencesBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/EnumPreferenceType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/IntPreferenceType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypeImpl.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypesRegistryBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/java/com/deepclone/lw/beans/prefs/StringPreferenceType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/account-cleanup-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/account-management-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/admin-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/admin-recap-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/administration-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/bans-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/bans-processor-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/default-preferences-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/ip-ban-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/preference-definitions-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/preferences-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/quit-processor-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/requests-expiration-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/user-session-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/users-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts => legacyworlds-server-beans-accounts}/src/main/resources/configuration/accounts/vacation-processor-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-accounts/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-accounts/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/pom.xml (65%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/AdminBugsBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/PlayerBugsBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/AccountInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/AllianceInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/BuildingsInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/EmpireInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/FleetInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/MovementInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/PlanetInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueItemInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/ShipsInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/java/com/deepclone/lw/beans/bt/esdata/SystemInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/resources/configuration/bt-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/resources/configuration/bt/admin-bugs-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/resources/configuration/bt/bugs-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/resources/configuration/bt/empire-summary-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt => legacyworlds-server-beans-bt}/src/main/resources/configuration/bt/player-bugs-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-bt/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-bt/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/pom.xml (68%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/EntryQueueItem.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/LoggerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/java/com/deepclone/lw/beans/eventlog/SystemLoggerImpl.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/resources/configuration/eventlog-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/resources/configuration/eventlog/log-cleaner-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/resources/configuration/eventlog/log-reader-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/resources/configuration/eventlog/log-writer-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog => legacyworlds-server-beans-eventlog}/src/main/resources/configuration/eventlog/logger-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-eventlog/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-eventlog/src/test/resources/.empty
 create mode 100644 legacyworlds-server-beans-i18n/pom.xml
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/java/com/deepclone/lw/beans/i18n/I18NAdministrationImpl.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/java/com/deepclone/lw/beans/i18n/I18NManagerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/java/com/deepclone/lw/beans/i18n/LanguageStore.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/resources/configuration/i18n-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/resources/configuration/i18n/i18n-manager-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n => legacyworlds-server-beans-i18n}/src/main/resources/configuration/i18n/i18n-translator-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-i18n/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-i18n/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/pom.xml (59%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/java/com/deepclone/lw/beans/mailer/MailDataImpl.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/java/com/deepclone/lw/beans/mailer/MailQueueHandler.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/java/com/deepclone/lw/beans/mailer/MailQueueItem.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTerminator.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/java/com/deepclone/lw/beans/mailer/MailerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/java/com/deepclone/lw/beans/mailer/QueuedMail.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/resources/configuration/mailer-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer => legacyworlds-server-beans-mailer}/src/main/resources/configuration/mailer/mailer-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-mailer/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-mailer/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming => legacyworlds-server-beans-naming}/pom.xml (66%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming => legacyworlds-server-beans-naming}/src/main/java/com/deepclone/lw/beans/naming/NamesManagerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming => legacyworlds-server-beans-naming}/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming => legacyworlds-server-beans-naming}/src/main/resources/configuration/naming-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming => legacyworlds-server-beans-naming}/src/main/resources/configuration/naming/names-manager-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming => legacyworlds-server-beans-naming}/src/main/resources/configuration/naming/naming-dao-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-naming/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-naming/src/test/resources/.empty
 create mode 100644 legacyworlds-server-beans-simple/pom.xml
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/empire/AllianceManagementBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/empire/PlanetListMapper.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/fleets/BattleViewerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/fleets/BattlesCacheBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/fleets/FleetManagementBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/map/MapViewerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/AdminMessagesBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/EmpireMessagesBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/MailTaskBase.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/MessageCleanerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/MessageContentCacheBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatRegistryBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatWiringBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/MessageTasksBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/NotificationsTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/RecapitulationTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/AbandonMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageExtractor.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceDisbandedMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceMessageExtractor.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/BattleMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/BugMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/DebtMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/ExternalMessageFormatBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageExtractor.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/KickedMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeadershipMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeftAllianceMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/LostPlanetMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/PendingRequestMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/PlanetMessageExtractor.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/QueueMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/RequestResultMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/StrikeMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/TakenPlanetMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/msgs/fmt/TechMessageFormatterBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/alliance-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/alliance-management-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/battle-data-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/empire-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/empire-management-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/fleet-management-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/fleets-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/game-update-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/map-viewer-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/message-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/planet-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/planets-management-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/universe-dao-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/universe-generator-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple => legacyworlds-server-beans-simple}/src/main/resources/configuration/simple/updates-dao-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-simple/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-simple/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/pom.xml (51%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/ConstantsAdministrationImpl.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/ServerSessionData.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/SessionManagerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/TickerTask.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/TickerTaskStatusHandler.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/java/com/deepclone/lw/beans/sys/TickerThread.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/resources/configuration/system-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/resources/configuration/system/constants-manager-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/resources/configuration/system/constants-registrar-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/resources/configuration/system/session-manager-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/resources/configuration/system/system-status-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system => legacyworlds-server-beans-system}/src/main/resources/configuration/system/ticker-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-system/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-system/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/pom.xml (63%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredCommandDelegate.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredSubTypeDelegate.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandDelegate.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandHandler.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandWiringBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeDelegate.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeWiringBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/abst/StatefulSessionTypeDefiner.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionDefinerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionSubType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/common/AdminOperation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/common/CommonCommandsBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/common/CreateAuthChallengeCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/common/NoOperationCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/common/SetPasswordCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminCommandsBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminOverviewCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/BansSummaryCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ConfirmBanCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/LiftBanCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ListBansCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RejectBanCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RequestBanCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/BugsSummaryCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/GetSnapshotCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ListBugsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/MergeReportsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ModerateCommentCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/PostCommentCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportBugCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportStatusCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportVisibilityCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ValidateReportCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ViewBugCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/GetConstantsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/SetConstantCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ChangeLanguageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/GetLanguageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/SetStringCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ViewLanguagesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/GetEntryCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/ViewLogsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EnableMaintenanceCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EndMaintenanceCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/ExtendMaintenanceCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/MaintenanceStatusCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ComposeMessageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/GetMessagesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/MessageBoxCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/PrepareMessageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ReadMessageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/SendSpamCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/names/GetNamesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesActionCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesSummaryCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/GetPrefDefaultsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/SetPrefDefaultCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ListAdministratorsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ResetAdminPasswordCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SUExistingOperation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SetPrivilegesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SuperUserOperation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ViewAdministratorCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/SetTaskStatusCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/TickerStatusCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/ToggleTickerCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/users/GiveCreditsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListAccountsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListSessionsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ViewAccountCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/ext/AccountCreationCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/ext/ConfPwdRecoveryCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/ext/ExternalSessionDefinerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/ext/LanguageListRequired.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/ext/ListLanguagesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/ext/ReqPwdRecoveryCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/BanDetailsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/BannedSubTypeBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/DisabledSubTypeBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/GameSubTypeBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/GetLanguageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/PlayerCommonCommandsBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionDefinerBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionSubType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/ReactivateCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/ValidationSubTypeBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/CancelQuitCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/CreateAuthChallengeCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/GetAccountCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/QuitGameCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/SetAddressCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/SetLanguageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/SetPasswordCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/SetPreferencesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/ToggleVacationCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/account/ValidateSetAddressCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/bt/ListBugsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/bt/PostCommentCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/bt/ReportBugCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/bt/ViewBugCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/GetBattleCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/ListBattlesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/ListPlanetsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/OverviewCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/ViewMapCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/AllianceStatusCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CancelJoinCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/JoinAllianceCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/KickMembersCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/LeaveAllianceCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ManageRequestsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/TransferLeadershipCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ViewAllianceCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/elist/AddEnemyCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/elist/EnemyListCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/elist/RemoveEnemiesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/DisbandFleetsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MergeFleetsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MoveFleetsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SetFleetsModeCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/ViewFleetsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/planets/AbandonPlanetCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/planets/BuildingActionCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/planets/FlushQueueCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ShipBuildingCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ViewPlanetCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/msgs/ComposeMessageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/msgs/GetMessagesCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/msgs/ListTargetsCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/msgs/MessageBoxCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/msgs/PrepareMessageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/java/com/deepclone/lw/beans/user/player/msgs/ReadMessageCommandDelegateBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/resources/configuration/user-beans.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/resources/configuration/user/admin-session-definer-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/resources/configuration/user/external-session-definer-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/resources/configuration/user/object-name-validator-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/resources/configuration/user/player-session-definer-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/resources/configuration/user/session-command-wiring-bean.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user => legacyworlds-server-beans-user}/src/main/resources/configuration/user/session-subtype-wiring-bean.xml (100%)
 create mode 100644 legacyworlds-server-beans-user/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-user/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-beans => legacyworlds-server-beans}/pom.xml (53%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/database.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/db-config.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/000-schemas.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/010-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/020-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/030-updates.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/000-typedefs.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/010-i18n-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/020-prefs-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/030-users-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/035-session-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/040-admin-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/050-accounts-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/055-bugs-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/060-naming-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/070-constants-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/080-techs-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/090-buildables-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/100-universe-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/110-empires-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/120-construction-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/130-fleets-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/140-status-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/150-logs-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/160-battle-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/170-events-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/data/180-messages-data.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/000-defs-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/002-sys-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/005-logs-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/010-constants-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/020-naming-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/030-tech-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/035-users-view.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/040-empire-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/050-computation-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/060-universe-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/070-users-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/075-session-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/080-buildings-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/100-status-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/110-prefs-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/120-map-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/140-planets-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/150-battle-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/160-battle-views.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/163-alliance-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/165-fleets-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/167-planet-list.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/170-event-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/180-messages-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/190-admin-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/200-bugs-functions.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/functions/210-admin-overview.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/000-updates-ctrl.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/010-empire-money.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/020-empire-research.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/025-empire-debt.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/030-fleet-arrivals.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/040-fleet-movements.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/050-fleet-status.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/060-planet-battle.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/070-planet-abandon.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/080-planet-construction.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/090-planet-military.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/100-planet-population.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/db-structure/parts/updates/110-planet-money.sql (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/pom.xml (71%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/accounts/Account.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/accounts/AccountOperationResult.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/accounts/QuittingAccount.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/accounts/ValidationResult.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/admin/AdminConnection.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/admin/AdminRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/AllianceMembership.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/MapData.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/RawFleetOwner.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/RawFleetShip.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/RawStaticFleet.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/battle/BattleListRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/battle/BuildingHistoryRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/battle/EmpireBattleRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/battle/EventItemRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/battle/EventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/battle/PresenceRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/battle/ProtagonistRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/game/battle/ShipHistoryRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/i18n/Translation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/AdminEventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/AllianceEventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/BugEventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/EmpireEventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/EventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/EventType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/EventTypeRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventFleet.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/FormatType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/InboxRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/MessageDataRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/NotificationsRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/PlanetEventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventLocation.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/msgs/TextMessageRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/sys/Constant.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/sys/ExceptionLog.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/sys/StackTraceLog.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/sys/Status.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/sys/SystemLogEntry.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/java/com/deepclone/lw/sqld/sys/TickerTaskRecord.java (100%)
 rename {legacyworlds-server/legacyworlds-server-data => legacyworlds-server-data}/src/main/resources/configuration/transaction-bean.xml (100%)
 create mode 100644 legacyworlds-server-data/src/test/java/.empty
 create mode 100644 legacyworlds-server-data/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/pom.xml (74%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/AccountMailException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/AccountManagement.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/AccountSession.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/EmailChangeException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/JoinGameException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/PasswordProhibitedException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/PasswordRecoveryException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/PermanentlyDisabledException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/UserSessionDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/acm/UsersDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAOException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/admin/Administration.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/admin/BanMailData.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/admin/BansDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/admin/IpBan.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/bt/AdminBugs.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/bt/BugsDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/bt/PlayerBugs.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/eventlog/ExtendedLogEntry.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/eventlog/LogReader.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/eventlog/LogWriter.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/eventlog/Logger.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/eventlog/SystemLogger.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/AllianceDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/AllianceManagement.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/BattleViewer.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/BattlesCache.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/BattlesDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/FleetManagement.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/FleetsDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/MapViewParameters.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/MapViewer.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/PlanetDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/PlanetsManagement.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/UniverseDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateLanguageException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateStringException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/I18NAdministration.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/I18NException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/I18NManager.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/InvalidUpdateException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/TranslationException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownLanguageException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownStringException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/mailer/AlreadySentException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/mailer/MailData.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/mailer/Mailer.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/mailer/MailerException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/mailer/MissingDataException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/mailer/NotSentException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/AdminMessages.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/EmpireMessages.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/MessageBoxDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/MessageContentCache.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/MessageExtractor.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatRegistry.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatter.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/MessageRecordsDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/msg/NotificationsDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/naming/EmpireNameException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/naming/MapNameException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/naming/NamesManager.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/naming/NamingDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/prefs/AccountPreferences.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/prefs/Preference.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitionException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitions.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceGroup.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceType.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceTypesRegistry.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/prefs/PreferencesDAO.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/session/ServerSession.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/session/SessionManager.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/session/SessionTypeDefiner.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/ConstantDefinition.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsAdministration.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsUser.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/EndAutowiredTransaction.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/InvalidConstantValue.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceData.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceStatusException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/TickStatusException.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/Ticker.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/TickerManager.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/UnknownConstantError.java (100%)
 rename {legacyworlds-server/legacyworlds-server-interfaces => legacyworlds-server-interfaces}/src/main/java/com/deepclone/lw/interfaces/sys/WiringException.java (100%)
 create mode 100644 legacyworlds-server-interfaces/src/main/resources/.empty
 create mode 100644 legacyworlds-server-interfaces/src/test/java/.empty
 create mode 100644 legacyworlds-server-interfaces/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data-source.xml (87%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/addressChangeMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/addressChangeMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/adminErrorMail.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/adminRecapMail.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/banLiftedMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/banLiftedMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/bannedMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/bannedMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/buildables-test.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/buildables.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/buildables.xsd (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/i18n-text.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/i18n-text.xsd (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/inactivityQuitMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/inactivityQuitMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/inactivityWarningMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/inactivityWarningMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/messageMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/messageMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/passwordRecoveryMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/passwordRecoveryMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/quitMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/quitMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/reactivationMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/reactivationMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/recapMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/recapMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/registrationMail-en.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/registrationMail-fr.txt (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/techs-test.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/techs.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/data/techs.xsd (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/pom.xml (65%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/Main.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/CLITool.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/CreateUser.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/ExportDB.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/ImportBuildables.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/ImportTechs.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/ImportText.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/Stop.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/Tick.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/ToolBase.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/Administrator.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BREComment.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BREInitialReport.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BREMerger.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BREStatusChange.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BREVisibilityChange.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BugEventMapper.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BugGroup.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BugReportEvent.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BugReports.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/BugSubmitter.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/LegacyWorldsDB.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/User.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/UserMapper.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/cli/dbexport/Warnings.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/srv/LogAppender.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/srv/Server.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/srv/ServerTerminator.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/java/com/deepclone/lw/srv/ServerTerminatorBean.java (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/resources/configuration/context-configuration.xml (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/resources/log4j.properties (100%)
 rename {legacyworlds-server/legacyworlds-server-main => legacyworlds-server-main}/src/main/resources/lw-server.xml (100%)
 create mode 100644 legacyworlds-server-main/src/test/java/.empty
 create mode 100644 legacyworlds-server-main/src/test/resources/.empty
 rename {legacyworlds-server/legacyworlds-server-tests => legacyworlds-server-tests}/pom.xml (76%)
 rename {legacyworlds-server/legacyworlds-server-utils => legacyworlds-server-utils}/pom.xml (71%)
 rename {legacyworlds-server/legacyworlds-server-utils => legacyworlds-server-utils}/src/main/java/com/deepclone/lw/utils/Base64Serializer.java (100%)
 rename {legacyworlds-server/legacyworlds-server-utils => legacyworlds-server-utils}/src/main/java/com/deepclone/lw/utils/EmailAddress.java (100%)
 rename {legacyworlds-server/legacyworlds-server-utils => legacyworlds-server-utils}/src/main/java/com/deepclone/lw/utils/Password.java (100%)
 rename {legacyworlds-server/legacyworlds-server-utils => legacyworlds-server-utils}/src/main/java/com/deepclone/lw/utils/RandomStringGenerator.java (100%)
 rename {legacyworlds-server/legacyworlds-server-utils => legacyworlds-server-utils}/src/main/java/com/deepclone/lw/utils/StoredProc.java (100%)
 create mode 100644 legacyworlds-server-utils/src/main/resources/.empty
 create mode 100644 legacyworlds-server-utils/src/test/java/.empty
 create mode 100644 legacyworlds-server-utils/src/test/resources/.empty
 delete mode 100644 legacyworlds-server/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/pom.xml
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/pom.xml
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-data/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-data/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-data/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-data/db-structure/test-mode.sql
 delete mode 100644 legacyworlds-server/legacyworlds-server-interfaces/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-interfaces/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-interfaces/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-main/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-main/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-main/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-main/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-main/hibernate.xml
 delete mode 100644 legacyworlds-server/legacyworlds-server-tests/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-tests/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-tests/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-server/legacyworlds-server-utils/.classpath
 delete mode 100644 legacyworlds-server/legacyworlds-server-utils/.project
 delete mode 100644 legacyworlds-server/legacyworlds-server-utils/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-session/.classpath
 delete mode 100644 legacyworlds-session/.project
 delete mode 100644 legacyworlds-session/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-session/.settings/org.maven.ide.eclipse.prefs
 create mode 100644 legacyworlds-session/src/main/resources/.empty
 create mode 100644 legacyworlds-session/src/test/java/.empty
 create mode 100644 legacyworlds-session/src/test/resources/.empty
 delete mode 100644 legacyworlds-utils/.classpath
 delete mode 100644 legacyworlds-utils/.project
 delete mode 100644 legacyworlds-utils/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-utils/.settings/org.maven.ide.eclipse.prefs
 create mode 100644 legacyworlds-utils/src/main/resources/.empty
 create mode 100644 legacyworlds-utils/src/test/java/.empty
 create mode 100644 legacyworlds-utils/src/test/resources/.empty
 create mode 100644 legacyworlds-web-admin/Content/Filtered/fm/version.ftl
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/META-INF/MANIFEST.MF (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/admin-servlet.xml (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/ROOT.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/containers/external.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/containers/internal.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/layout/columns.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/layout/datatable.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/layout/fields.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/layout/form.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/layout/lists.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/layout/tabs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/addAdmin.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/admins.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/banReject.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/banRequest.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/bans.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/bansSummary.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/bugsList.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/bugsReport.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/bugsSummary.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/bugsTabs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/bugsView.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/changePassword.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/constants.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/language.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/languages.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/logEntry.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/login.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/logs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/main.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/maintenance.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/message.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/messageBox.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/messageTabs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/messageWriter.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/names.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/namesSummary.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/offline.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/preferences.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/resetAdmin.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/spam.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/ticker.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/user.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/userSessions.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/users.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/fm/types/viewAdmin.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/WEB-INF/web.xml (100%)
 rename {legacyworlds-web/legacyworlds-web-admin/WebContent => legacyworlds-web-admin/Content/Raw}/css/main.css (100%)
 create mode 100644 legacyworlds-web-admin/pom.xml
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/BanhammerPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/BugTrackerPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/ConstantsPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/ErrorHandlerBean.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/I18NPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/LogPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/LoginPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/MaintenancePages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/MessageBoxView.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/MessagesPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/NamesPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/PasswordPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/PreferencesPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/SessionPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/SpamPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/SuperUserPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/TickerPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/UsersPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/i18ne/LanguageExport.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/java/com/deepclone/lw/web/admin/i18ne/StringExport.java (100%)
 rename {legacyworlds-web/legacyworlds-web-admin => legacyworlds-web-admin}/src/main/resources/log4j.properties (100%)
 create mode 100644 legacyworlds-web-admin/src/test/java/.empty
 create mode 100644 legacyworlds-web-admin/src/test/resources/.empty
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/pom.xml (72%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/intercept/IEContentTypeBean.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/intercept/LanguageInterceptorBean.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/intercept/SessionInterceptorBean.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/intercept/SessionRequirement.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatter.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatterBean.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/ClientSessionReference.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatus.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatusBean.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/Session.java (97%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/SessionClient.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/SessionClientBean.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/SessionMaintenanceException.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/SessionServerException.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/session/SessionType.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/view/BugTrackerBase.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/view/Page.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/beans/view/PageControllerBase.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/csess/AdminSession.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/csess/ExternalSession.java (100%)
 rename {legacyworlds-web/legacyworlds-web-beans => legacyworlds-web-beans}/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java (100%)
 create mode 100644 legacyworlds-web-beans/src/main/resources/.empty
 create mode 100644 legacyworlds-web-beans/src/test/java/.empty
 create mode 100644 legacyworlds-web-beans/src/test/resources/.empty
 create mode 100644 legacyworlds-web-main/Content/Filtered/fm/version.ftl
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/META-INF/MANIFEST.MF (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/ROOT.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/containers/chat.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/containers/external.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/containers/game.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/containers/offline.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/containers/restricted.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/game.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/static/home.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/static/loggedOut.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/static/loginFailed.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/static/noSession.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/static/passwordRecoveryOk.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/static/reactivate.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/static/rules.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/static/scope.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/account.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/alliance.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/banned.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/battle.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/battles.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/bugsList.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/bugsReport.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/bugsTabs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/bugsView.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/chat.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/enemies.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/fleets.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/fleetsCommand.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/getNewPlanet.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/maintenance.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/map.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/message.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/messageBox.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/messageTabs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/messageTargets.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/messageWriter.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/offline.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/overview.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/passwordRecovery.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/planet.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/planets.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/reactivation.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/register.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/registered.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/splitFleet.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/static.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/en/types/validation.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/containers/chat.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/containers/external.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/containers/game.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/containers/offline.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/containers/restricted.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/game.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/static/home.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/static/loggedOut.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/static/loginFailed.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/static/noSession.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/static/passwordRecoveryOk.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/static/reactivate.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/static/rules.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/static/scope.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/account.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/alliance.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/banned.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/battle.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/battles.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/bugsList.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/bugsReport.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/bugsTabs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/bugsView.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/chat.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/enemies.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/fleets.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/fleetsCommand.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/getNewPlanet.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/maintenance.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/map.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/message.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/messageBox.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/messageTabs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/messageTargets.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/messageWriter.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/offline.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/overview.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/passwordRecovery.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/planet.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/planets.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/reactivation.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/register.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/registered.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/splitFleet.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/static.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/fr/types/validation.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/layout/columns.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/layout/datatable.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/layout/fields.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/layout/form.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/layout/happiness.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/layout/lists.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/fm/layout/tabs.ftl (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/main-servlet.xml (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/WEB-INF/web.xml (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/css/main.css (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/background.jpg (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/button-0.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/button-1.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/button-2.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/button-3.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/button-4.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/button-5.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/button-6.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/1.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/10.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/100.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/101.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/102.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/103.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/104.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/105.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/106.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/107.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/108.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/109.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/11.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/110.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/111.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/112.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/113.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/114.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/115.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/116.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/117.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/118.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/119.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/12.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/120.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/121.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/122.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/123.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/124.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/125.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/126.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/127.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/128.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/129.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/13.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/130.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/131.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/132.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/133.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/134.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/135.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/136.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/137.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/138.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/139.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/14.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/140.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/141.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/142.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/143.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/144.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/145.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/146.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/147.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/148.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/149.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/15.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/150.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/151.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/152.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/153.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/154.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/155.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/156.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/157.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/158.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/159.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/16.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/160.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/161.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/162.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/163.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/164.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/165.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/166.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/167.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/168.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/169.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/17.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/170.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/171.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/172.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/173.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/174.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/175.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/176.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/177.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/178.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/179.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/18.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/180.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/181.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/182.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/183.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/184.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/185.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/186.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/187.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/188.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/189.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/19.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/190.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/191.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/192.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/193.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/194.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/195.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/196.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/197.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/198.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/199.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/2.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/20.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/200.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/21.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/22.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/23.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/24.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/25.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/26.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/27.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/28.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/29.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/3.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/30.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/31.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/32.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/33.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/34.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/35.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/36.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/37.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/38.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/39.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/4.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/40.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/41.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/42.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/43.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/44.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/45.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/46.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/47.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/48.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/49.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/5.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/50.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/51.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/52.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/53.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/54.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/55.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/56.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/57.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/58.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/59.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/6.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/60.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/61.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/62.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/63.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/64.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/65.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/66.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/67.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/68.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/69.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/7.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/70.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/71.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/72.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/73.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/74.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/75.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/76.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/77.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/78.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/79.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/8.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/80.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/81.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/82.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/83.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/84.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/85.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/86.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/87.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/88.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/89.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/9.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/90.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/91.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/92.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/93.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/94.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/95.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/96.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/97.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/98.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/l/99.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/1.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/10.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/100.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/101.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/102.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/103.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/104.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/105.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/106.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/107.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/108.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/109.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/11.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/110.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/111.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/112.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/113.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/114.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/115.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/116.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/117.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/118.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/119.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/12.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/120.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/121.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/122.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/123.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/124.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/125.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/126.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/127.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/128.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/129.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/13.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/130.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/131.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/132.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/133.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/134.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/135.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/136.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/137.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/138.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/139.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/14.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/140.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/141.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/142.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/143.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/144.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/145.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/146.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/147.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/148.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/149.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/15.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/150.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/151.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/152.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/153.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/154.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/155.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/156.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/157.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/158.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/159.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/16.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/160.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/161.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/162.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/163.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/164.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/165.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/166.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/167.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/168.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/169.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/17.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/170.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/171.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/172.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/173.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/174.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/175.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/176.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/177.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/178.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/179.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/18.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/180.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/181.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/182.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/183.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/184.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/185.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/186.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/187.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/188.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/189.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/19.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/190.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/191.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/192.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/193.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/194.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/195.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/196.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/197.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/198.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/199.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/2.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/20.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/200.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/21.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/22.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/23.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/24.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/25.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/26.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/27.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/28.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/29.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/3.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/30.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/31.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/32.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/33.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/34.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/35.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/36.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/37.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/38.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/39.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/4.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/40.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/41.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/42.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/43.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/44.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/45.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/46.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/47.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/48.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/49.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/5.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/50.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/51.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/52.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/53.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/54.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/55.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/56.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/57.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/58.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/59.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/6.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/60.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/61.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/62.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/63.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/64.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/65.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/66.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/67.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/68.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/69.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/7.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/70.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/71.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/72.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/73.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/74.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/75.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/76.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/77.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/78.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/79.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/8.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/80.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/81.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/82.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/83.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/84.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/85.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/86.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/87.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/88.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/89.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/9.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/90.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/91.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/92.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/93.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/94.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/95.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/96.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/97.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/98.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/img/pp/s/99.png (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/js/jquery-1.4.2.min.js (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/js/main.js (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/IRCApplet.class (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/background.gif (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/english.lng (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/french.lng (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/irc.cab (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/irc.jar (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/pixx-english.lng (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/pixx-french.lng (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/pixx.cab (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/pixx.jar (100%)
 rename {legacyworlds-web/legacyworlds-web-main/WebContent => legacyworlds-web-main/Content/Raw}/pjirc/securedirc.cab (100%)
 create mode 100644 legacyworlds-web-main/pom.xml
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/BannedPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/CommonPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/ErrorHandlerBean.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/ExternalPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/LoginPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/LogoutPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/PasswordRecoveryPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/PlayerSessionRedirector.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/ReactivationPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/RegistrationPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/ValidationPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/AccountPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/AlliancePage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/BattlePages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/BugTrackerPages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/ChatPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/EnemiesPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/FleetsPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/GetPlanetPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/MapPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/MessageBoxView.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/MessagePages.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/PlanetListPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java (100%)
 rename {legacyworlds-web/legacyworlds-web-main => legacyworlds-web-main}/src/main/resources/log4j.properties (100%)
 create mode 100644 legacyworlds-web-main/src/test/java/.empty
 create mode 100644 legacyworlds-web-main/src/test/resources/.empty
 delete mode 100644 legacyworlds-web/.project
 delete mode 100644 legacyworlds-web/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.classpath
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.project
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.settings/.jsdtscope
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.common.component
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.common.project.facet.core.xml
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.jsdt.ui.superType.container
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.jsdt.ui.superType.name
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.ws.service.policy.prefs
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/version.ftl
 delete mode 100644 legacyworlds-web/legacyworlds-web-admin/pom.xml
 delete mode 100644 legacyworlds-web/legacyworlds-web-beans/.classpath
 delete mode 100644 legacyworlds-web/legacyworlds-web-beans/.project
 delete mode 100644 legacyworlds-web/legacyworlds-web-beans/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-web/legacyworlds-web-beans/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.classpath
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.project
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.settings/.jsdtscope
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.jdt.core.prefs
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.common.component
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.common.project.facet.core.xml
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.jsdt.ui.superType.container
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.jsdt.ui.superType.name
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/.settings/org.maven.ide.eclipse.prefs
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/version.ftl
 delete mode 100644 legacyworlds-web/legacyworlds-web-main/pom.xml
 create mode 100644 legacyworlds/.gitignore
 rename {build-tools => legacyworlds/build-tools}/execute-clit.sh (100%)
 rename {build-tools => legacyworlds/build-tools}/server-config-example.sh (100%)
 rename {build-tools => legacyworlds/build-tools}/start-server.sh (100%)
 rename {build-tools => legacyworlds/build-tools}/stop-server.sh (100%)
 create mode 100644 legacyworlds/doc/Eclipse-import.txt
 create mode 100644 legacyworlds/doc/TODO.txt
 create mode 100644 legacyworlds/doc/local-deployment.txt
 create mode 100644 legacyworlds/eclipse-code-cleanup.xml
 rename eclipse-code-format.xml => legacyworlds/eclipse-code-format.xml (100%)
 create mode 100644 legacyworlds/pom.xml
 delete mode 100644 pom.xml
 delete mode 100755 runsrv.sh
 delete mode 100755 runtool.sh

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1e07caf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+target
+.settings
+.classpath
+.project
diff --git a/.project b/.project
deleted file mode 100644
index b09df49..0000000
--- a/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/build-tools/BUILD.sh b/build-tools/BUILD.sh
deleted file mode 100755
index b76b7f5..0000000
--- a/build-tools/BUILD.sh
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/bin/sh
-
-cd `dirname $0`/..
-SRCDIR=`pwd`
-if [ "x$1" = "x" ]; then
-	OUTDIR="$SRCDIR";
-else
-	OUTDIR="$1";
-fi
-
-echo "Building Legacy Worlds..."
-mvn clean || { echo "Maven clean-up failed"; exit 1; };
-mvn package || { echo "Maven build failed"; exit 1; };
-
-echo "Copying files..."
-
-TEMPDIR=`mktemp -d`
-mkdir $TEMPDIR/legacyworlds
-mkdir $TEMPDIR/legacyworlds/server
-mkdir $TEMPDIR/legacyworlds/server/data
-mkdir $TEMPDIR/legacyworlds/web
-mkdir $TEMPDIR/legacyworlds/sql
-
-# Database and initial data
-cp -Rapv legacyworlds-server/legacyworlds-server-data/db-structure/database.sql $TEMPDIR/legacyworlds/sql
-cp -Rapv legacyworlds-server/legacyworlds-server-data/db-structure/db-config.txt $TEMPDIR/legacyworlds/sql/db-config-example.txt
-cp -Rapv legacyworlds-server/legacyworlds-server-data/db-structure/parts $TEMPDIR/legacyworlds/sql
-cp -Rapv legacyworlds-server/legacyworlds-server-main/data/* $TEMPDIR/legacyworlds/server/data
-# Server
-cp -Rapv legacyworlds-server/legacyworlds-server-main/target/*.jar $TEMPDIR/legacyworlds/server
-cp -Rapv legacyworlds-server/legacyworlds-server-main/target/lib $TEMPDIR/legacyworlds/server
-cp -Rapv legacyworlds-server/legacyworlds-server-main/data-source.xml $TEMPDIR/legacyworlds/server/data-source-example.xml
-# Tools
-cp -Rapv build-tools/server-config-example.sh $TEMPDIR/legacyworlds
-cp -Rapv build-tools/start-server.sh $TEMPDIR/legacyworlds
-cp -Rapv build-tools/stop-server.sh $TEMPDIR/legacyworlds
-cp -Rapv build-tools/execute-clit.sh $TEMPDIR/legacyworlds
-cp -Rapv build-tools/deploy.sh $TEMPDIR/legacyworlds
-# Web sites
-cp -Rapv legacyworlds-web/legacyworlds-web-*/target/*.war $TEMPDIR/legacyworlds/web
-
-echo "Preparing archive..."
-cd $TEMPDIR
-find $TEMPDIR/legacyworlds -type d -name .svn | xargs rm -rf
-tar cvjf $OUTDIR/legacyworlds.tar.bz2 legacyworlds || { echo "Archive generation failed"; exit 1; };
-
-echo "Removing temporary directory..."
-cd $SRCDIR
-rm -rf $TEMPDIR
-
-echo "All done. Legacy Worlds archive: $OUTDIR/legacyworlds.tar.bz2"
diff --git a/build-tools/deploy.sh b/build-tools/deploy.sh
deleted file mode 100755
index 5edbf77..0000000
--- a/build-tools/deploy.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-COREDIR="$1"
-if [ "x$COREDIR" = "x" ] || ! [ -d "$COREDIR" ]; then
-	echo "Syntax: $0 main_dir tomcat_dir web_dir"
-	exit 1;
-fi
-
-TOMCATDIR="$2"
-if [ "x$TOMCATDIR" = "x" ] || ! [ -d "$TOMCATDIR" ]; then
-	echo "Syntax: $0 main_dir tomcat_dir web_dir"
-	exit 1;
-fi
-
-WEBDIR="$3"
-if [ "x$WEBDIR" = "x" ] || ! [ -d "$WEBDIR" ]; then
-	echo "Syntax: $0 main_dir tomcat_dir web_dir"
-	exit 1;
-fi
-
-
-cd `dirname $0`
-SRCDIR=`pwd`
-cp -Rap server web *-*.sh $COREDIR
-
-cd "$TOMCATDIR"
-if [ -L "lwmain.war" ]; then
-	rm -f lwmain.war lwadmin.war
-fi
-ln -s "$COREDIR/web/legacyworlds-web-main-"*.war lwmain.war
-ln -s "$COREDIR/web/legacyworlds-web-admin-"*.war lwadmin.war
-
-cd $WEBDIR
-unzip -o $COREDIR/web/legacyworlds-web-main-*.war "css/*" "js/*" "pjirc/*" "img/*"
diff --git a/legacyworlds-server-DIST/pom.xml b/legacyworlds-server-DIST/pom.xml
new file mode 100644
index 0000000..a2fc403
--- /dev/null
+++ b/legacyworlds-server-DIST/pom.xml
@@ -0,0 +1,53 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds-server</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-server-DIST</artifactId>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+
+	<packaging>pom</packaging>
+	<name>Legacy Worlds - Server - Packaging</name>
+	<description>This Maven module is responsible for creating the Legacy Worlds server's packaging.</description>
+
+
+	<!-- Depend solely on the server's JAR, other dependencies will update automatically -->
+	<dependencies>
+		<dependency>
+			<groupId>com.deepclone.lw</groupId>
+			<artifactId>legacyworlds-server-main</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+	</dependencies>
+
+	<!-- Use the assembly plug-in to generate the distribution -->
+	<build>
+		<plugins>
+			<plugin>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>distribution-assembly</id>
+						<phase>package</phase>
+						<goals>
+							<goal>single</goal>
+						</goals>
+						<configuration>
+							<attach>false</attach>
+							<finalName>legacyworlds-server-${project.version}</finalName>
+							<descriptors>
+								<descriptor>src/server.xml</descriptor>
+							</descriptors>
+							<appendAssemblyId>false</appendAssemblyId>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>
\ No newline at end of file
diff --git a/legacyworlds-server-DIST/src/server.xml b/legacyworlds-server-DIST/src/server.xml
new file mode 100644
index 0000000..4250052
--- /dev/null
+++ b/legacyworlds-server-DIST/src/server.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly
+	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+
+	<!-- Legacy Worlds server assembly -->
+
+	<id>legacyworlds-server</id>
+	<formats>
+		<format>dir</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+
+	<dependencySets>
+
+		<!-- The server's main archive -->
+		<dependencySet>
+			<useProjectArtifact>false</useProjectArtifact>
+			<outputDirectory>.</outputDirectory>
+			<unpack>false</unpack>
+			<includes>
+				<include>com.deepclone.lw:legacyworlds-server-main:jar</include>
+			</includes>
+		</dependencySet>
+
+		<!-- Libraries - both internal and external dependencies -->
+		<dependencySet>
+			<useProjectArtifact>false</useProjectArtifact>
+			<outputDirectory>lib</outputDirectory>
+			<unpack>false</unpack>
+			<includes>
+				<include>*:jar</include>
+			</includes>
+			<excludes>
+				<exclude>com.deepclone.lw:legacyworlds-server-main:jar</exclude>
+			</excludes>
+		</dependencySet>
+
+	</dependencySets>
+
+	<fileSets>
+
+		<!-- Database definitions -->
+		<fileSet>
+			<directory>../legacyworlds-server-data/db-structure</directory>
+			<outputDirectory>sql</outputDirectory>
+			<includes>
+				<include>*.sql</include>
+				<include>*/*.sql</include>
+				<include>*/*/*.sql</include>
+			</includes>
+			<excludes>
+				<exclude>*.txt</exclude>
+			</excludes>
+		</fileSet>
+
+		<!-- Default data -->
+		<fileSet>
+			<directory>../legacyworlds-server-main/data</directory>
+			<outputDirectory>data</outputDirectory>
+			<includes>
+				<include>*.txt</include>
+				<include>*.xml</include>
+			</includes>
+		</fileSet>
+
+	</fileSets>
+
+	<files>
+
+		<!-- Data source configuration -->
+		<file>
+			<source>../legacyworlds-server-main/data-source.xml</source>
+			<destName>data-source.sample.xml</destName>
+			<fileMode>0600</fileMode>
+			<outputDirectory>.</outputDirectory>
+		</file>
+		
+		<!-- Database definition variables -->
+		<file>
+			<source>../legacyworlds-server-data/db-structure/db-config.txt</source>
+			<destName>db-config.sample.txt</destName>
+			<fileMode>0600</fileMode>
+			<outputDirectory>sql</outputDirectory>
+		</file>
+
+	</files>
+
+
+</assembly>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/pom.xml b/legacyworlds-server-beans-accounts/pom.xml
similarity index 67%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/pom.xml
rename to legacyworlds-server-beans-accounts/pom.xml
index 093cc46..a150eda 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/pom.xml
+++ b/legacyworlds-server-beans-accounts/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-accounts</artifactId>
-	<name>Legacy Worlds account management</name>
-	<version>5.99.1</version>
+	<name>Legacy Worlds - Server - Components - Accounts</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 	<description>This package contains the beans responsible for managing accounts, including registration, inactivity checks, bans and authentication.</description>
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupTask.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupTask.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountCleanupTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountManagementBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountManagementBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountManagementBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/AccountManagementBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorTask.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorTask.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/QuitProcessorTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationTask.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationTask.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/RequestsExpirationTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorTask.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorTask.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/VacationProcessorTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminRecapBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminRecapBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminRecapBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminRecapBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminRecapTask.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminRecapTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminRecapTask.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminRecapTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdministrationBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdministrationBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdministrationBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdministrationBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansProcessorBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansProcessorBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansProcessorBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansProcessorBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansProcessorTask.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansProcessorTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansProcessorTask.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansProcessorTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/IpBanBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/IpBanBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/IpBanBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/IpBanBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/BoolPreferenceType.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/BoolPreferenceType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/BoolPreferenceType.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/BoolPreferenceType.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/DefaultPreferencesBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/DefaultPreferencesBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/DefaultPreferencesBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/DefaultPreferencesBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/EnumPreferenceType.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/EnumPreferenceType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/EnumPreferenceType.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/EnumPreferenceType.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/IntPreferenceType.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/IntPreferenceType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/IntPreferenceType.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/IntPreferenceType.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypeImpl.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypeImpl.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypeImpl.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypeImpl.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypesRegistryBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypesRegistryBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypesRegistryBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceTypesRegistryBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/StringPreferenceType.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/StringPreferenceType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/StringPreferenceType.java
rename to legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/StringPreferenceType.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts-beans.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts-beans.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-cleanup-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-cleanup-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-cleanup-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-cleanup-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-management-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-management-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-management-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-management-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-dao-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-recap-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-recap-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-recap-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-recap-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/administration-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/administration-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/administration-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/administration-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-dao-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-processor-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-processor-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-processor-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-processor-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/default-preferences-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/default-preferences-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/default-preferences-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/default-preferences-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/ip-ban-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/ip-ban-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/ip-ban-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/ip-ban-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preference-definitions-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preference-definitions-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preference-definitions-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preference-definitions-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preferences-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preferences-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preferences-dao-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preferences-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/quit-processor-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/quit-processor-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/quit-processor-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/quit-processor-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/requests-expiration-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/requests-expiration-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/requests-expiration-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/requests-expiration-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/user-session-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/user-session-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/user-session-dao-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/user-session-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/users-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/users-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/users-dao-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/users-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/vacation-processor-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/vacation-processor-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/vacation-processor-bean.xml
rename to legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/vacation-processor-bean.xml
diff --git a/legacyworlds-server-beans-accounts/src/test/java/.empty b/legacyworlds-server-beans-accounts/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-accounts/src/test/resources/.empty b/legacyworlds-server-beans-accounts/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/pom.xml b/legacyworlds-server-beans-bt/pom.xml
similarity index 65%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/pom.xml
rename to legacyworlds-server-beans-bt/pom.xml
index 35ab5e4..378a9a5 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/pom.xml
+++ b/legacyworlds-server-beans-bt/pom.xml
@@ -4,7 +4,8 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
 	</parent>
 
 	<dependencies>
@@ -12,14 +13,11 @@
 		<dependency>
 			<groupId>com.thoughtworks.xstream</groupId>
 			<artifactId>xstream</artifactId>
-			<version>${com.thoughtworks.xstream.version}</version>
-			<type>jar</type>
 		</dependency>
 
 	</dependencies>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-bt</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds bug tracking system</name>
-</project>
\ No newline at end of file
+	<name>Legacy Worlds - Server - Components - Bug tracking system</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/AdminBugsBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/AdminBugsBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/AdminBugsBean.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/AdminBugsBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/PlayerBugsBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/PlayerBugsBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/PlayerBugsBean.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/PlayerBugsBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AccountInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AccountInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AccountInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AccountInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AllianceInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AllianceInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AllianceInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AllianceInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/BuildingsInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/BuildingsInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/BuildingsInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/BuildingsInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/EmpireInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/EmpireInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/EmpireInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/EmpireInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/FleetInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/FleetInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/FleetInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/FleetInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/MovementInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/MovementInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/MovementInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/MovementInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/PlanetInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/PlanetInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/PlanetInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/PlanetInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueItemInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueItemInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueItemInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueItemInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ShipsInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ShipsInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ShipsInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ShipsInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/SystemInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/SystemInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/SystemInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/SystemInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt-beans.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt-beans.xml
rename to legacyworlds-server-beans-bt/src/main/resources/configuration/bt-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/admin-bugs-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/admin-bugs-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/admin-bugs-bean.xml
rename to legacyworlds-server-beans-bt/src/main/resources/configuration/bt/admin-bugs-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/bugs-dao-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/bugs-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/bugs-dao-bean.xml
rename to legacyworlds-server-beans-bt/src/main/resources/configuration/bt/bugs-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
rename to legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/player-bugs-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/player-bugs-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/player-bugs-bean.xml
rename to legacyworlds-server-beans-bt/src/main/resources/configuration/bt/player-bugs-bean.xml
diff --git a/legacyworlds-server-beans-bt/src/test/java/.empty b/legacyworlds-server-beans-bt/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-bt/src/test/resources/.empty b/legacyworlds-server-beans-bt/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/pom.xml b/legacyworlds-server-beans-eventlog/pom.xml
similarity index 68%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/pom.xml
rename to legacyworlds-server-beans-eventlog/pom.xml
index 0305043..2c15149 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/pom.xml
+++ b/legacyworlds-server-beans-eventlog/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-eventlog</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds event log</name>
+	<name>Legacy Worlds - Server - Components - Logging</name>
 	<description>This package is responsible for all logging in Legacy Worlds through three different beans (system event logger, admin event logger and user event logger).</description>
-</project>
\ No newline at end of file
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailBean.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailBean.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailTask.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailTask.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/AdminErrorMailTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/EntryQueueItem.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/EntryQueueItem.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/EntryQueueItem.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/EntryQueueItem.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerBean.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerBean.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerTask.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerTask.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogCleanerTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterBean.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterBean.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterTask.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterTask.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogWriterTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LoggerBean.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LoggerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LoggerBean.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LoggerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/SystemLoggerImpl.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/SystemLoggerImpl.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/SystemLoggerImpl.java
rename to legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/SystemLoggerImpl.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog-beans.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog-beans.xml
rename to legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml
rename to legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-cleaner-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-cleaner-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-cleaner-bean.xml
rename to legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-cleaner-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-reader-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-reader-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-reader-bean.xml
rename to legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-reader-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-writer-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-writer-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-writer-bean.xml
rename to legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-writer-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/logger-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/logger-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/logger-bean.xml
rename to legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/logger-bean.xml
diff --git a/legacyworlds-server-beans-eventlog/src/test/java/.empty b/legacyworlds-server-beans-eventlog/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-eventlog/src/test/resources/.empty b/legacyworlds-server-beans-eventlog/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-i18n/pom.xml b/legacyworlds-server-beans-i18n/pom.xml
new file mode 100644
index 0000000..6660fee
--- /dev/null
+++ b/legacyworlds-server-beans-i18n/pom.xml
@@ -0,0 +1,16 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds-server-beans</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-server-beans-i18n</artifactId>
+	<name>Legacy Worlds - Server - Components - Internationalisation</name>
+	<description>This package defines the components which control server-side internationalised text management.</description>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NAdministrationImpl.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NAdministrationImpl.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NAdministrationImpl.java
rename to legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NAdministrationImpl.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
rename to legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NManagerBean.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NManagerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NManagerBean.java
rename to legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NManagerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LanguageStore.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LanguageStore.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LanguageStore.java
rename to legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LanguageStore.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java
rename to legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
rename to legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n-beans.xml b/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n-beans.xml
rename to legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-manager-bean.xml b/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-manager-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-manager-bean.xml
rename to legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-manager-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-translator-bean.xml b/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-translator-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-translator-bean.xml
rename to legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-translator-bean.xml
diff --git a/legacyworlds-server-beans-i18n/src/test/java/.empty b/legacyworlds-server-beans-i18n/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-i18n/src/test/resources/.empty b/legacyworlds-server-beans-i18n/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/pom.xml b/legacyworlds-server-beans-mailer/pom.xml
similarity index 59%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/pom.xml
rename to legacyworlds-server-beans-mailer/pom.xml
index 9b2eba1..a69cc12 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/pom.xml
+++ b/legacyworlds-server-beans-mailer/pom.xml
@@ -4,31 +4,26 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-mailer</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds mailer</name>
-	<description>
-		This package contains the mailer component, which uses LW's i18n system and Spring's mail sending interfaces.
-It is capable of sending mails synchronously or asynchronously.
-	</description>
+	<name>Legacy Worlds - Server - Components - Mailer</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<description>This package contains the mailer component, which uses LW's i18n system and Spring's mail sending interfaces.
+It is capable of sending mails synchronously or asynchronously.</description>
 
 	<dependencies>
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-context-support</artifactId>
-			<version>${org.springframework.version}</version>
 			<type>jar</type>
 		</dependency>
 
 		<dependency>
 			<groupId>javax.mail</groupId>
 			<artifactId>mail</artifactId>
-			<version>${javax.mail.version}</version>
-			<scope>runtime</scope>
 		</dependency>
 	</dependencies>
 
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailDataImpl.java b/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailDataImpl.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailDataImpl.java
rename to legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailDataImpl.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueHandler.java b/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueHandler.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueHandler.java
rename to legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueHandler.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueItem.java b/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueItem.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueItem.java
rename to legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueItem.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTask.java b/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTask.java
rename to legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTerminator.java b/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTerminator.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTerminator.java
rename to legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailQueueTerminator.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailerBean.java b/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailerBean.java
rename to legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/MailerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/QueuedMail.java b/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/QueuedMail.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/QueuedMail.java
rename to legacyworlds-server-beans-mailer/src/main/java/com/deepclone/lw/beans/mailer/QueuedMail.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer-beans.xml b/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer-beans.xml
rename to legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer/mailer-bean.xml b/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer/mailer-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer/mailer-bean.xml
rename to legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer/mailer-bean.xml
diff --git a/legacyworlds-server-beans-mailer/src/test/java/.empty b/legacyworlds-server-beans-mailer/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-mailer/src/test/resources/.empty b/legacyworlds-server-beans-mailer/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/pom.xml b/legacyworlds-server-beans-naming/pom.xml
similarity index 66%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/pom.xml
rename to legacyworlds-server-beans-naming/pom.xml
index 00964d7..864cec9 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/pom.xml
+++ b/legacyworlds-server-beans-naming/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-naming</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds object naming system</name>
+	<name>Legacy Worlds - Server - Components - Naming system</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 	<description>This module contains the beans responsible for managing the names of the various objects (players and planets).</description>
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamesManagerBean.java b/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamesManagerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamesManagerBean.java
rename to legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamesManagerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java b/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java
rename to legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/resources/configuration/naming-beans.xml b/legacyworlds-server-beans-naming/src/main/resources/configuration/naming-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/resources/configuration/naming-beans.xml
rename to legacyworlds-server-beans-naming/src/main/resources/configuration/naming-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/names-manager-bean.xml b/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/names-manager-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/names-manager-bean.xml
rename to legacyworlds-server-beans-naming/src/main/resources/configuration/naming/names-manager-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/naming-dao-bean.xml b/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/naming-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/naming-dao-bean.xml
rename to legacyworlds-server-beans-naming/src/main/resources/configuration/naming/naming-dao-bean.xml
diff --git a/legacyworlds-server-beans-naming/src/test/java/.empty b/legacyworlds-server-beans-naming/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-naming/src/test/resources/.empty b/legacyworlds-server-beans-naming/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-simple/pom.xml b/legacyworlds-server-beans-simple/pom.xml
new file mode 100644
index 0000000..023a858
--- /dev/null
+++ b/legacyworlds-server-beans-simple/pom.xml
@@ -0,0 +1,14 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds-server-beans</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-server-beans-simple</artifactId>
+	<name>Legacy Worlds - Server - Components - Simple game</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<description>This module contains code that corresponds to a simple "placeholder" game. This code should become obsolete over time, as it is being replaced with actual LWB6 code, until the module can finally be removed.</description>
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceManagementBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceManagementBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceManagementBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/PlanetListMapper.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/PlanetListMapper.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/PlanetListMapper.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/PlanetListMapper.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattleViewerBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattleViewerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattleViewerBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattleViewerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesCacheBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesCacheBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesCacheBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesCacheBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetManagementBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetManagementBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetManagementBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/MapViewerBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/MapViewerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/MapViewerBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/MapViewerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorTask.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorTask.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseGeneratorTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/AdminMessagesBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/AdminMessagesBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/AdminMessagesBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/AdminMessagesBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/EmpireMessagesBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/EmpireMessagesBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/EmpireMessagesBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/EmpireMessagesBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MailTaskBase.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MailTaskBase.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MailTaskBase.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MailTaskBase.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageCleanerBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageCleanerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageCleanerBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageCleanerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageContentCacheBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageContentCacheBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageContentCacheBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageContentCacheBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatRegistryBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatRegistryBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatRegistryBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatRegistryBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatWiringBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatWiringBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatWiringBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageFormatWiringBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageTasksBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageTasksBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageTasksBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageTasksBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsTask.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsTask.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/RecapitulationTask.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/RecapitulationTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/RecapitulationTask.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/RecapitulationTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AbandonMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AbandonMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AbandonMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AbandonMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageExtractor.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageExtractor.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageExtractor.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageExtractor.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AdminMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceDisbandedMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceDisbandedMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceDisbandedMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceDisbandedMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceMessageExtractor.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceMessageExtractor.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceMessageExtractor.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/AllianceMessageExtractor.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/BattleMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/BattleMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/BattleMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/BattleMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/BugMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/BugMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/BugMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/BugMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/DebtMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/DebtMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/DebtMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/DebtMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/ExternalMessageFormatBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/ExternalMessageFormatBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/ExternalMessageFormatBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/ExternalMessageFormatBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageExtractor.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageExtractor.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageExtractor.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageExtractor.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/FleetMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/KickedMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/KickedMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/KickedMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/KickedMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeadershipMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeadershipMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeadershipMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeadershipMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeftAllianceMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeftAllianceMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeftAllianceMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LeftAllianceMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LostPlanetMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LostPlanetMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LostPlanetMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/LostPlanetMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/PendingRequestMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/PendingRequestMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/PendingRequestMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/PendingRequestMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/PlanetMessageExtractor.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/PlanetMessageExtractor.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/PlanetMessageExtractor.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/PlanetMessageExtractor.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/QueueMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/QueueMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/QueueMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/QueueMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/RequestResultMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/RequestResultMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/RequestResultMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/RequestResultMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/StrikeMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/StrikeMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/StrikeMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/StrikeMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/TakenPlanetMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/TakenPlanetMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/TakenPlanetMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/TakenPlanetMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/TechMessageFormatterBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/TechMessageFormatterBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/TechMessageFormatterBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/fmt/TechMessageFormatterBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
rename to legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/alliance-dao-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/alliance-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/alliance-dao-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/alliance-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/alliance-management-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/alliance-management-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/alliance-management-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/alliance-management-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/battle-data-beans.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/battle-data-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/battle-data-beans.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/battle-data-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/empire-dao-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/empire-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/empire-dao-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/empire-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/empire-management-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/empire-management-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/empire-management-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/empire-management-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/fleet-management-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/fleet-management-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/fleet-management-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/fleet-management-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/fleets-dao-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/fleets-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/fleets-dao-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/fleets-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/map-viewer-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/map-viewer-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/map-viewer-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/map-viewer-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/message-beans.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/message-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/message-beans.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/message-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/planet-dao-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/planet-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/planet-dao-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/planet-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/planets-management-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/planets-management-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/planets-management-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/planets-management-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/universe-dao-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/universe-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/universe-dao-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/universe-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/universe-generator-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/universe-generator-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/universe-generator-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/universe-generator-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml
diff --git a/legacyworlds-server-beans-simple/src/test/java/.empty b/legacyworlds-server-beans-simple/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-simple/src/test/resources/.empty b/legacyworlds-server-beans-simple/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/pom.xml b/legacyworlds-server-beans-system/pom.xml
similarity index 51%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/pom.xml
rename to legacyworlds-server-beans-system/pom.xml
index 47dfd4f..e367674 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/pom.xml
+++ b/legacyworlds-server-beans-system/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-system</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds system management</name>
-	<description>This module regroups system management beans such as the constants manager.</description>
-</project>
\ No newline at end of file
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Server - Components - System</name>
+	<description>This module regroups system management components such as the constants manager.</description>
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsAdministrationImpl.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsAdministrationImpl.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsAdministrationImpl.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsAdministrationImpl.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ServerSessionData.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ServerSessionData.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ServerSessionData.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ServerSessionData.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SessionManagerBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SessionManagerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SessionManagerBean.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SessionManagerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerTask.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerTask.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerTask.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerTask.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerTaskStatusHandler.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerTaskStatusHandler.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerTaskStatusHandler.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerTaskStatusHandler.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerThread.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerThread.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerThread.java
rename to legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerThread.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system-beans.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system-beans.xml
rename to legacyworlds-server-beans-system/src/main/resources/configuration/system-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-manager-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-manager-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-manager-bean.xml
rename to legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-manager-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-registrar-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-registrar-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-registrar-bean.xml
rename to legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-registrar-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/session-manager-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/session-manager-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/session-manager-bean.xml
rename to legacyworlds-server-beans-system/src/main/resources/configuration/system/session-manager-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/system-status-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/system-status-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/system-status-bean.xml
rename to legacyworlds-server-beans-system/src/main/resources/configuration/system/system-status-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/ticker-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/ticker-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/resources/configuration/system/ticker-bean.xml
rename to legacyworlds-server-beans-system/src/main/resources/configuration/system/ticker-bean.xml
diff --git a/legacyworlds-server-beans-system/src/test/java/.empty b/legacyworlds-server-beans-system/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-system/src/test/resources/.empty b/legacyworlds-server-beans-system/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/pom.xml b/legacyworlds-server-beans-user/pom.xml
similarity index 63%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/pom.xml
rename to legacyworlds-server-beans-user/pom.xml
index 0de863a..e506d66 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/pom.xml
+++ b/legacyworlds-server-beans-user/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-user</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds server - user actions</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Server - Components - User actions</name>
 	<description>This module defines beans and classes that handle user actions.</description>
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredCommandDelegate.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredCommandDelegate.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredCommandDelegate.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredCommandDelegate.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredSubTypeDelegate.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredSubTypeDelegate.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredSubTypeDelegate.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/AutowiredSubTypeDelegate.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandDelegate.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandDelegate.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandDelegate.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandDelegate.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandHandler.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandHandler.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandHandler.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandHandler.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandWiringBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandWiringBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandWiringBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionCommandWiringBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeDelegate.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeDelegate.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeDelegate.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeDelegate.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeWiringBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeWiringBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeWiringBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/SessionSubTypeWiringBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/StatefulSessionTypeDefiner.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/StatefulSessionTypeDefiner.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/StatefulSessionTypeDefiner.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/abst/StatefulSessionTypeDefiner.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionDefinerBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionDefinerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionDefinerBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionDefinerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionSubType.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionSubType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionSubType.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/AdminSessionSubType.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/AdminOperation.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/AdminOperation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/AdminOperation.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/AdminOperation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/CommonCommandsBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/CommonCommandsBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/CommonCommandsBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/CommonCommandsBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/CreateAuthChallengeCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/CreateAuthChallengeCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/CreateAuthChallengeCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/CreateAuthChallengeCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/NoOperationCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/NoOperationCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/NoOperationCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/NoOperationCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/SetPasswordCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/SetPasswordCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/SetPasswordCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/common/SetPasswordCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminCommandsBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminCommandsBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminCommandsBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminCommandsBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminOverviewCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminOverviewCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminOverviewCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/AdminOverviewCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/BansSummaryCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/BansSummaryCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/BansSummaryCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/BansSummaryCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ConfirmBanCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ConfirmBanCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ConfirmBanCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ConfirmBanCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/LiftBanCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/LiftBanCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/LiftBanCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/LiftBanCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ListBansCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ListBansCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ListBansCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/ListBansCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RejectBanCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RejectBanCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RejectBanCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RejectBanCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RequestBanCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RequestBanCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RequestBanCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bans/RequestBanCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/BugsSummaryCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/BugsSummaryCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/BugsSummaryCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/BugsSummaryCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/GetSnapshotCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/GetSnapshotCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/GetSnapshotCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/GetSnapshotCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ListBugsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ListBugsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ListBugsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ListBugsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/MergeReportsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/MergeReportsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/MergeReportsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/MergeReportsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ModerateCommentCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ModerateCommentCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ModerateCommentCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ModerateCommentCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/PostCommentCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/PostCommentCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/PostCommentCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/PostCommentCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportBugCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportBugCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportBugCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportBugCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportStatusCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportStatusCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportStatusCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportStatusCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportVisibilityCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportVisibilityCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportVisibilityCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ReportVisibilityCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ValidateReportCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ValidateReportCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ValidateReportCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ValidateReportCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ViewBugCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ViewBugCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ViewBugCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/bt/ViewBugCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/GetConstantsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/GetConstantsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/GetConstantsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/GetConstantsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/SetConstantCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/SetConstantCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/SetConstantCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/cnst/SetConstantCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ChangeLanguageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ChangeLanguageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ChangeLanguageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ChangeLanguageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/GetLanguageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/GetLanguageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/GetLanguageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/GetLanguageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/SetStringCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/SetStringCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/SetStringCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/SetStringCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ViewLanguagesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ViewLanguagesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ViewLanguagesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/i18n/ViewLanguagesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/GetEntryCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/GetEntryCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/GetEntryCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/GetEntryCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/ViewLogsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/ViewLogsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/ViewLogsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/logs/ViewLogsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EnableMaintenanceCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EnableMaintenanceCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EnableMaintenanceCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EnableMaintenanceCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EndMaintenanceCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EndMaintenanceCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EndMaintenanceCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/EndMaintenanceCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/ExtendMaintenanceCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/ExtendMaintenanceCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/ExtendMaintenanceCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/ExtendMaintenanceCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/MaintenanceStatusCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/MaintenanceStatusCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/MaintenanceStatusCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/mntm/MaintenanceStatusCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ComposeMessageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ComposeMessageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ComposeMessageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ComposeMessageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/GetMessagesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/GetMessagesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/GetMessagesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/GetMessagesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/MessageBoxCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/MessageBoxCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/MessageBoxCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/MessageBoxCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/PrepareMessageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/PrepareMessageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/PrepareMessageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/PrepareMessageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ReadMessageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ReadMessageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ReadMessageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/ReadMessageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/SendSpamCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/SendSpamCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/SendSpamCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/msgs/SendSpamCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/GetNamesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/GetNamesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/GetNamesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/GetNamesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesActionCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesActionCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesActionCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesActionCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesSummaryCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesSummaryCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesSummaryCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/names/NamesSummaryCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/GetPrefDefaultsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/GetPrefDefaultsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/GetPrefDefaultsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/GetPrefDefaultsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/SetPrefDefaultCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/SetPrefDefaultCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/SetPrefDefaultCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/prefs/SetPrefDefaultCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ListAdministratorsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ListAdministratorsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ListAdministratorsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ListAdministratorsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ResetAdminPasswordCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ResetAdminPasswordCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ResetAdminPasswordCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ResetAdminPasswordCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SUExistingOperation.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SUExistingOperation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SUExistingOperation.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SUExistingOperation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SetPrivilegesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SetPrivilegesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SetPrivilegesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SetPrivilegesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SuperUserOperation.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SuperUserOperation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SuperUserOperation.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/SuperUserOperation.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ViewAdministratorCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ViewAdministratorCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ViewAdministratorCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/ViewAdministratorCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/SetTaskStatusCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/SetTaskStatusCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/SetTaskStatusCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/SetTaskStatusCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/TickerStatusCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/TickerStatusCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/TickerStatusCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/TickerStatusCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/ToggleTickerCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/ToggleTickerCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/ToggleTickerCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/tick/ToggleTickerCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/GiveCreditsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/GiveCreditsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/GiveCreditsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/GiveCreditsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListAccountsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListAccountsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListAccountsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListAccountsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListSessionsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListSessionsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListSessionsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ListSessionsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ViewAccountCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ViewAccountCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ViewAccountCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/users/ViewAccountCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/AccountCreationCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/AccountCreationCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/AccountCreationCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/AccountCreationCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ConfPwdRecoveryCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ConfPwdRecoveryCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ConfPwdRecoveryCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ConfPwdRecoveryCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ExternalSessionDefinerBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ExternalSessionDefinerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ExternalSessionDefinerBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ExternalSessionDefinerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/LanguageListRequired.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/LanguageListRequired.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/LanguageListRequired.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/LanguageListRequired.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ListLanguagesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ListLanguagesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ListLanguagesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ListLanguagesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ReqPwdRecoveryCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ReqPwdRecoveryCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ReqPwdRecoveryCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ext/ReqPwdRecoveryCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/BanDetailsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/BanDetailsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/BanDetailsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/BanDetailsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/BannedSubTypeBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/BannedSubTypeBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/BannedSubTypeBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/BannedSubTypeBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/DisabledSubTypeBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/DisabledSubTypeBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/DisabledSubTypeBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/DisabledSubTypeBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/GameSubTypeBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/GameSubTypeBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/GameSubTypeBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/GameSubTypeBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/GetLanguageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/GetLanguageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/GetLanguageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/GetLanguageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerCommonCommandsBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerCommonCommandsBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerCommonCommandsBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerCommonCommandsBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionDefinerBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionDefinerBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionDefinerBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionDefinerBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionSubType.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionSubType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionSubType.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/PlayerSessionSubType.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ReactivateCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ReactivateCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ReactivateCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ReactivateCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationSubTypeBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationSubTypeBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationSubTypeBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationSubTypeBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/CancelQuitCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/CancelQuitCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/CancelQuitCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/CancelQuitCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/CreateAuthChallengeCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/CreateAuthChallengeCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/CreateAuthChallengeCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/CreateAuthChallengeCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/GetAccountCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/GetAccountCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/GetAccountCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/GetAccountCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/QuitGameCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/QuitGameCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/QuitGameCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/QuitGameCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetAddressCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetAddressCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetAddressCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetAddressCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetLanguageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetLanguageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetLanguageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetLanguageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetPasswordCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetPasswordCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetPasswordCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetPasswordCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetPreferencesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetPreferencesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetPreferencesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/SetPreferencesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/ToggleVacationCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/ToggleVacationCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/ToggleVacationCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/ToggleVacationCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/ValidateSetAddressCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/ValidateSetAddressCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/ValidateSetAddressCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/account/ValidateSetAddressCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ListBugsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ListBugsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ListBugsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ListBugsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/PostCommentCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/PostCommentCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/PostCommentCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/PostCommentCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ReportBugCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ReportBugCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ReportBugCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ReportBugCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ViewBugCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ViewBugCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ViewBugCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/bt/ViewBugCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetBattleCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetBattleCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetBattleCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetBattleCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ListBattlesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ListBattlesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ListBattlesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ListBattlesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ListPlanetsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ListPlanetsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ListPlanetsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ListPlanetsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/OverviewCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/OverviewCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/OverviewCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/OverviewCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ViewMapCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ViewMapCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ViewMapCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ViewMapCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/AllianceStatusCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/AllianceStatusCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/AllianceStatusCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/AllianceStatusCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CancelJoinCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CancelJoinCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CancelJoinCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CancelJoinCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/JoinAllianceCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/JoinAllianceCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/JoinAllianceCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/JoinAllianceCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/KickMembersCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/KickMembersCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/KickMembersCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/KickMembersCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/LeaveAllianceCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/LeaveAllianceCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/LeaveAllianceCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/LeaveAllianceCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ManageRequestsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ManageRequestsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ManageRequestsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ManageRequestsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/TransferLeadershipCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/TransferLeadershipCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/TransferLeadershipCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/TransferLeadershipCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ViewAllianceCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ViewAllianceCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ViewAllianceCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/ViewAllianceCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/AddEnemyCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/AddEnemyCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/AddEnemyCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/AddEnemyCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/EnemyListCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/EnemyListCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/EnemyListCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/EnemyListCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/RemoveEnemiesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/RemoveEnemiesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/RemoveEnemiesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/elist/RemoveEnemiesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/DisbandFleetsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/DisbandFleetsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/DisbandFleetsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/DisbandFleetsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MergeFleetsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MergeFleetsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MergeFleetsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MergeFleetsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MoveFleetsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MoveFleetsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MoveFleetsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/MoveFleetsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SetFleetsModeCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SetFleetsModeCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SetFleetsModeCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SetFleetsModeCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/ViewFleetsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/ViewFleetsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/ViewFleetsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/ViewFleetsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/AbandonPlanetCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/AbandonPlanetCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/AbandonPlanetCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/AbandonPlanetCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/BuildingActionCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/BuildingActionCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/BuildingActionCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/BuildingActionCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/FlushQueueCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/FlushQueueCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/FlushQueueCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/FlushQueueCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ShipBuildingCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ShipBuildingCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ShipBuildingCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ShipBuildingCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ViewPlanetCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ViewPlanetCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ViewPlanetCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ViewPlanetCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ComposeMessageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ComposeMessageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ComposeMessageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ComposeMessageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/GetMessagesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/GetMessagesCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/GetMessagesCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/GetMessagesCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ListTargetsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ListTargetsCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ListTargetsCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ListTargetsCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/MessageBoxCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/MessageBoxCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/MessageBoxCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/MessageBoxCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/PrepareMessageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/PrepareMessageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/PrepareMessageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/PrepareMessageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ReadMessageCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ReadMessageCommandDelegateBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ReadMessageCommandDelegateBean.java
rename to legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/msgs/ReadMessageCommandDelegateBean.java
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user-beans.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user-beans.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user-beans.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/user-beans.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/external-session-definer-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user/external-session-definer-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/external-session-definer-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/user/external-session-definer-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/object-name-validator-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user/object-name-validator-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/object-name-validator-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/user/object-name-validator-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-command-wiring-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-command-wiring-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-command-wiring-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/user/session-command-wiring-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-subtype-wiring-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-subtype-wiring-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-subtype-wiring-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/user/session-subtype-wiring-bean.xml
diff --git a/legacyworlds-server-beans-user/src/test/java/.empty b/legacyworlds-server-beans-user/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-user/src/test/resources/.empty b/legacyworlds-server-beans-user/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-beans/pom.xml b/legacyworlds-server-beans/pom.xml
similarity index 53%
rename from legacyworlds-server/legacyworlds-server-beans/pom.xml
rename to legacyworlds-server-beans/pom.xml
index 9d74460..1c27874 100644
--- a/legacyworlds-server/legacyworlds-server-beans/pom.xml
+++ b/legacyworlds-server-beans/pom.xml
@@ -4,13 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans</artifactId>
-	<name>Legacy Worlds server beans</name>
-	<version>5.99.1</version>
+	<name>Legacy Worlds - Server - Components</name>
 	<packaging>pom</packaging>
 	<description>This metapackage regroups all packages which define beans for the Legacy Worlds server.</description>
 
@@ -18,19 +17,18 @@
 		<dependency>
 			<groupId>com.deepclone.lw</groupId>
 			<artifactId>legacyworlds-server-interfaces</artifactId>
-			<version>${project.version}</version>
 		</dependency>
 	</dependencies>
 
 	<modules>
-		<module>legacyworlds-server-beans-i18n</module>
-		<module>legacyworlds-server-beans-eventlog</module>
-		<module>legacyworlds-server-beans-accounts</module>
-		<module>legacyworlds-server-beans-mailer</module>
-		<module>legacyworlds-server-beans-system</module>
-		<module>legacyworlds-server-beans-naming</module>
-		<module>legacyworlds-server-beans-bt</module>
-		<module>legacyworlds-server-beans-user</module>
-		<module>legacyworlds-server-beans-simple</module>
+		<module>../legacyworlds-server-beans-i18n</module>
+		<module>../legacyworlds-server-beans-eventlog</module>
+		<module>../legacyworlds-server-beans-accounts</module>
+		<module>../legacyworlds-server-beans-mailer</module>
+		<module>../legacyworlds-server-beans-system</module>
+		<module>../legacyworlds-server-beans-naming</module>
+		<module>../legacyworlds-server-beans-bt</module>
+		<module>../legacyworlds-server-beans-user</module>
+		<module>../legacyworlds-server-beans-simple</module>
 	</modules>
 </project>
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/database.sql b/legacyworlds-server-data/db-structure/database.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/database.sql
rename to legacyworlds-server-data/db-structure/database.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/db-config.txt b/legacyworlds-server-data/db-structure/db-config.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/db-config.txt
rename to legacyworlds-server-data/db-structure/db-config.txt
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/000-schemas.sql b/legacyworlds-server-data/db-structure/parts/000-schemas.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/000-schemas.sql
rename to legacyworlds-server-data/db-structure/parts/000-schemas.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/010-data.sql b/legacyworlds-server-data/db-structure/parts/010-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/010-data.sql
rename to legacyworlds-server-data/db-structure/parts/010-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/020-functions.sql b/legacyworlds-server-data/db-structure/parts/020-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/020-functions.sql
rename to legacyworlds-server-data/db-structure/parts/020-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/030-updates.sql b/legacyworlds-server-data/db-structure/parts/030-updates.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/030-updates.sql
rename to legacyworlds-server-data/db-structure/parts/030-updates.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql b/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
rename to legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/010-i18n-data.sql b/legacyworlds-server-data/db-structure/parts/data/010-i18n-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/010-i18n-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/010-i18n-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/020-prefs-data.sql b/legacyworlds-server-data/db-structure/parts/data/020-prefs-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/020-prefs-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/020-prefs-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/030-users-data.sql b/legacyworlds-server-data/db-structure/parts/data/030-users-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/030-users-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/030-users-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/035-session-data.sql b/legacyworlds-server-data/db-structure/parts/data/035-session-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/035-session-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/035-session-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/040-admin-data.sql b/legacyworlds-server-data/db-structure/parts/data/040-admin-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/040-admin-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/040-admin-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/050-accounts-data.sql b/legacyworlds-server-data/db-structure/parts/data/050-accounts-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/050-accounts-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/050-accounts-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/055-bugs-data.sql b/legacyworlds-server-data/db-structure/parts/data/055-bugs-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/055-bugs-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/055-bugs-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/060-naming-data.sql b/legacyworlds-server-data/db-structure/parts/data/060-naming-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/060-naming-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/060-naming-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql b/legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql b/legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql b/legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql b/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql b/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql b/legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql b/legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/140-status-data.sql b/legacyworlds-server-data/db-structure/parts/data/140-status-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/140-status-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/140-status-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/150-logs-data.sql b/legacyworlds-server-data/db-structure/parts/data/150-logs-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/150-logs-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/150-logs-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/160-battle-data.sql b/legacyworlds-server-data/db-structure/parts/data/160-battle-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/160-battle-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/160-battle-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/170-events-data.sql b/legacyworlds-server-data/db-structure/parts/data/170-events-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/170-events-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/170-events-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/180-messages-data.sql b/legacyworlds-server-data/db-structure/parts/data/180-messages-data.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/180-messages-data.sql
rename to legacyworlds-server-data/db-structure/parts/data/180-messages-data.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/000-defs-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/000-defs-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/000-defs-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/000-defs-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/020-naming-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/020-naming-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/020-naming-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/020-naming-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/035-users-view.sql b/legacyworlds-server-data/db-structure/parts/functions/035-users-view.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/035-users-view.sql
rename to legacyworlds-server-data/db-structure/parts/functions/035-users-view.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/075-session-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/075-session-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/075-session-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/075-session-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/080-buildings-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/080-buildings-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/080-buildings-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/080-buildings-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/100-status-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/100-status-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/100-status-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/100-status-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/110-prefs-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/110-prefs-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/110-prefs-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/110-prefs-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/120-map-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/120-map-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/120-map-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/120-map-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql b/legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql
rename to legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/163-alliance-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/163-alliance-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/163-alliance-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/163-alliance-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql b/legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql
rename to legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql
rename to legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/210-admin-overview.sql b/legacyworlds-server-data/db-structure/parts/functions/210-admin-overview.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/210-admin-overview.sql
rename to legacyworlds-server-data/db-structure/parts/functions/210-admin-overview.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql b/legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql
rename to legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql b/legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql
rename to legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql b/legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql
rename to legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql b/legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql
rename to legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/030-fleet-arrivals.sql b/legacyworlds-server-data/db-structure/parts/updates/030-fleet-arrivals.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/030-fleet-arrivals.sql
rename to legacyworlds-server-data/db-structure/parts/updates/030-fleet-arrivals.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/040-fleet-movements.sql b/legacyworlds-server-data/db-structure/parts/updates/040-fleet-movements.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/040-fleet-movements.sql
rename to legacyworlds-server-data/db-structure/parts/updates/040-fleet-movements.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/050-fleet-status.sql b/legacyworlds-server-data/db-structure/parts/updates/050-fleet-status.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/050-fleet-status.sql
rename to legacyworlds-server-data/db-structure/parts/updates/050-fleet-status.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql b/legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql
rename to legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/070-planet-abandon.sql b/legacyworlds-server-data/db-structure/parts/updates/070-planet-abandon.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/070-planet-abandon.sql
rename to legacyworlds-server-data/db-structure/parts/updates/070-planet-abandon.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql b/legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql
rename to legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql b/legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql
rename to legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql b/legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql
rename to legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql b/legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql
rename to legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/pom.xml b/legacyworlds-server-data/pom.xml
similarity index 71%
rename from legacyworlds-server/legacyworlds-server-data/pom.xml
rename to legacyworlds-server-data/pom.xml
index a84f721..90ce316 100644
--- a/legacyworlds-server/legacyworlds-server-data/pom.xml
+++ b/legacyworlds-server-data/pom.xml
@@ -4,21 +4,20 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-data</artifactId>
-	<name>Legacy Worlds server data</name>
-	<version>5.99.1</version>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Server - Data</name>
 	<description>This package contains all data access classes for the Legacy Worlds server.</description>
 
 	<dependencies>
 		<dependency>
 			<groupId>com.deepclone.lw</groupId>
 			<artifactId>legacyworlds-session</artifactId>
-			<version>${project.version}</version>
 		</dependency>
 	</dependencies>
 
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/Account.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/Account.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/Account.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/Account.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/AccountOperationResult.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/AccountOperationResult.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/AccountOperationResult.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/AccountOperationResult.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/QuittingAccount.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/QuittingAccount.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/QuittingAccount.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/QuittingAccount.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/ValidationResult.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/ValidationResult.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/ValidationResult.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/accounts/ValidationResult.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/admin/AdminConnection.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/admin/AdminConnection.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/admin/AdminConnection.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/admin/AdminConnection.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/admin/AdminRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/admin/AdminRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/admin/AdminRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/admin/AdminRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/AllianceMembership.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/AllianceMembership.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/AllianceMembership.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/AllianceMembership.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/MapData.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/MapData.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/MapData.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/MapData.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawFleetOwner.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawFleetOwner.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawFleetOwner.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawFleetOwner.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawFleetShip.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawFleetShip.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawFleetShip.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawFleetShip.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawStaticFleet.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawStaticFleet.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawStaticFleet.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/RawStaticFleet.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/BattleListRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/BattleListRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/BattleListRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/BattleListRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/BuildingHistoryRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/BuildingHistoryRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/BuildingHistoryRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/BuildingHistoryRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EmpireBattleRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EmpireBattleRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EmpireBattleRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EmpireBattleRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EventItemRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EventItemRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EventItemRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EventItemRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/EventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/PresenceRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/PresenceRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/PresenceRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/PresenceRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/ProtagonistRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/ProtagonistRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/ProtagonistRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/ProtagonistRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/ShipHistoryRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/ShipHistoryRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/ShipHistoryRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/battle/ShipHistoryRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/i18n/Translation.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/i18n/Translation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/i18n/Translation.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/i18n/Translation.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/AdminEventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/AdminEventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/AdminEventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/AdminEventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/AllianceEventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/AllianceEventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/AllianceEventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/AllianceEventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/BugEventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/BugEventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/BugEventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/BugEventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EmpireEventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EmpireEventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EmpireEventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EmpireEventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventType.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventType.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventType.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventTypeRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventTypeRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventTypeRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/EventTypeRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventFleet.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventFleet.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventFleet.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventFleet.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FleetEventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FormatType.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FormatType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FormatType.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/FormatType.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/InboxRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/InboxRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/InboxRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/InboxRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/MessageDataRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/MessageDataRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/MessageDataRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/MessageDataRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/NotificationsRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/NotificationsRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/NotificationsRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/NotificationsRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/PlanetEventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/PlanetEventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/PlanetEventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/PlanetEventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventLocation.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventLocation.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventLocation.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventLocation.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/QueueEventRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/TextMessageRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/TextMessageRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/TextMessageRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/msgs/TextMessageRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Constant.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Constant.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Constant.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Constant.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/ExceptionLog.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/ExceptionLog.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/ExceptionLog.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/ExceptionLog.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/StackTraceLog.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/StackTraceLog.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/StackTraceLog.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/StackTraceLog.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Status.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Status.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Status.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Status.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/SystemLogEntry.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/SystemLogEntry.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/SystemLogEntry.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/SystemLogEntry.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/TickerTaskRecord.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/TickerTaskRecord.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/TickerTaskRecord.java
rename to legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/TickerTaskRecord.java
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/resources/configuration/transaction-bean.xml b/legacyworlds-server-data/src/main/resources/configuration/transaction-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-data/src/main/resources/configuration/transaction-bean.xml
rename to legacyworlds-server-data/src/main/resources/configuration/transaction-bean.xml
diff --git a/legacyworlds-server-data/src/test/java/.empty b/legacyworlds-server-data/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-data/src/test/resources/.empty b/legacyworlds-server-data/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/pom.xml b/legacyworlds-server-interfaces/pom.xml
similarity index 74%
rename from legacyworlds-server/legacyworlds-server-interfaces/pom.xml
rename to legacyworlds-server-interfaces/pom.xml
index 1a0f426..4581833 100644
--- a/legacyworlds-server/legacyworlds-server-interfaces/pom.xml
+++ b/legacyworlds-server-interfaces/pom.xml
@@ -4,26 +4,24 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-interfaces</artifactId>
-	<version>5.99.1</version>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 
-	<name>Legacy Worlds server interfaces</name>
+	<name>Legacy Worlds - Server - Component interfaces</name>
 	<description>This package contains interfaces for all beans provided by the various server components.</description>
 
 	<dependencies>
 		<dependency>
 			<groupId>com.deepclone.lw</groupId>
 			<artifactId>legacyworlds-server-data</artifactId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<groupId>com.deepclone.lw</groupId>
 			<artifactId>legacyworlds-server-utils</artifactId>
-			<version>${project.version}</version>
 		</dependency>
 	</dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountMailException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountMailException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountMailException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountMailException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountManagement.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountManagement.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountManagement.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountManagement.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountSession.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountSession.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountSession.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/AccountSession.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/EmailChangeException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/EmailChangeException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/EmailChangeException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/EmailChangeException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/JoinGameException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/JoinGameException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/JoinGameException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/JoinGameException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PasswordProhibitedException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PasswordProhibitedException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PasswordProhibitedException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PasswordProhibitedException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PasswordRecoveryException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PasswordRecoveryException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PasswordRecoveryException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PasswordRecoveryException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PermanentlyDisabledException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PermanentlyDisabledException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PermanentlyDisabledException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/PermanentlyDisabledException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/UserSessionDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/UserSessionDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/UserSessionDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/UserSessionDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/UsersDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/UsersDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/UsersDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/acm/UsersDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAOException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAOException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAOException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/AdminDAOException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/Administration.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/Administration.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/Administration.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/Administration.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/BanMailData.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/BanMailData.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/BanMailData.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/BanMailData.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/BansDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/BansDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/BansDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/BansDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/IpBan.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/IpBan.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/IpBan.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/admin/IpBan.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/AdminBugs.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/AdminBugs.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/AdminBugs.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/AdminBugs.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/BugsDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/BugsDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/BugsDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/BugsDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/PlayerBugs.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/PlayerBugs.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/PlayerBugs.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/PlayerBugs.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/ExtendedLogEntry.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/ExtendedLogEntry.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/ExtendedLogEntry.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/ExtendedLogEntry.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/LogReader.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/LogReader.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/LogReader.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/LogReader.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/LogWriter.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/LogWriter.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/LogWriter.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/LogWriter.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/Logger.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/Logger.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/Logger.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/Logger.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/SystemLogger.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/SystemLogger.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/SystemLogger.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/eventlog/SystemLogger.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/AllianceDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/AllianceDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/AllianceDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/AllianceDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/AllianceManagement.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/AllianceManagement.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/AllianceManagement.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/AllianceManagement.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattleViewer.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattleViewer.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattleViewer.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattleViewer.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattlesCache.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattlesCache.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattlesCache.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattlesCache.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattlesDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattlesDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattlesDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/BattlesDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/FleetManagement.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/FleetManagement.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/FleetManagement.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/FleetManagement.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/FleetsDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/FleetsDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/FleetsDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/FleetsDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/MapViewParameters.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/MapViewParameters.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/MapViewParameters.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/MapViewParameters.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/MapViewer.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/MapViewer.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/MapViewer.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/MapViewer.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/PlanetDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/PlanetDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/PlanetDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/PlanetDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/PlanetsManagement.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/PlanetsManagement.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/PlanetsManagement.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/PlanetsManagement.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UniverseDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UniverseDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UniverseDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UniverseDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateLanguageException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateLanguageException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateLanguageException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateLanguageException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateStringException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateStringException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateStringException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/DuplicateStringException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NAdministration.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NAdministration.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NAdministration.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NAdministration.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NManager.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NManager.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NManager.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/I18NManager.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/InvalidUpdateException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/InvalidUpdateException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/InvalidUpdateException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/InvalidUpdateException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/TranslationException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/TranslationException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/TranslationException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/TranslationException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownLanguageException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownLanguageException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownLanguageException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownLanguageException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownStringException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownStringException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownStringException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/UnknownStringException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/AlreadySentException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/AlreadySentException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/AlreadySentException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/AlreadySentException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MailData.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MailData.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MailData.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MailData.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/Mailer.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/Mailer.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/Mailer.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/Mailer.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MailerException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MailerException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MailerException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MailerException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MissingDataException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MissingDataException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MissingDataException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/MissingDataException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/NotSentException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/NotSentException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/NotSentException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/mailer/NotSentException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/AdminMessages.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/AdminMessages.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/AdminMessages.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/AdminMessages.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/EmpireMessages.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/EmpireMessages.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/EmpireMessages.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/EmpireMessages.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageBoxDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageBoxDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageBoxDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageBoxDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageContentCache.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageContentCache.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageContentCache.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageContentCache.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageExtractor.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageExtractor.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageExtractor.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageExtractor.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatRegistry.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatRegistry.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatRegistry.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatRegistry.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatter.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatter.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatter.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageFormatter.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageRecordsDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageRecordsDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageRecordsDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/MessageRecordsDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/NotificationsDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/NotificationsDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/NotificationsDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/msg/NotificationsDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/EmpireNameException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/EmpireNameException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/EmpireNameException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/EmpireNameException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/MapNameException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/MapNameException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/MapNameException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/MapNameException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/NamesManager.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/NamesManager.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/NamesManager.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/NamesManager.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/NamingDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/NamingDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/NamingDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/NamingDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/AccountPreferences.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/AccountPreferences.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/AccountPreferences.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/AccountPreferences.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/Preference.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/Preference.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/Preference.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/Preference.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitionException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitionException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitionException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitionException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitions.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitions.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitions.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceDefinitions.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceGroup.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceGroup.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceGroup.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceGroup.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceType.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceType.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceType.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceType.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceTypesRegistry.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceTypesRegistry.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceTypesRegistry.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferenceTypesRegistry.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferencesDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferencesDAO.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferencesDAO.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/prefs/PreferencesDAO.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/ServerSession.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/ServerSession.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/ServerSession.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/ServerSession.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/SessionManager.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/SessionManager.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/SessionManager.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/SessionManager.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/SessionTypeDefiner.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/SessionTypeDefiner.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/SessionTypeDefiner.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/session/SessionTypeDefiner.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantDefinition.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantDefinition.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantDefinition.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantDefinition.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsAdministration.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsAdministration.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsAdministration.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsAdministration.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsUser.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsUser.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsUser.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsUser.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/EndAutowiredTransaction.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/EndAutowiredTransaction.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/EndAutowiredTransaction.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/EndAutowiredTransaction.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/InvalidConstantValue.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/InvalidConstantValue.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/InvalidConstantValue.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/InvalidConstantValue.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceData.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceData.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceData.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceData.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceStatusException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceStatusException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceStatusException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/MaintenanceStatusException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/TickStatusException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/TickStatusException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/TickStatusException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/TickStatusException.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/Ticker.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/Ticker.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/Ticker.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/Ticker.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/TickerManager.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/TickerManager.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/TickerManager.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/TickerManager.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/UnknownConstantError.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/UnknownConstantError.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/UnknownConstantError.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/UnknownConstantError.java
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/WiringException.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/WiringException.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/WiringException.java
rename to legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/WiringException.java
diff --git a/legacyworlds-server-interfaces/src/main/resources/.empty b/legacyworlds-server-interfaces/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-interfaces/src/test/java/.empty b/legacyworlds-server-interfaces/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-interfaces/src/test/resources/.empty b/legacyworlds-server-interfaces/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-main/data-source.xml b/legacyworlds-server-main/data-source.xml
similarity index 87%
rename from legacyworlds-server/legacyworlds-server-main/data-source.xml
rename to legacyworlds-server-main/data-source.xml
index fccf83f..8dcf68d 100644
--- a/legacyworlds-server/legacyworlds-server-main/data-source.xml
+++ b/legacyworlds-server-main/data-source.xml
@@ -2,8 +2,7 @@
 	<!-- Legacy Worlds Beta 6 - Default data source configuration -->
 	<!--
 		This file is provided as an example on how to configure the game
-		server's data sources. It configures a memory-based HSQL database,
-		initialising its schemas and structure.
+		server's data source.
 	-->
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-en.txt b/legacyworlds-server-main/data/addressChangeMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-en.txt
rename to legacyworlds-server-main/data/addressChangeMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-fr.txt b/legacyworlds-server-main/data/addressChangeMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-fr.txt
rename to legacyworlds-server-main/data/addressChangeMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/adminErrorMail.txt b/legacyworlds-server-main/data/adminErrorMail.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/adminErrorMail.txt
rename to legacyworlds-server-main/data/adminErrorMail.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/adminRecapMail.txt b/legacyworlds-server-main/data/adminRecapMail.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/adminRecapMail.txt
rename to legacyworlds-server-main/data/adminRecapMail.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-en.txt b/legacyworlds-server-main/data/banLiftedMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-en.txt
rename to legacyworlds-server-main/data/banLiftedMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-fr.txt b/legacyworlds-server-main/data/banLiftedMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-fr.txt
rename to legacyworlds-server-main/data/banLiftedMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/bannedMail-en.txt b/legacyworlds-server-main/data/bannedMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/bannedMail-en.txt
rename to legacyworlds-server-main/data/bannedMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/bannedMail-fr.txt b/legacyworlds-server-main/data/bannedMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/bannedMail-fr.txt
rename to legacyworlds-server-main/data/bannedMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/buildables-test.xml b/legacyworlds-server-main/data/buildables-test.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/buildables-test.xml
rename to legacyworlds-server-main/data/buildables-test.xml
diff --git a/legacyworlds-server/legacyworlds-server-main/data/buildables.xml b/legacyworlds-server-main/data/buildables.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/buildables.xml
rename to legacyworlds-server-main/data/buildables.xml
diff --git a/legacyworlds-server/legacyworlds-server-main/data/buildables.xsd b/legacyworlds-server-main/data/buildables.xsd
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/buildables.xsd
rename to legacyworlds-server-main/data/buildables.xsd
diff --git a/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xml b/legacyworlds-server-main/data/i18n-text.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/i18n-text.xml
rename to legacyworlds-server-main/data/i18n-text.xml
diff --git a/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xsd b/legacyworlds-server-main/data/i18n-text.xsd
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/i18n-text.xsd
rename to legacyworlds-server-main/data/i18n-text.xsd
diff --git a/legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-en.txt b/legacyworlds-server-main/data/inactivityQuitMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-en.txt
rename to legacyworlds-server-main/data/inactivityQuitMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-fr.txt b/legacyworlds-server-main/data/inactivityQuitMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-fr.txt
rename to legacyworlds-server-main/data/inactivityQuitMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-en.txt b/legacyworlds-server-main/data/inactivityWarningMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-en.txt
rename to legacyworlds-server-main/data/inactivityWarningMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-fr.txt b/legacyworlds-server-main/data/inactivityWarningMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-fr.txt
rename to legacyworlds-server-main/data/inactivityWarningMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/messageMail-en.txt b/legacyworlds-server-main/data/messageMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/messageMail-en.txt
rename to legacyworlds-server-main/data/messageMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/messageMail-fr.txt b/legacyworlds-server-main/data/messageMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/messageMail-fr.txt
rename to legacyworlds-server-main/data/messageMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-en.txt b/legacyworlds-server-main/data/passwordRecoveryMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-en.txt
rename to legacyworlds-server-main/data/passwordRecoveryMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-fr.txt b/legacyworlds-server-main/data/passwordRecoveryMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-fr.txt
rename to legacyworlds-server-main/data/passwordRecoveryMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/quitMail-en.txt b/legacyworlds-server-main/data/quitMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/quitMail-en.txt
rename to legacyworlds-server-main/data/quitMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/quitMail-fr.txt b/legacyworlds-server-main/data/quitMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/quitMail-fr.txt
rename to legacyworlds-server-main/data/quitMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/reactivationMail-en.txt b/legacyworlds-server-main/data/reactivationMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/reactivationMail-en.txt
rename to legacyworlds-server-main/data/reactivationMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/reactivationMail-fr.txt b/legacyworlds-server-main/data/reactivationMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/reactivationMail-fr.txt
rename to legacyworlds-server-main/data/reactivationMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/recapMail-en.txt b/legacyworlds-server-main/data/recapMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/recapMail-en.txt
rename to legacyworlds-server-main/data/recapMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/recapMail-fr.txt b/legacyworlds-server-main/data/recapMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/recapMail-fr.txt
rename to legacyworlds-server-main/data/recapMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/registrationMail-en.txt b/legacyworlds-server-main/data/registrationMail-en.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/registrationMail-en.txt
rename to legacyworlds-server-main/data/registrationMail-en.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/registrationMail-fr.txt b/legacyworlds-server-main/data/registrationMail-fr.txt
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/registrationMail-fr.txt
rename to legacyworlds-server-main/data/registrationMail-fr.txt
diff --git a/legacyworlds-server/legacyworlds-server-main/data/techs-test.xml b/legacyworlds-server-main/data/techs-test.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/techs-test.xml
rename to legacyworlds-server-main/data/techs-test.xml
diff --git a/legacyworlds-server/legacyworlds-server-main/data/techs.xml b/legacyworlds-server-main/data/techs.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/techs.xml
rename to legacyworlds-server-main/data/techs.xml
diff --git a/legacyworlds-server/legacyworlds-server-main/data/techs.xsd b/legacyworlds-server-main/data/techs.xsd
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/data/techs.xsd
rename to legacyworlds-server-main/data/techs.xsd
diff --git a/legacyworlds-server/legacyworlds-server-main/pom.xml b/legacyworlds-server-main/pom.xml
similarity index 65%
rename from legacyworlds-server/legacyworlds-server-main/pom.xml
rename to legacyworlds-server-main/pom.xml
index 6774468..5f28b4b 100644
--- a/legacyworlds-server/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server-main/pom.xml
@@ -4,74 +4,61 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-main</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds server</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Server - Main executable</name>
 	<description>Server main classes and JAR builder.</description>
 
 	<dependencies>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-accounts</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-bt</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-eventlog</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-i18n</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-mailer</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-naming</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-simple</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-system</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-user</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>com.thoughtworks.xstream</groupId>
 			<artifactId>xstream</artifactId>
-			<version>${com.thoughtworks.xstream.version}</version>
-			<type>jar</type>
 		</dependency>
 
 		<dependency>
 			<groupId>postgresql</groupId>
 			<artifactId>postgresql</artifactId>
-			<version>8.4-701.jdbc4</version>
-			<type>jar</type>
 			<scope>runtime</scope>
 		</dependency>
 
@@ -80,9 +67,7 @@
 	<build>
 		<plugins>
 			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-jar-plugin</artifactId>
-				<version>2.2</version>
 				<executions>
 					<execution>
 						<id>default-jar</id>
@@ -102,25 +87,6 @@
 					</execution>
 				</executions>
 			</plugin>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-dependency-plugin</artifactId>
-				<executions>
-					<execution>
-						<id>copy-dependencies</id>
-						<phase>package</phase>
-						<goals>
-							<goal>copy-dependencies</goal>
-						</goals>
-						<configuration>
-							<outputDirectory>${project.build.directory}/lib</outputDirectory>
-							<overWriteReleases>false</overWriteReleases>
-							<overWriteSnapshots>false</overWriteSnapshots>
-							<overWriteIfNewer>true</overWriteIfNewer>
-						</configuration>
-					</execution>
-				</executions>
-			</plugin>
 		</plugins>
 	</build>
 
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/Main.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/Main.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/Main.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/Main.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CLITool.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CLITool.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CLITool.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CLITool.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateUser.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateUser.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateUser.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateUser.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ExportDB.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ExportDB.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ExportDB.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ExportDB.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/Stop.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/Stop.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/Stop.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/Stop.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/Tick.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/Tick.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/Tick.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/Tick.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ToolBase.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ToolBase.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ToolBase.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ToolBase.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/Administrator.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/Administrator.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/Administrator.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/Administrator.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREComment.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREComment.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREComment.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREComment.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREInitialReport.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREInitialReport.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREInitialReport.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREInitialReport.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREMerger.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREMerger.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREMerger.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREMerger.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREStatusChange.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREStatusChange.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREStatusChange.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREStatusChange.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREVisibilityChange.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREVisibilityChange.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREVisibilityChange.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BREVisibilityChange.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugEventMapper.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugEventMapper.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugEventMapper.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugEventMapper.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugGroup.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugGroup.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugGroup.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugGroup.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugReportEvent.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugReportEvent.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugReportEvent.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugReportEvent.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugReports.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugReports.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugReports.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugReports.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugSubmitter.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugSubmitter.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugSubmitter.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/BugSubmitter.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/LegacyWorldsDB.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/LegacyWorldsDB.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/LegacyWorldsDB.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/LegacyWorldsDB.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/User.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/User.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/User.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/User.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/UserMapper.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/UserMapper.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/UserMapper.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/UserMapper.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/Warnings.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/Warnings.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/Warnings.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/dbexport/Warnings.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/LogAppender.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/LogAppender.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/LogAppender.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/LogAppender.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/Server.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/Server.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/Server.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/Server.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/ServerTerminator.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/ServerTerminator.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/ServerTerminator.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/ServerTerminator.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/ServerTerminatorBean.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/ServerTerminatorBean.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/ServerTerminatorBean.java
rename to legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/ServerTerminatorBean.java
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/resources/configuration/context-configuration.xml b/legacyworlds-server-main/src/main/resources/configuration/context-configuration.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/resources/configuration/context-configuration.xml
rename to legacyworlds-server-main/src/main/resources/configuration/context-configuration.xml
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/resources/log4j.properties b/legacyworlds-server-main/src/main/resources/log4j.properties
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/resources/log4j.properties
rename to legacyworlds-server-main/src/main/resources/log4j.properties
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/resources/lw-server.xml b/legacyworlds-server-main/src/main/resources/lw-server.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-main/src/main/resources/lw-server.xml
rename to legacyworlds-server-main/src/main/resources/lw-server.xml
diff --git a/legacyworlds-server-main/src/test/java/.empty b/legacyworlds-server-main/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-main/src/test/resources/.empty b/legacyworlds-server-main/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/legacyworlds-server-tests/pom.xml b/legacyworlds-server-tests/pom.xml
similarity index 76%
rename from legacyworlds-server/legacyworlds-server-tests/pom.xml
rename to legacyworlds-server-tests/pom.xml
index ccf4b6b..d1b1438 100644
--- a/legacyworlds-server/legacyworlds-server-tests/pom.xml
+++ b/legacyworlds-server-tests/pom.xml
@@ -4,72 +4,61 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-tests</artifactId>
-	<name>Legacy Worlds server tests</name>
-	<version>5.99.1</version>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Server - Tests (OH WAIT!)</name>
 	<description>This package regroups all tests for server capabilities.</description>
 
 	<dependencies>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-accounts</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-bt</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-eventlog</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-i18n</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-mailer</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-naming</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-simple</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-system</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-user</artifactId>
 			<groupId>com.deepclone.lw</groupId>
-			<version>${project.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
-			<version>${junit.version}</version>
 			<scope>test</scope>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-test</artifactId>
-			<version>${org.springframework.version}</version>
 			<scope>test</scope>
 		</dependency>
 	</dependencies>
diff --git a/legacyworlds-server/legacyworlds-server-utils/pom.xml b/legacyworlds-server-utils/pom.xml
similarity index 71%
rename from legacyworlds-server/legacyworlds-server-utils/pom.xml
rename to legacyworlds-server-utils/pom.xml
index 492a2e1..6925d1c 100644
--- a/legacyworlds-server/legacyworlds-server-utils/pom.xml
+++ b/legacyworlds-server-utils/pom.xml
@@ -4,20 +4,19 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-utils</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds server utility classes</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Server - Utility classes</name>
 	<description>This package contains utility classes used by various parts of the server-side code.</description>
 
 	<dependencies>
 		<dependency>
 			<groupId>com.deepclone.lw</groupId>
 			<artifactId>legacyworlds-utils</artifactId>
-			<version>${project.version}</version>
 		</dependency>
 	</dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/Base64Serializer.java b/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/Base64Serializer.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/Base64Serializer.java
rename to legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/Base64Serializer.java
diff --git a/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/EmailAddress.java b/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/EmailAddress.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/EmailAddress.java
rename to legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/EmailAddress.java
diff --git a/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/Password.java b/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/Password.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/Password.java
rename to legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/Password.java
diff --git a/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/RandomStringGenerator.java b/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/RandomStringGenerator.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/RandomStringGenerator.java
rename to legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/RandomStringGenerator.java
diff --git a/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/StoredProc.java b/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/StoredProc.java
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/StoredProc.java
rename to legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/StoredProc.java
diff --git a/legacyworlds-server-utils/src/main/resources/.empty b/legacyworlds-server-utils/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-utils/src/test/java/.empty b/legacyworlds-server-utils/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-utils/src/test/resources/.empty b/legacyworlds-server-utils/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server/.project b/legacyworlds-server/.project
deleted file mode 100644
index 5ff6ca3..0000000
--- a/legacyworlds-server/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/.project b/legacyworlds-server/legacyworlds-server-beans/.project
deleted file mode 100644
index 7f3bd93..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.project
deleted file mode 100644
index a5031a0..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-accounts</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index cb25da1..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 09 10:19:20 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.project
deleted file mode 100644
index 6701380..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-bt</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 1e525bc..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Tue Apr 13 08:30:06 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index 12bb0a0..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Tue Apr 13 08:30:04 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.project
deleted file mode 100644
index e52eaf0..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-eventlog</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 82cc298..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 09 10:19:28 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.project
deleted file mode 100644
index dffe14c..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-i18n</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 9640d22..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 09 10:19:36 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/pom.xml
deleted file mode 100644
index 26ad335..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/pom.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<artifactId>legacyworlds-server-beans</artifactId>
-		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
-	</parent>
-
-	<groupId>com.deepclone.lw</groupId>
-	<artifactId>legacyworlds-server-beans-i18n</artifactId>
-	<name>Legacy Worlds internationalisation</name>
-	<version>5.99.1</version>
-	<description>This package defines the two beans which control server-side internationalised text management.</description>
-
-</project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.project
deleted file mode 100644
index 1c8b8cf..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-mailer</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 0a8422b..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 09 10:19:43 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.project
deleted file mode 100644
index f7bc35d..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-naming</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index d627e17..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Thu Feb 25 09:52:02 CET 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index 8e6c298..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Thu Feb 25 09:52:02 CET 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.project
deleted file mode 100644
index e30c96f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-simple</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index a82c1db..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Thu Apr 22 11:23:34 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index fa121cb..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Thu Apr 22 11:23:34 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/pom.xml
deleted file mode 100644
index e1929cf..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/pom.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <artifactId>legacyworlds-server-beans</artifactId>
-    <groupId>com.deepclone.lw</groupId>
-    <version>5.99.1</version>
-  </parent>
-  <groupId>com.deepclone.lw</groupId>
-  <artifactId>legacyworlds-server-beans-simple</artifactId>
-  <version>5.99.1</version>
-  <name>Legacy Worlds simple game</name>
-  <description>This module contains code that corresponds to a simple "placeholder" game. This code should become obsolete over time, as it is being replaced with actual LWB6 code, until the module can finally be removed.</description>
-</project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.project
deleted file mode 100644
index 8351fb8..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-system</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 35c2ee3..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Tue Feb 23 12:51:19 CET 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index b7fd985..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Tue Feb 23 12:51:19 CET 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.project
deleted file mode 100644
index b2adbb1..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-beans-user</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index ce751ed..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Wed Apr 14 12:43:45 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index 6b7d8f9..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Wed Apr 14 12:43:44 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-server/legacyworlds-server-data/.classpath b/legacyworlds-server/legacyworlds-server-data/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-data/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-data/.project b/legacyworlds-server/legacyworlds-server-data/.project
deleted file mode 100644
index e0c56e5..0000000
--- a/legacyworlds-server/legacyworlds-server-data/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-data</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-data/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-data/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index b66e1f0..0000000
--- a/legacyworlds-server/legacyworlds-server-data/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 09 10:19:59 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/test-mode.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/test-mode.sql
deleted file mode 100644
index 777b268..0000000
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/test-mode.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-INSERT INTO admin.administrators (appear_as, pass_md5 , pass_sha1, privileges)
-	VALUES ( 'Test' , '...' , '...' , 0 );
-SELECT sys.set_constant( 'game.growthFactor' , 200 , 1 );
-SELECT sys.set_constant( 'game.battle.damage' , 0.05 , 1 );
-SELECT sys.set_constant( 'game.battle.timeToFullIntensity' , 5 , 1 );
-SELECT sys.set_constant( 'map.names.minDelay' , 1 , 1 );
-SELECT sys.set_constant( 'game.work.wuPerPopUnit' , 0.5 , 1 );
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/.classpath b/legacyworlds-server/legacyworlds-server-interfaces/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-interfaces/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/.project b/legacyworlds-server/legacyworlds-server-interfaces/.project
deleted file mode 100644
index 25a4794..0000000
--- a/legacyworlds-server/legacyworlds-server-interfaces/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-interfaces</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-interfaces/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 847aea3..0000000
--- a/legacyworlds-server/legacyworlds-server-interfaces/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 09 10:20:04 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-main/.classpath b/legacyworlds-server/legacyworlds-server-main/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-main/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-main/.project b/legacyworlds-server/legacyworlds-server-main/.project
deleted file mode 100644
index 4fabc51..0000000
--- a/legacyworlds-server/legacyworlds-server-main/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-main</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-main/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-main/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index deacd36..0000000
--- a/legacyworlds-server/legacyworlds-server-main/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Thu Apr 15 09:16:34 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-main/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-main/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index 1a8e041..0000000
--- a/legacyworlds-server/legacyworlds-server-main/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Thu Apr 15 09:16:34 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-server/legacyworlds-server-main/hibernate.xml b/legacyworlds-server/legacyworlds-server-main/hibernate.xml
deleted file mode 100644
index 03001ba..0000000
--- a/legacyworlds-server/legacyworlds-server-main/hibernate.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-	<!-- Legacy Worlds Beta 6 - Default data source configuration -->
-	<!--
-		This file is provided as an example on how to configure the game
-		server's data sources. It configures a memory-based HSQL database,
-		initialising its schemas and structure.
-	-->
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="hibernateProperties"
-		class="org.springframework.beans.factory.config.PropertiesFactoryBean">
-		<property name="properties">
-			<props>
-				<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
-				<prop key="hibernate.jdbc.batch_size">0</prop> 
-				<prop key="hibernate.show_sql">true</prop>
-				<prop key="hibernate.format_sql">true</prop><!-- -->
-			</props>
-		</property>
-	</bean>
-
-</beans>
diff --git a/legacyworlds-server/legacyworlds-server-tests/.classpath b/legacyworlds-server/legacyworlds-server-tests/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-tests/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-tests/.project b/legacyworlds-server/legacyworlds-server-tests/.project
deleted file mode 100644
index 076ecb4..0000000
--- a/legacyworlds-server/legacyworlds-server-tests/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-tests</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-tests/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-tests/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 96c9dfc..0000000
--- a/legacyworlds-server/legacyworlds-server-tests/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 09 10:20:32 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-utils/.classpath b/legacyworlds-server/legacyworlds-server-utils/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-server/legacyworlds-server-utils/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-utils/.project b/legacyworlds-server/legacyworlds-server-utils/.project
deleted file mode 100644
index fc174e7..0000000
--- a/legacyworlds-server/legacyworlds-server-utils/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-server-utils</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-utils/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-utils/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 6939a66..0000000
--- a/legacyworlds-server/legacyworlds-server-utils/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 09 10:20:36 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/pom.xml b/legacyworlds-server/pom.xml
index db8aa26..5d5ec72 100644
--- a/legacyworlds-server/pom.xml
+++ b/legacyworlds-server/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds/pom.xml</relativePath>
 	</parent>
-	<groupId>com.deepclone.lw</groupId>
+
 	<artifactId>legacyworlds-server</artifactId>
-	<name>Legacy Worlds server</name>
-	<version>5.99.1</version>
+	<name>Legacy Worlds - Server</name>
 	<packaging>pom</packaging>
 	<description>This metapackage is the root of the game server's components' code.</description>
 
@@ -18,68 +18,53 @@
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-core</artifactId>
-			<version>${org.springframework.version}</version>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-context</artifactId>
-			<version>${org.springframework.version}</version>
-			<exclusions>
-				<exclusion>
-					<groupId>commons-logging</groupId>
-					<artifactId>commons-logging</artifactId>
-				</exclusion>
-			</exclusions>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-jdbc</artifactId>
-			<version>${org.springframework.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>jcl-over-slf4j</artifactId>
-			<version>${org.slf4j.version}</version>
 		</dependency>
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
-			<version>${org.slf4j.version}</version>
 		</dependency>
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-log4j12</artifactId>
-			<version>${org.slf4j.version}</version>
 		</dependency>
-
 		<dependency>
 			<groupId>log4j</groupId>
 			<artifactId>log4j</artifactId>
-			<version>${log4j.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>commons-dbcp</groupId>
 			<artifactId>commons-dbcp</artifactId>
-			<version>${commons.dbcp.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>cglib</groupId>
 			<artifactId>cglib</artifactId>
-			<version>${cglib.version}</version>
 		</dependency>
 
 	</dependencies>
 
 	<modules>
-		<module>legacyworlds-server-data</module>
-		<module>legacyworlds-server-beans</module>
-		<module>legacyworlds-server-tests</module>
-		<module>legacyworlds-server-interfaces</module>
-		<module>legacyworlds-server-utils</module>
-		<module>legacyworlds-server-main</module>
+		<module>../legacyworlds-server-data</module>
+		<module>../legacyworlds-server-beans</module>
+		<module>../legacyworlds-server-tests</module>
+		<module>../legacyworlds-server-interfaces</module>
+		<module>../legacyworlds-server-utils</module>
+		<module>../legacyworlds-server-main</module>
+		<module>../legacyworlds-server-DIST</module>
 	</modules>
 
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-session/.classpath b/legacyworlds-session/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-session/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-session/.project b/legacyworlds-session/.project
deleted file mode 100644
index a0b0b33..0000000
--- a/legacyworlds-session/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-session</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-session/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-session/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 8d7d4ea..0000000
--- a/legacyworlds-session/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Tue Apr 13 16:03:03 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-session/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-session/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index 83991fa..0000000
--- a/legacyworlds-session/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Tue Apr 13 16:03:03 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-session/pom.xml b/legacyworlds-session/pom.xml
index 3ea53f7..7169c86 100644
--- a/legacyworlds-session/pom.xml
+++ b/legacyworlds-session/pom.xml
@@ -4,13 +4,13 @@
 	<parent>
 		<artifactId>legacyworlds</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-session</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds sessions</name>
+	<name>Legacy Worlds - Sessions</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 	<description>This module contains the definition of sessions used in client-server communications and all related classes and exceptions.</description>
 
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-session/src/main/resources/.empty b/legacyworlds-session/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-session/src/test/java/.empty b/legacyworlds-session/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-session/src/test/resources/.empty b/legacyworlds-session/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-utils/.classpath b/legacyworlds-utils/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-utils/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-utils/.project b/legacyworlds-utils/.project
deleted file mode 100644
index fd839fa..0000000
--- a/legacyworlds-utils/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-utils</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-utils/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-utils/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 878d24f..0000000
--- a/legacyworlds-utils/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Mon Apr 19 12:23:34 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-utils/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-utils/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index 762f9e9..0000000
--- a/legacyworlds-utils/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Mon Apr 19 12:23:33 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-utils/pom.xml b/legacyworlds-utils/pom.xml
index 195d0d6..34b6e3e 100644
--- a/legacyworlds-utils/pom.xml
+++ b/legacyworlds-utils/pom.xml
@@ -4,21 +4,19 @@
 	<parent>
 		<artifactId>legacyworlds</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-utils</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds common utilities</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Common utilities</name>
 	<description>The classes in this package are used by all parts of the Legacy Worlds code.</description>
 
 	<dependencies>
 		<dependency>
 			<groupId>commons-codec</groupId>
 			<artifactId>commons-codec</artifactId>
-			<version>${commons.codec.version}</version>
-			<type>jar</type>
 		</dependency>
 	</dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-utils/src/main/resources/.empty b/legacyworlds-utils/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-utils/src/test/java/.empty b/legacyworlds-utils/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-utils/src/test/resources/.empty b/legacyworlds-utils/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-admin/Content/Filtered/fm/version.ftl b/legacyworlds-web-admin/Content/Filtered/fm/version.ftl
new file mode 100644
index 0000000..c3974e0
--- /dev/null
+++ b/legacyworlds-web-admin/Content/Filtered/fm/version.ftl
@@ -0,0 +1,2 @@
+<#macro version>${legacyworlds.version.string}</#macro>
+<#macro full_version>Beta 6 ${legacyworlds.version.string} (${legacyworlds.version.main}.${legacyworlds.version.release} b${legacyworlds.version.build})</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/META-INF/MANIFEST.MF b/legacyworlds-web-admin/Content/Raw/META-INF/MANIFEST.MF
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/META-INF/MANIFEST.MF
rename to legacyworlds-web-admin/Content/Raw/META-INF/MANIFEST.MF
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/admin-servlet.xml b/legacyworlds-web-admin/Content/Raw/WEB-INF/admin-servlet.xml
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/admin-servlet.xml
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/admin-servlet.xml
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/ROOT.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/ROOT.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/ROOT.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/ROOT.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/containers/external.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/containers/external.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/containers/external.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/containers/external.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/containers/internal.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/containers/internal.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/containers/internal.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/containers/internal.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/columns.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/columns.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/columns.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/columns.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/datatable.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/datatable.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/datatable.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/datatable.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/fields.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/fields.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/fields.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/fields.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/form.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/form.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/form.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/form.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/lists.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/lists.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/lists.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/lists.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/tabs.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/tabs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/layout/tabs.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/layout/tabs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/addAdmin.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/addAdmin.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/addAdmin.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/addAdmin.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/admins.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/admins.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/admins.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/admins.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/banReject.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/banReject.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/banReject.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/banReject.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/banRequest.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/banRequest.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/banRequest.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/banRequest.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bans.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bans.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bans.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bans.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bansSummary.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bansSummary.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bansSummary.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bansSummary.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsList.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsList.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsList.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsList.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsReport.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsReport.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsReport.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsReport.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsSummary.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsSummary.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsSummary.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsSummary.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsTabs.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsTabs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsTabs.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsTabs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsView.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsView.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/bugsView.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/bugsView.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/changePassword.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/changePassword.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/changePassword.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/changePassword.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/constants.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/constants.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/constants.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/constants.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/language.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/language.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/language.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/language.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/languages.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/languages.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/languages.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/languages.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/logEntry.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/logEntry.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/logEntry.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/logEntry.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/login.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/login.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/login.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/login.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/logs.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/logs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/logs.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/logs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/main.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/main.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/main.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/main.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/maintenance.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/maintenance.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/maintenance.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/maintenance.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/message.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/message.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/message.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/message.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/messageBox.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/messageBox.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/messageBox.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/messageBox.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/messageTabs.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/messageTabs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/messageTabs.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/messageTabs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/messageWriter.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/messageWriter.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/messageWriter.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/messageWriter.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/names.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/names.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/names.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/names.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/namesSummary.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/namesSummary.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/namesSummary.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/namesSummary.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/offline.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/offline.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/offline.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/offline.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/preferences.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/preferences.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/preferences.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/preferences.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/resetAdmin.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/resetAdmin.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/resetAdmin.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/resetAdmin.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/spam.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/spam.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/spam.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/spam.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/ticker.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/ticker.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/ticker.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/ticker.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/user.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/user.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/user.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/user.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/userSessions.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/userSessions.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/userSessions.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/userSessions.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/users.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/users.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/users.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/users.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/viewAdmin.ftl b/legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/viewAdmin.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/viewAdmin.ftl
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/fm/types/viewAdmin.ftl
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/web.xml b/legacyworlds-web-admin/Content/Raw/WEB-INF/web.xml
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/web.xml
rename to legacyworlds-web-admin/Content/Raw/WEB-INF/web.xml
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/css/main.css b/legacyworlds-web-admin/Content/Raw/css/main.css
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/WebContent/css/main.css
rename to legacyworlds-web-admin/Content/Raw/css/main.css
diff --git a/legacyworlds-web-admin/pom.xml b/legacyworlds-web-admin/pom.xml
new file mode 100644
index 0000000..a126184
--- /dev/null
+++ b/legacyworlds-web-admin/pom.xml
@@ -0,0 +1,51 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds-web</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-web/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-web-admin</artifactId>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+
+	<packaging>war</packaging>
+	<name>Legacy Worlds - Web - Administration</name>
+
+	<dependencies>
+		<dependency>
+			<groupId>com.deepclone.lw</groupId>
+			<artifactId>legacyworlds-web-beans</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.freemarker</groupId>
+			<artifactId>freemarker</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>com.thoughtworks.xstream</groupId>
+			<artifactId>xstream</artifactId>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-war-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>tomcat-maven-plugin</artifactId>
+				<configuration>
+					<path>/lwadmin</path>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/BanhammerPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/BanhammerPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/BanhammerPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/BanhammerPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/BugTrackerPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/BugTrackerPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/BugTrackerPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/BugTrackerPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/ConstantsPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/ConstantsPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/ConstantsPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/ConstantsPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/ErrorHandlerBean.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/ErrorHandlerBean.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/ErrorHandlerBean.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/ErrorHandlerBean.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/I18NPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/I18NPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/I18NPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/I18NPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/LogPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/LogPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/LogPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/LogPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/LoginPage.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/LoginPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/LoginPage.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/LoginPage.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MaintenancePages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MaintenancePages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MaintenancePages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MaintenancePages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MessageBoxView.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MessageBoxView.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MessageBoxView.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MessageBoxView.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MessagesPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MessagesPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MessagesPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/MessagesPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/NamesPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/NamesPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/NamesPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/NamesPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/PasswordPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/PasswordPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/PasswordPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/PasswordPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/PreferencesPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/PreferencesPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/PreferencesPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/PreferencesPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SessionPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SessionPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SessionPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SessionPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SpamPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SpamPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SpamPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SpamPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SuperUserPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SuperUserPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SuperUserPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/SuperUserPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/TickerPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/TickerPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/TickerPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/TickerPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/UsersPages.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/UsersPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/UsersPages.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/UsersPages.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/i18ne/LanguageExport.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/i18ne/LanguageExport.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/i18ne/LanguageExport.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/i18ne/LanguageExport.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/i18ne/StringExport.java b/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/i18ne/StringExport.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/i18ne/StringExport.java
rename to legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/i18ne/StringExport.java
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/resources/log4j.properties b/legacyworlds-web-admin/src/main/resources/log4j.properties
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-admin/src/main/resources/log4j.properties
rename to legacyworlds-web-admin/src/main/resources/log4j.properties
diff --git a/legacyworlds-web-admin/src/test/java/.empty b/legacyworlds-web-admin/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-admin/src/test/resources/.empty b/legacyworlds-web-admin/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web/legacyworlds-web-beans/pom.xml b/legacyworlds-web-beans/pom.xml
similarity index 72%
rename from legacyworlds-web/legacyworlds-web-beans/pom.xml
rename to legacyworlds-web-beans/pom.xml
index 5560487..07e567e 100644
--- a/legacyworlds-web/legacyworlds-web-beans/pom.xml
+++ b/legacyworlds-web-beans/pom.xml
@@ -4,13 +4,13 @@
 	<parent>
 		<artifactId>legacyworlds-web</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-web/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-web-beans</artifactId>
-	<version>5.99.1</version>
-	<name>Legacy Worlds common web beans</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Web - Common components</name>
 	<description>Module for common web-related beans. </description>
 	<packaging>jar</packaging>
 
@@ -18,53 +18,43 @@
 		<dependency>
 			<groupId>com.deepclone.lw</groupId>
 			<artifactId>legacyworlds-session</artifactId>
-			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<groupId>com.deepclone.lw</groupId>
 			<artifactId>legacyworlds-utils</artifactId>
-			<version>${project.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-core</artifactId>
-			<version>${org.springframework.version}</version>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-context</artifactId>
-			<version>${org.springframework.version}</version>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-webmvc</artifactId>
-			<version>${org.springframework.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>log4j</groupId>
 			<artifactId>log4j</artifactId>
-			<version>${log4j.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>commons-dbcp</groupId>
 			<artifactId>commons-dbcp</artifactId>
-			<version>${commons.dbcp.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>cglib</groupId>
 			<artifactId>cglib</artifactId>
-			<version>${cglib.version}</version>
 		</dependency>
 
 		<dependency>
 			<groupId>javax.servlet</groupId>
 			<artifactId>servlet-api</artifactId>
-			<version>2.5</version>
-			<scope>provided</scope>
 		</dependency>
 	</dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/IEContentTypeBean.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/IEContentTypeBean.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/IEContentTypeBean.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/IEContentTypeBean.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/LanguageInterceptorBean.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/LanguageInterceptorBean.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/LanguageInterceptorBean.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/LanguageInterceptorBean.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/SessionInterceptorBean.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/SessionInterceptorBean.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/SessionInterceptorBean.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/SessionInterceptorBean.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/SessionRequirement.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/SessionRequirement.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/SessionRequirement.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/intercept/SessionRequirement.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatter.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatter.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatter.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatter.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatterBean.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatterBean.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatterBean.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/msgs/MessageFormatterBean.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/ClientSessionReference.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/ClientSessionReference.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/ClientSessionReference.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/ClientSessionReference.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatus.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatus.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatus.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatus.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatusBean.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatusBean.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatusBean.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/MaintenanceStatusBean.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/Session.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/Session.java
similarity index 97%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/Session.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/Session.java
index b826f25..81e6a3a 100644
--- a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/Session.java
+++ b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/Session.java
@@ -11,7 +11,6 @@ import com.deepclone.lw.session.SessionException;
 public abstract class Session
 {
 
-	private static final long serialVersionUID = 1L;
 	private ClientSessionReference reference;
 	private SessionClient sClient;
 
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionClient.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionClient.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionClient.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionClient.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionClientBean.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionClientBean.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionClientBean.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionClientBean.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionMaintenanceException.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionMaintenanceException.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionMaintenanceException.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionMaintenanceException.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionServerException.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionServerException.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionServerException.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionServerException.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionType.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionType.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionType.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/session/SessionType.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/BugTrackerBase.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/BugTrackerBase.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/BugTrackerBase.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/BugTrackerBase.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/Page.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/Page.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/Page.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/Page.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/PageControllerBase.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/PageControllerBase.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/PageControllerBase.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/beans/view/PageControllerBase.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/AdminSession.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/AdminSession.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/AdminSession.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/AdminSession.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/ExternalSession.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/ExternalSession.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/ExternalSession.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/ExternalSession.java
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
rename to legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
diff --git a/legacyworlds-web-beans/src/main/resources/.empty b/legacyworlds-web-beans/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-beans/src/test/java/.empty b/legacyworlds-web-beans/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-beans/src/test/resources/.empty b/legacyworlds-web-beans/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-main/Content/Filtered/fm/version.ftl b/legacyworlds-web-main/Content/Filtered/fm/version.ftl
new file mode 100644
index 0000000..c3974e0
--- /dev/null
+++ b/legacyworlds-web-main/Content/Filtered/fm/version.ftl
@@ -0,0 +1,2 @@
+<#macro version>${legacyworlds.version.string}</#macro>
+<#macro full_version>Beta 6 ${legacyworlds.version.string} (${legacyworlds.version.main}.${legacyworlds.version.release} b${legacyworlds.version.build})</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/META-INF/MANIFEST.MF b/legacyworlds-web-main/Content/Raw/META-INF/MANIFEST.MF
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/META-INF/MANIFEST.MF
rename to legacyworlds-web-main/Content/Raw/META-INF/MANIFEST.MF
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/ROOT.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/ROOT.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/ROOT.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/ROOT.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/chat.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/chat.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/chat.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/chat.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/external.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/external.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/external.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/external.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/game.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/offline.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/offline.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/offline.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/offline.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/restricted.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/restricted.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/containers/restricted.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/restricted.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/game.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/game.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/game.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/home.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/home.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/home.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/home.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/loggedOut.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/loggedOut.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/loggedOut.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/loggedOut.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/loginFailed.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/loginFailed.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/loginFailed.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/loginFailed.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/noSession.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/noSession.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/noSession.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/noSession.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/passwordRecoveryOk.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/passwordRecoveryOk.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/passwordRecoveryOk.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/passwordRecoveryOk.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/reactivate.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/reactivate.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/reactivate.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/reactivate.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/rules.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/rules.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/rules.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/rules.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/scope.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/scope.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/scope.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/static/scope.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/account.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/account.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/account.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/account.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/alliance.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/alliance.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/alliance.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/alliance.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/banned.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/banned.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/banned.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/banned.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/battle.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/battle.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/battle.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/battle.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/battles.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/battles.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/battles.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/battles.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/bugsList.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/bugsList.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/bugsList.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/bugsList.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/bugsReport.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/bugsReport.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/bugsReport.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/bugsReport.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/bugsTabs.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/bugsTabs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/bugsTabs.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/bugsTabs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/bugsView.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/bugsView.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/bugsView.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/bugsView.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/chat.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/chat.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/chat.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/chat.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/enemies.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/enemies.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/enemies.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/enemies.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/fleets.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/fleets.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/fleets.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/fleets.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/fleetsCommand.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/fleetsCommand.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/fleetsCommand.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/fleetsCommand.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/getNewPlanet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/getNewPlanet.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/getNewPlanet.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/getNewPlanet.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/maintenance.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/maintenance.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/maintenance.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/maintenance.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/map.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/map.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/map.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/map.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/message.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/message.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/message.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/message.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/messageBox.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/messageBox.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/messageBox.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/messageBox.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/messageTabs.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/messageTabs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/messageTabs.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/messageTabs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/messageTargets.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/messageTargets.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/messageTargets.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/messageTargets.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/messageWriter.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/messageWriter.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/messageWriter.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/messageWriter.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/offline.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/offline.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/offline.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/offline.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/overview.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/passwordRecovery.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/passwordRecovery.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/passwordRecovery.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/passwordRecovery.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/planet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/planet.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/planets.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/planets.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/reactivation.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/reactivation.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/reactivation.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/reactivation.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/register.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/register.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/register.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/register.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/registered.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/registered.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/registered.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/registered.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/splitFleet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/splitFleet.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/splitFleet.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/splitFleet.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/static.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/static.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/static.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/static.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/validation.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/validation.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/validation.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/validation.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/chat.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/chat.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/chat.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/chat.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/external.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/external.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/external.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/external.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/game.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/offline.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/offline.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/offline.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/offline.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/restricted.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/restricted.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/containers/restricted.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/restricted.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/game.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/game.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/game.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/home.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/home.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/home.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/home.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/loggedOut.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/loggedOut.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/loggedOut.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/loggedOut.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/loginFailed.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/loginFailed.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/loginFailed.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/loginFailed.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/noSession.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/noSession.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/noSession.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/noSession.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/passwordRecoveryOk.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/passwordRecoveryOk.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/passwordRecoveryOk.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/passwordRecoveryOk.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/reactivate.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/reactivate.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/reactivate.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/reactivate.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/rules.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/rules.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/rules.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/rules.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/scope.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/scope.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/static/scope.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/static/scope.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/account.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/account.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/account.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/account.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/alliance.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/alliance.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/alliance.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/alliance.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/banned.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/banned.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/banned.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/banned.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/battle.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/battle.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/battle.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/battle.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/battles.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/battles.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/battles.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/battles.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/bugsList.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/bugsList.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/bugsList.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/bugsList.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/bugsReport.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/bugsReport.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/bugsReport.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/bugsReport.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/bugsTabs.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/bugsTabs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/bugsTabs.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/bugsTabs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/bugsView.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/bugsView.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/bugsView.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/bugsView.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/chat.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/chat.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/chat.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/chat.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/enemies.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/enemies.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/enemies.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/enemies.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/fleets.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/fleets.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/fleets.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/fleets.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/fleetsCommand.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/fleetsCommand.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/fleetsCommand.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/fleetsCommand.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/getNewPlanet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/getNewPlanet.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/getNewPlanet.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/getNewPlanet.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/maintenance.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/maintenance.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/maintenance.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/maintenance.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/map.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/map.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/map.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/map.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/message.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/message.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/message.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/message.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/messageBox.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/messageBox.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/messageBox.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/messageBox.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/messageTabs.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/messageTabs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/messageTabs.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/messageTabs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/messageTargets.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/messageTargets.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/messageTargets.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/messageTargets.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/messageWriter.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/messageWriter.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/messageWriter.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/messageWriter.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/offline.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/offline.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/offline.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/offline.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/overview.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/passwordRecovery.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/passwordRecovery.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/passwordRecovery.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/passwordRecovery.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/planet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/planet.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/planets.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/planets.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/reactivation.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/reactivation.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/reactivation.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/reactivation.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/register.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/register.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/register.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/register.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/registered.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/registered.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/registered.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/registered.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/splitFleet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/splitFleet.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/splitFleet.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/splitFleet.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/static.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/static.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/static.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/static.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/validation.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/validation.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/validation.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/validation.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/columns.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/columns.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/columns.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/columns.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/datatable.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/datatable.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/datatable.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/datatable.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/fields.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/fields.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/fields.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/fields.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/form.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/form.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/form.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/form.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/happiness.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/happiness.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/happiness.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/happiness.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/lists.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/lists.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/tabs.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/tabs.ftl
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/layout/tabs.ftl
rename to legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/tabs.ftl
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/main-servlet.xml b/legacyworlds-web-main/Content/Raw/WEB-INF/main-servlet.xml
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/main-servlet.xml
rename to legacyworlds-web-main/Content/Raw/WEB-INF/main-servlet.xml
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/web.xml b/legacyworlds-web-main/Content/Raw/WEB-INF/web.xml
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/web.xml
rename to legacyworlds-web-main/Content/Raw/WEB-INF/web.xml
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/css/main.css b/legacyworlds-web-main/Content/Raw/css/main.css
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/css/main.css
rename to legacyworlds-web-main/Content/Raw/css/main.css
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/background.jpg b/legacyworlds-web-main/Content/Raw/img/background.jpg
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/background.jpg
rename to legacyworlds-web-main/Content/Raw/img/background.jpg
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/button-0.png b/legacyworlds-web-main/Content/Raw/img/button-0.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/button-0.png
rename to legacyworlds-web-main/Content/Raw/img/button-0.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/button-1.png b/legacyworlds-web-main/Content/Raw/img/button-1.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/button-1.png
rename to legacyworlds-web-main/Content/Raw/img/button-1.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/button-2.png b/legacyworlds-web-main/Content/Raw/img/button-2.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/button-2.png
rename to legacyworlds-web-main/Content/Raw/img/button-2.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/button-3.png b/legacyworlds-web-main/Content/Raw/img/button-3.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/button-3.png
rename to legacyworlds-web-main/Content/Raw/img/button-3.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/button-4.png b/legacyworlds-web-main/Content/Raw/img/button-4.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/button-4.png
rename to legacyworlds-web-main/Content/Raw/img/button-4.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/button-5.png b/legacyworlds-web-main/Content/Raw/img/button-5.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/button-5.png
rename to legacyworlds-web-main/Content/Raw/img/button-5.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/button-6.png b/legacyworlds-web-main/Content/Raw/img/button-6.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/button-6.png
rename to legacyworlds-web-main/Content/Raw/img/button-6.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/1.png b/legacyworlds-web-main/Content/Raw/img/pp/l/1.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/1.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/1.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/10.png b/legacyworlds-web-main/Content/Raw/img/pp/l/10.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/10.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/10.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/100.png b/legacyworlds-web-main/Content/Raw/img/pp/l/100.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/100.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/100.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/101.png b/legacyworlds-web-main/Content/Raw/img/pp/l/101.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/101.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/101.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/102.png b/legacyworlds-web-main/Content/Raw/img/pp/l/102.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/102.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/102.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/103.png b/legacyworlds-web-main/Content/Raw/img/pp/l/103.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/103.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/103.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/104.png b/legacyworlds-web-main/Content/Raw/img/pp/l/104.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/104.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/104.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/105.png b/legacyworlds-web-main/Content/Raw/img/pp/l/105.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/105.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/105.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/106.png b/legacyworlds-web-main/Content/Raw/img/pp/l/106.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/106.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/106.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/107.png b/legacyworlds-web-main/Content/Raw/img/pp/l/107.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/107.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/107.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/108.png b/legacyworlds-web-main/Content/Raw/img/pp/l/108.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/108.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/108.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/109.png b/legacyworlds-web-main/Content/Raw/img/pp/l/109.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/109.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/109.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/11.png b/legacyworlds-web-main/Content/Raw/img/pp/l/11.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/11.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/11.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/110.png b/legacyworlds-web-main/Content/Raw/img/pp/l/110.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/110.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/110.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/111.png b/legacyworlds-web-main/Content/Raw/img/pp/l/111.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/111.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/111.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/112.png b/legacyworlds-web-main/Content/Raw/img/pp/l/112.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/112.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/112.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/113.png b/legacyworlds-web-main/Content/Raw/img/pp/l/113.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/113.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/113.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/114.png b/legacyworlds-web-main/Content/Raw/img/pp/l/114.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/114.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/114.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/115.png b/legacyworlds-web-main/Content/Raw/img/pp/l/115.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/115.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/115.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/116.png b/legacyworlds-web-main/Content/Raw/img/pp/l/116.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/116.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/116.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/117.png b/legacyworlds-web-main/Content/Raw/img/pp/l/117.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/117.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/117.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/118.png b/legacyworlds-web-main/Content/Raw/img/pp/l/118.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/118.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/118.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/119.png b/legacyworlds-web-main/Content/Raw/img/pp/l/119.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/119.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/119.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/12.png b/legacyworlds-web-main/Content/Raw/img/pp/l/12.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/12.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/12.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/120.png b/legacyworlds-web-main/Content/Raw/img/pp/l/120.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/120.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/120.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/121.png b/legacyworlds-web-main/Content/Raw/img/pp/l/121.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/121.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/121.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/122.png b/legacyworlds-web-main/Content/Raw/img/pp/l/122.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/122.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/122.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/123.png b/legacyworlds-web-main/Content/Raw/img/pp/l/123.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/123.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/123.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/124.png b/legacyworlds-web-main/Content/Raw/img/pp/l/124.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/124.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/124.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/125.png b/legacyworlds-web-main/Content/Raw/img/pp/l/125.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/125.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/125.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/126.png b/legacyworlds-web-main/Content/Raw/img/pp/l/126.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/126.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/126.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/127.png b/legacyworlds-web-main/Content/Raw/img/pp/l/127.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/127.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/127.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/128.png b/legacyworlds-web-main/Content/Raw/img/pp/l/128.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/128.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/128.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/129.png b/legacyworlds-web-main/Content/Raw/img/pp/l/129.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/129.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/129.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/13.png b/legacyworlds-web-main/Content/Raw/img/pp/l/13.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/13.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/13.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/130.png b/legacyworlds-web-main/Content/Raw/img/pp/l/130.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/130.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/130.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/131.png b/legacyworlds-web-main/Content/Raw/img/pp/l/131.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/131.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/131.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/132.png b/legacyworlds-web-main/Content/Raw/img/pp/l/132.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/132.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/132.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/133.png b/legacyworlds-web-main/Content/Raw/img/pp/l/133.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/133.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/133.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/134.png b/legacyworlds-web-main/Content/Raw/img/pp/l/134.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/134.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/134.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/135.png b/legacyworlds-web-main/Content/Raw/img/pp/l/135.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/135.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/135.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/136.png b/legacyworlds-web-main/Content/Raw/img/pp/l/136.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/136.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/136.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/137.png b/legacyworlds-web-main/Content/Raw/img/pp/l/137.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/137.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/137.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/138.png b/legacyworlds-web-main/Content/Raw/img/pp/l/138.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/138.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/138.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/139.png b/legacyworlds-web-main/Content/Raw/img/pp/l/139.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/139.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/139.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/14.png b/legacyworlds-web-main/Content/Raw/img/pp/l/14.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/14.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/14.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/140.png b/legacyworlds-web-main/Content/Raw/img/pp/l/140.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/140.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/140.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/141.png b/legacyworlds-web-main/Content/Raw/img/pp/l/141.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/141.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/141.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/142.png b/legacyworlds-web-main/Content/Raw/img/pp/l/142.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/142.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/142.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/143.png b/legacyworlds-web-main/Content/Raw/img/pp/l/143.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/143.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/143.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/144.png b/legacyworlds-web-main/Content/Raw/img/pp/l/144.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/144.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/144.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/145.png b/legacyworlds-web-main/Content/Raw/img/pp/l/145.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/145.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/145.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/146.png b/legacyworlds-web-main/Content/Raw/img/pp/l/146.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/146.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/146.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/147.png b/legacyworlds-web-main/Content/Raw/img/pp/l/147.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/147.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/147.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/148.png b/legacyworlds-web-main/Content/Raw/img/pp/l/148.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/148.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/148.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/149.png b/legacyworlds-web-main/Content/Raw/img/pp/l/149.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/149.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/149.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/15.png b/legacyworlds-web-main/Content/Raw/img/pp/l/15.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/15.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/15.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/150.png b/legacyworlds-web-main/Content/Raw/img/pp/l/150.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/150.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/150.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/151.png b/legacyworlds-web-main/Content/Raw/img/pp/l/151.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/151.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/151.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/152.png b/legacyworlds-web-main/Content/Raw/img/pp/l/152.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/152.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/152.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/153.png b/legacyworlds-web-main/Content/Raw/img/pp/l/153.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/153.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/153.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/154.png b/legacyworlds-web-main/Content/Raw/img/pp/l/154.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/154.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/154.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/155.png b/legacyworlds-web-main/Content/Raw/img/pp/l/155.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/155.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/155.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/156.png b/legacyworlds-web-main/Content/Raw/img/pp/l/156.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/156.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/156.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/157.png b/legacyworlds-web-main/Content/Raw/img/pp/l/157.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/157.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/157.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/158.png b/legacyworlds-web-main/Content/Raw/img/pp/l/158.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/158.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/158.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/159.png b/legacyworlds-web-main/Content/Raw/img/pp/l/159.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/159.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/159.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/16.png b/legacyworlds-web-main/Content/Raw/img/pp/l/16.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/16.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/16.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/160.png b/legacyworlds-web-main/Content/Raw/img/pp/l/160.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/160.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/160.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/161.png b/legacyworlds-web-main/Content/Raw/img/pp/l/161.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/161.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/161.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/162.png b/legacyworlds-web-main/Content/Raw/img/pp/l/162.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/162.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/162.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/163.png b/legacyworlds-web-main/Content/Raw/img/pp/l/163.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/163.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/163.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/164.png b/legacyworlds-web-main/Content/Raw/img/pp/l/164.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/164.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/164.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/165.png b/legacyworlds-web-main/Content/Raw/img/pp/l/165.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/165.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/165.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/166.png b/legacyworlds-web-main/Content/Raw/img/pp/l/166.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/166.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/166.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/167.png b/legacyworlds-web-main/Content/Raw/img/pp/l/167.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/167.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/167.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/168.png b/legacyworlds-web-main/Content/Raw/img/pp/l/168.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/168.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/168.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/169.png b/legacyworlds-web-main/Content/Raw/img/pp/l/169.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/169.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/169.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/17.png b/legacyworlds-web-main/Content/Raw/img/pp/l/17.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/17.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/17.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/170.png b/legacyworlds-web-main/Content/Raw/img/pp/l/170.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/170.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/170.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/171.png b/legacyworlds-web-main/Content/Raw/img/pp/l/171.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/171.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/171.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/172.png b/legacyworlds-web-main/Content/Raw/img/pp/l/172.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/172.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/172.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/173.png b/legacyworlds-web-main/Content/Raw/img/pp/l/173.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/173.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/173.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/174.png b/legacyworlds-web-main/Content/Raw/img/pp/l/174.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/174.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/174.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/175.png b/legacyworlds-web-main/Content/Raw/img/pp/l/175.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/175.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/175.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/176.png b/legacyworlds-web-main/Content/Raw/img/pp/l/176.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/176.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/176.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/177.png b/legacyworlds-web-main/Content/Raw/img/pp/l/177.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/177.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/177.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/178.png b/legacyworlds-web-main/Content/Raw/img/pp/l/178.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/178.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/178.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/179.png b/legacyworlds-web-main/Content/Raw/img/pp/l/179.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/179.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/179.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/18.png b/legacyworlds-web-main/Content/Raw/img/pp/l/18.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/18.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/18.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/180.png b/legacyworlds-web-main/Content/Raw/img/pp/l/180.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/180.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/180.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/181.png b/legacyworlds-web-main/Content/Raw/img/pp/l/181.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/181.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/181.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/182.png b/legacyworlds-web-main/Content/Raw/img/pp/l/182.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/182.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/182.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/183.png b/legacyworlds-web-main/Content/Raw/img/pp/l/183.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/183.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/183.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/184.png b/legacyworlds-web-main/Content/Raw/img/pp/l/184.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/184.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/184.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/185.png b/legacyworlds-web-main/Content/Raw/img/pp/l/185.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/185.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/185.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/186.png b/legacyworlds-web-main/Content/Raw/img/pp/l/186.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/186.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/186.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/187.png b/legacyworlds-web-main/Content/Raw/img/pp/l/187.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/187.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/187.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/188.png b/legacyworlds-web-main/Content/Raw/img/pp/l/188.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/188.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/188.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/189.png b/legacyworlds-web-main/Content/Raw/img/pp/l/189.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/189.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/189.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/19.png b/legacyworlds-web-main/Content/Raw/img/pp/l/19.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/19.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/19.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/190.png b/legacyworlds-web-main/Content/Raw/img/pp/l/190.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/190.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/190.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/191.png b/legacyworlds-web-main/Content/Raw/img/pp/l/191.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/191.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/191.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/192.png b/legacyworlds-web-main/Content/Raw/img/pp/l/192.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/192.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/192.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/193.png b/legacyworlds-web-main/Content/Raw/img/pp/l/193.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/193.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/193.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/194.png b/legacyworlds-web-main/Content/Raw/img/pp/l/194.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/194.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/194.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/195.png b/legacyworlds-web-main/Content/Raw/img/pp/l/195.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/195.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/195.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/196.png b/legacyworlds-web-main/Content/Raw/img/pp/l/196.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/196.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/196.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/197.png b/legacyworlds-web-main/Content/Raw/img/pp/l/197.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/197.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/197.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/198.png b/legacyworlds-web-main/Content/Raw/img/pp/l/198.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/198.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/198.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/199.png b/legacyworlds-web-main/Content/Raw/img/pp/l/199.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/199.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/199.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/2.png b/legacyworlds-web-main/Content/Raw/img/pp/l/2.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/2.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/2.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/20.png b/legacyworlds-web-main/Content/Raw/img/pp/l/20.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/20.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/20.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/200.png b/legacyworlds-web-main/Content/Raw/img/pp/l/200.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/200.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/200.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/21.png b/legacyworlds-web-main/Content/Raw/img/pp/l/21.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/21.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/21.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/22.png b/legacyworlds-web-main/Content/Raw/img/pp/l/22.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/22.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/22.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/23.png b/legacyworlds-web-main/Content/Raw/img/pp/l/23.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/23.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/23.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/24.png b/legacyworlds-web-main/Content/Raw/img/pp/l/24.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/24.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/24.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/25.png b/legacyworlds-web-main/Content/Raw/img/pp/l/25.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/25.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/25.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/26.png b/legacyworlds-web-main/Content/Raw/img/pp/l/26.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/26.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/26.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/27.png b/legacyworlds-web-main/Content/Raw/img/pp/l/27.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/27.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/27.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/28.png b/legacyworlds-web-main/Content/Raw/img/pp/l/28.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/28.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/28.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/29.png b/legacyworlds-web-main/Content/Raw/img/pp/l/29.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/29.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/29.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/3.png b/legacyworlds-web-main/Content/Raw/img/pp/l/3.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/3.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/3.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/30.png b/legacyworlds-web-main/Content/Raw/img/pp/l/30.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/30.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/30.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/31.png b/legacyworlds-web-main/Content/Raw/img/pp/l/31.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/31.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/31.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/32.png b/legacyworlds-web-main/Content/Raw/img/pp/l/32.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/32.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/32.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/33.png b/legacyworlds-web-main/Content/Raw/img/pp/l/33.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/33.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/33.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/34.png b/legacyworlds-web-main/Content/Raw/img/pp/l/34.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/34.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/34.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/35.png b/legacyworlds-web-main/Content/Raw/img/pp/l/35.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/35.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/35.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/36.png b/legacyworlds-web-main/Content/Raw/img/pp/l/36.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/36.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/36.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/37.png b/legacyworlds-web-main/Content/Raw/img/pp/l/37.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/37.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/37.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/38.png b/legacyworlds-web-main/Content/Raw/img/pp/l/38.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/38.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/38.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/39.png b/legacyworlds-web-main/Content/Raw/img/pp/l/39.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/39.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/39.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/4.png b/legacyworlds-web-main/Content/Raw/img/pp/l/4.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/4.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/4.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/40.png b/legacyworlds-web-main/Content/Raw/img/pp/l/40.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/40.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/40.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/41.png b/legacyworlds-web-main/Content/Raw/img/pp/l/41.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/41.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/41.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/42.png b/legacyworlds-web-main/Content/Raw/img/pp/l/42.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/42.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/42.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/43.png b/legacyworlds-web-main/Content/Raw/img/pp/l/43.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/43.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/43.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/44.png b/legacyworlds-web-main/Content/Raw/img/pp/l/44.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/44.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/44.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/45.png b/legacyworlds-web-main/Content/Raw/img/pp/l/45.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/45.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/45.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/46.png b/legacyworlds-web-main/Content/Raw/img/pp/l/46.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/46.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/46.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/47.png b/legacyworlds-web-main/Content/Raw/img/pp/l/47.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/47.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/47.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/48.png b/legacyworlds-web-main/Content/Raw/img/pp/l/48.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/48.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/48.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/49.png b/legacyworlds-web-main/Content/Raw/img/pp/l/49.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/49.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/49.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/5.png b/legacyworlds-web-main/Content/Raw/img/pp/l/5.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/5.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/5.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/50.png b/legacyworlds-web-main/Content/Raw/img/pp/l/50.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/50.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/50.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/51.png b/legacyworlds-web-main/Content/Raw/img/pp/l/51.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/51.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/51.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/52.png b/legacyworlds-web-main/Content/Raw/img/pp/l/52.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/52.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/52.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/53.png b/legacyworlds-web-main/Content/Raw/img/pp/l/53.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/53.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/53.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/54.png b/legacyworlds-web-main/Content/Raw/img/pp/l/54.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/54.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/54.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/55.png b/legacyworlds-web-main/Content/Raw/img/pp/l/55.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/55.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/55.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/56.png b/legacyworlds-web-main/Content/Raw/img/pp/l/56.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/56.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/56.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/57.png b/legacyworlds-web-main/Content/Raw/img/pp/l/57.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/57.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/57.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/58.png b/legacyworlds-web-main/Content/Raw/img/pp/l/58.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/58.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/58.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/59.png b/legacyworlds-web-main/Content/Raw/img/pp/l/59.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/59.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/59.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/6.png b/legacyworlds-web-main/Content/Raw/img/pp/l/6.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/6.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/6.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/60.png b/legacyworlds-web-main/Content/Raw/img/pp/l/60.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/60.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/60.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/61.png b/legacyworlds-web-main/Content/Raw/img/pp/l/61.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/61.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/61.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/62.png b/legacyworlds-web-main/Content/Raw/img/pp/l/62.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/62.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/62.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/63.png b/legacyworlds-web-main/Content/Raw/img/pp/l/63.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/63.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/63.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/64.png b/legacyworlds-web-main/Content/Raw/img/pp/l/64.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/64.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/64.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/65.png b/legacyworlds-web-main/Content/Raw/img/pp/l/65.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/65.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/65.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/66.png b/legacyworlds-web-main/Content/Raw/img/pp/l/66.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/66.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/66.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/67.png b/legacyworlds-web-main/Content/Raw/img/pp/l/67.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/67.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/67.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/68.png b/legacyworlds-web-main/Content/Raw/img/pp/l/68.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/68.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/68.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/69.png b/legacyworlds-web-main/Content/Raw/img/pp/l/69.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/69.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/69.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/7.png b/legacyworlds-web-main/Content/Raw/img/pp/l/7.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/7.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/7.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/70.png b/legacyworlds-web-main/Content/Raw/img/pp/l/70.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/70.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/70.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/71.png b/legacyworlds-web-main/Content/Raw/img/pp/l/71.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/71.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/71.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/72.png b/legacyworlds-web-main/Content/Raw/img/pp/l/72.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/72.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/72.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/73.png b/legacyworlds-web-main/Content/Raw/img/pp/l/73.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/73.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/73.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/74.png b/legacyworlds-web-main/Content/Raw/img/pp/l/74.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/74.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/74.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/75.png b/legacyworlds-web-main/Content/Raw/img/pp/l/75.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/75.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/75.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/76.png b/legacyworlds-web-main/Content/Raw/img/pp/l/76.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/76.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/76.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/77.png b/legacyworlds-web-main/Content/Raw/img/pp/l/77.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/77.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/77.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/78.png b/legacyworlds-web-main/Content/Raw/img/pp/l/78.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/78.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/78.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/79.png b/legacyworlds-web-main/Content/Raw/img/pp/l/79.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/79.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/79.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/8.png b/legacyworlds-web-main/Content/Raw/img/pp/l/8.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/8.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/8.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/80.png b/legacyworlds-web-main/Content/Raw/img/pp/l/80.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/80.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/80.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/81.png b/legacyworlds-web-main/Content/Raw/img/pp/l/81.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/81.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/81.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/82.png b/legacyworlds-web-main/Content/Raw/img/pp/l/82.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/82.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/82.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/83.png b/legacyworlds-web-main/Content/Raw/img/pp/l/83.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/83.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/83.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/84.png b/legacyworlds-web-main/Content/Raw/img/pp/l/84.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/84.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/84.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/85.png b/legacyworlds-web-main/Content/Raw/img/pp/l/85.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/85.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/85.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/86.png b/legacyworlds-web-main/Content/Raw/img/pp/l/86.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/86.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/86.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/87.png b/legacyworlds-web-main/Content/Raw/img/pp/l/87.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/87.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/87.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/88.png b/legacyworlds-web-main/Content/Raw/img/pp/l/88.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/88.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/88.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/89.png b/legacyworlds-web-main/Content/Raw/img/pp/l/89.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/89.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/89.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/9.png b/legacyworlds-web-main/Content/Raw/img/pp/l/9.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/9.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/9.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/90.png b/legacyworlds-web-main/Content/Raw/img/pp/l/90.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/90.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/90.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/91.png b/legacyworlds-web-main/Content/Raw/img/pp/l/91.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/91.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/91.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/92.png b/legacyworlds-web-main/Content/Raw/img/pp/l/92.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/92.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/92.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/93.png b/legacyworlds-web-main/Content/Raw/img/pp/l/93.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/93.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/93.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/94.png b/legacyworlds-web-main/Content/Raw/img/pp/l/94.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/94.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/94.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/95.png b/legacyworlds-web-main/Content/Raw/img/pp/l/95.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/95.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/95.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/96.png b/legacyworlds-web-main/Content/Raw/img/pp/l/96.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/96.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/96.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/97.png b/legacyworlds-web-main/Content/Raw/img/pp/l/97.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/97.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/97.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/98.png b/legacyworlds-web-main/Content/Raw/img/pp/l/98.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/98.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/98.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/99.png b/legacyworlds-web-main/Content/Raw/img/pp/l/99.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/l/99.png
rename to legacyworlds-web-main/Content/Raw/img/pp/l/99.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/1.png b/legacyworlds-web-main/Content/Raw/img/pp/s/1.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/1.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/1.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/10.png b/legacyworlds-web-main/Content/Raw/img/pp/s/10.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/10.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/10.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/100.png b/legacyworlds-web-main/Content/Raw/img/pp/s/100.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/100.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/100.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/101.png b/legacyworlds-web-main/Content/Raw/img/pp/s/101.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/101.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/101.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/102.png b/legacyworlds-web-main/Content/Raw/img/pp/s/102.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/102.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/102.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/103.png b/legacyworlds-web-main/Content/Raw/img/pp/s/103.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/103.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/103.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/104.png b/legacyworlds-web-main/Content/Raw/img/pp/s/104.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/104.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/104.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/105.png b/legacyworlds-web-main/Content/Raw/img/pp/s/105.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/105.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/105.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/106.png b/legacyworlds-web-main/Content/Raw/img/pp/s/106.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/106.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/106.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/107.png b/legacyworlds-web-main/Content/Raw/img/pp/s/107.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/107.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/107.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/108.png b/legacyworlds-web-main/Content/Raw/img/pp/s/108.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/108.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/108.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/109.png b/legacyworlds-web-main/Content/Raw/img/pp/s/109.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/109.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/109.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/11.png b/legacyworlds-web-main/Content/Raw/img/pp/s/11.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/11.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/11.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/110.png b/legacyworlds-web-main/Content/Raw/img/pp/s/110.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/110.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/110.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/111.png b/legacyworlds-web-main/Content/Raw/img/pp/s/111.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/111.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/111.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/112.png b/legacyworlds-web-main/Content/Raw/img/pp/s/112.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/112.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/112.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/113.png b/legacyworlds-web-main/Content/Raw/img/pp/s/113.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/113.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/113.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/114.png b/legacyworlds-web-main/Content/Raw/img/pp/s/114.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/114.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/114.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/115.png b/legacyworlds-web-main/Content/Raw/img/pp/s/115.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/115.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/115.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/116.png b/legacyworlds-web-main/Content/Raw/img/pp/s/116.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/116.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/116.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/117.png b/legacyworlds-web-main/Content/Raw/img/pp/s/117.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/117.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/117.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/118.png b/legacyworlds-web-main/Content/Raw/img/pp/s/118.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/118.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/118.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/119.png b/legacyworlds-web-main/Content/Raw/img/pp/s/119.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/119.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/119.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/12.png b/legacyworlds-web-main/Content/Raw/img/pp/s/12.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/12.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/12.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/120.png b/legacyworlds-web-main/Content/Raw/img/pp/s/120.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/120.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/120.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/121.png b/legacyworlds-web-main/Content/Raw/img/pp/s/121.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/121.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/121.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/122.png b/legacyworlds-web-main/Content/Raw/img/pp/s/122.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/122.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/122.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/123.png b/legacyworlds-web-main/Content/Raw/img/pp/s/123.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/123.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/123.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/124.png b/legacyworlds-web-main/Content/Raw/img/pp/s/124.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/124.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/124.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/125.png b/legacyworlds-web-main/Content/Raw/img/pp/s/125.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/125.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/125.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/126.png b/legacyworlds-web-main/Content/Raw/img/pp/s/126.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/126.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/126.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/127.png b/legacyworlds-web-main/Content/Raw/img/pp/s/127.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/127.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/127.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/128.png b/legacyworlds-web-main/Content/Raw/img/pp/s/128.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/128.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/128.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/129.png b/legacyworlds-web-main/Content/Raw/img/pp/s/129.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/129.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/129.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/13.png b/legacyworlds-web-main/Content/Raw/img/pp/s/13.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/13.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/13.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/130.png b/legacyworlds-web-main/Content/Raw/img/pp/s/130.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/130.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/130.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/131.png b/legacyworlds-web-main/Content/Raw/img/pp/s/131.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/131.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/131.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/132.png b/legacyworlds-web-main/Content/Raw/img/pp/s/132.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/132.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/132.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/133.png b/legacyworlds-web-main/Content/Raw/img/pp/s/133.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/133.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/133.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/134.png b/legacyworlds-web-main/Content/Raw/img/pp/s/134.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/134.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/134.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/135.png b/legacyworlds-web-main/Content/Raw/img/pp/s/135.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/135.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/135.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/136.png b/legacyworlds-web-main/Content/Raw/img/pp/s/136.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/136.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/136.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/137.png b/legacyworlds-web-main/Content/Raw/img/pp/s/137.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/137.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/137.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/138.png b/legacyworlds-web-main/Content/Raw/img/pp/s/138.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/138.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/138.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/139.png b/legacyworlds-web-main/Content/Raw/img/pp/s/139.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/139.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/139.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/14.png b/legacyworlds-web-main/Content/Raw/img/pp/s/14.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/14.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/14.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/140.png b/legacyworlds-web-main/Content/Raw/img/pp/s/140.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/140.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/140.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/141.png b/legacyworlds-web-main/Content/Raw/img/pp/s/141.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/141.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/141.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/142.png b/legacyworlds-web-main/Content/Raw/img/pp/s/142.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/142.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/142.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/143.png b/legacyworlds-web-main/Content/Raw/img/pp/s/143.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/143.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/143.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/144.png b/legacyworlds-web-main/Content/Raw/img/pp/s/144.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/144.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/144.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/145.png b/legacyworlds-web-main/Content/Raw/img/pp/s/145.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/145.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/145.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/146.png b/legacyworlds-web-main/Content/Raw/img/pp/s/146.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/146.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/146.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/147.png b/legacyworlds-web-main/Content/Raw/img/pp/s/147.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/147.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/147.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/148.png b/legacyworlds-web-main/Content/Raw/img/pp/s/148.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/148.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/148.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/149.png b/legacyworlds-web-main/Content/Raw/img/pp/s/149.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/149.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/149.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/15.png b/legacyworlds-web-main/Content/Raw/img/pp/s/15.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/15.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/15.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/150.png b/legacyworlds-web-main/Content/Raw/img/pp/s/150.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/150.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/150.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/151.png b/legacyworlds-web-main/Content/Raw/img/pp/s/151.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/151.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/151.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/152.png b/legacyworlds-web-main/Content/Raw/img/pp/s/152.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/152.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/152.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/153.png b/legacyworlds-web-main/Content/Raw/img/pp/s/153.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/153.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/153.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/154.png b/legacyworlds-web-main/Content/Raw/img/pp/s/154.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/154.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/154.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/155.png b/legacyworlds-web-main/Content/Raw/img/pp/s/155.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/155.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/155.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/156.png b/legacyworlds-web-main/Content/Raw/img/pp/s/156.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/156.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/156.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/157.png b/legacyworlds-web-main/Content/Raw/img/pp/s/157.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/157.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/157.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/158.png b/legacyworlds-web-main/Content/Raw/img/pp/s/158.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/158.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/158.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/159.png b/legacyworlds-web-main/Content/Raw/img/pp/s/159.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/159.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/159.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/16.png b/legacyworlds-web-main/Content/Raw/img/pp/s/16.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/16.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/16.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/160.png b/legacyworlds-web-main/Content/Raw/img/pp/s/160.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/160.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/160.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/161.png b/legacyworlds-web-main/Content/Raw/img/pp/s/161.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/161.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/161.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/162.png b/legacyworlds-web-main/Content/Raw/img/pp/s/162.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/162.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/162.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/163.png b/legacyworlds-web-main/Content/Raw/img/pp/s/163.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/163.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/163.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/164.png b/legacyworlds-web-main/Content/Raw/img/pp/s/164.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/164.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/164.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/165.png b/legacyworlds-web-main/Content/Raw/img/pp/s/165.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/165.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/165.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/166.png b/legacyworlds-web-main/Content/Raw/img/pp/s/166.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/166.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/166.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/167.png b/legacyworlds-web-main/Content/Raw/img/pp/s/167.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/167.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/167.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/168.png b/legacyworlds-web-main/Content/Raw/img/pp/s/168.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/168.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/168.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/169.png b/legacyworlds-web-main/Content/Raw/img/pp/s/169.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/169.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/169.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/17.png b/legacyworlds-web-main/Content/Raw/img/pp/s/17.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/17.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/17.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/170.png b/legacyworlds-web-main/Content/Raw/img/pp/s/170.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/170.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/170.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/171.png b/legacyworlds-web-main/Content/Raw/img/pp/s/171.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/171.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/171.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/172.png b/legacyworlds-web-main/Content/Raw/img/pp/s/172.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/172.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/172.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/173.png b/legacyworlds-web-main/Content/Raw/img/pp/s/173.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/173.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/173.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/174.png b/legacyworlds-web-main/Content/Raw/img/pp/s/174.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/174.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/174.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/175.png b/legacyworlds-web-main/Content/Raw/img/pp/s/175.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/175.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/175.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/176.png b/legacyworlds-web-main/Content/Raw/img/pp/s/176.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/176.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/176.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/177.png b/legacyworlds-web-main/Content/Raw/img/pp/s/177.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/177.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/177.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/178.png b/legacyworlds-web-main/Content/Raw/img/pp/s/178.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/178.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/178.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/179.png b/legacyworlds-web-main/Content/Raw/img/pp/s/179.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/179.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/179.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/18.png b/legacyworlds-web-main/Content/Raw/img/pp/s/18.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/18.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/18.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/180.png b/legacyworlds-web-main/Content/Raw/img/pp/s/180.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/180.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/180.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/181.png b/legacyworlds-web-main/Content/Raw/img/pp/s/181.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/181.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/181.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/182.png b/legacyworlds-web-main/Content/Raw/img/pp/s/182.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/182.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/182.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/183.png b/legacyworlds-web-main/Content/Raw/img/pp/s/183.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/183.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/183.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/184.png b/legacyworlds-web-main/Content/Raw/img/pp/s/184.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/184.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/184.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/185.png b/legacyworlds-web-main/Content/Raw/img/pp/s/185.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/185.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/185.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/186.png b/legacyworlds-web-main/Content/Raw/img/pp/s/186.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/186.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/186.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/187.png b/legacyworlds-web-main/Content/Raw/img/pp/s/187.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/187.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/187.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/188.png b/legacyworlds-web-main/Content/Raw/img/pp/s/188.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/188.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/188.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/189.png b/legacyworlds-web-main/Content/Raw/img/pp/s/189.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/189.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/189.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/19.png b/legacyworlds-web-main/Content/Raw/img/pp/s/19.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/19.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/19.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/190.png b/legacyworlds-web-main/Content/Raw/img/pp/s/190.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/190.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/190.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/191.png b/legacyworlds-web-main/Content/Raw/img/pp/s/191.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/191.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/191.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/192.png b/legacyworlds-web-main/Content/Raw/img/pp/s/192.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/192.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/192.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/193.png b/legacyworlds-web-main/Content/Raw/img/pp/s/193.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/193.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/193.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/194.png b/legacyworlds-web-main/Content/Raw/img/pp/s/194.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/194.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/194.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/195.png b/legacyworlds-web-main/Content/Raw/img/pp/s/195.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/195.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/195.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/196.png b/legacyworlds-web-main/Content/Raw/img/pp/s/196.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/196.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/196.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/197.png b/legacyworlds-web-main/Content/Raw/img/pp/s/197.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/197.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/197.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/198.png b/legacyworlds-web-main/Content/Raw/img/pp/s/198.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/198.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/198.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/199.png b/legacyworlds-web-main/Content/Raw/img/pp/s/199.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/199.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/199.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/2.png b/legacyworlds-web-main/Content/Raw/img/pp/s/2.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/2.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/2.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/20.png b/legacyworlds-web-main/Content/Raw/img/pp/s/20.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/20.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/20.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/200.png b/legacyworlds-web-main/Content/Raw/img/pp/s/200.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/200.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/200.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/21.png b/legacyworlds-web-main/Content/Raw/img/pp/s/21.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/21.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/21.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/22.png b/legacyworlds-web-main/Content/Raw/img/pp/s/22.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/22.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/22.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/23.png b/legacyworlds-web-main/Content/Raw/img/pp/s/23.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/23.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/23.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/24.png b/legacyworlds-web-main/Content/Raw/img/pp/s/24.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/24.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/24.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/25.png b/legacyworlds-web-main/Content/Raw/img/pp/s/25.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/25.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/25.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/26.png b/legacyworlds-web-main/Content/Raw/img/pp/s/26.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/26.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/26.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/27.png b/legacyworlds-web-main/Content/Raw/img/pp/s/27.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/27.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/27.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/28.png b/legacyworlds-web-main/Content/Raw/img/pp/s/28.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/28.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/28.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/29.png b/legacyworlds-web-main/Content/Raw/img/pp/s/29.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/29.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/29.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/3.png b/legacyworlds-web-main/Content/Raw/img/pp/s/3.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/3.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/3.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/30.png b/legacyworlds-web-main/Content/Raw/img/pp/s/30.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/30.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/30.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/31.png b/legacyworlds-web-main/Content/Raw/img/pp/s/31.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/31.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/31.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/32.png b/legacyworlds-web-main/Content/Raw/img/pp/s/32.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/32.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/32.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/33.png b/legacyworlds-web-main/Content/Raw/img/pp/s/33.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/33.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/33.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/34.png b/legacyworlds-web-main/Content/Raw/img/pp/s/34.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/34.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/34.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/35.png b/legacyworlds-web-main/Content/Raw/img/pp/s/35.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/35.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/35.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/36.png b/legacyworlds-web-main/Content/Raw/img/pp/s/36.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/36.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/36.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/37.png b/legacyworlds-web-main/Content/Raw/img/pp/s/37.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/37.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/37.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/38.png b/legacyworlds-web-main/Content/Raw/img/pp/s/38.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/38.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/38.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/39.png b/legacyworlds-web-main/Content/Raw/img/pp/s/39.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/39.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/39.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/4.png b/legacyworlds-web-main/Content/Raw/img/pp/s/4.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/4.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/4.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/40.png b/legacyworlds-web-main/Content/Raw/img/pp/s/40.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/40.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/40.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/41.png b/legacyworlds-web-main/Content/Raw/img/pp/s/41.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/41.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/41.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/42.png b/legacyworlds-web-main/Content/Raw/img/pp/s/42.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/42.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/42.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/43.png b/legacyworlds-web-main/Content/Raw/img/pp/s/43.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/43.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/43.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/44.png b/legacyworlds-web-main/Content/Raw/img/pp/s/44.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/44.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/44.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/45.png b/legacyworlds-web-main/Content/Raw/img/pp/s/45.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/45.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/45.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/46.png b/legacyworlds-web-main/Content/Raw/img/pp/s/46.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/46.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/46.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/47.png b/legacyworlds-web-main/Content/Raw/img/pp/s/47.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/47.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/47.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/48.png b/legacyworlds-web-main/Content/Raw/img/pp/s/48.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/48.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/48.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/49.png b/legacyworlds-web-main/Content/Raw/img/pp/s/49.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/49.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/49.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/5.png b/legacyworlds-web-main/Content/Raw/img/pp/s/5.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/5.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/5.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/50.png b/legacyworlds-web-main/Content/Raw/img/pp/s/50.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/50.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/50.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/51.png b/legacyworlds-web-main/Content/Raw/img/pp/s/51.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/51.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/51.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/52.png b/legacyworlds-web-main/Content/Raw/img/pp/s/52.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/52.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/52.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/53.png b/legacyworlds-web-main/Content/Raw/img/pp/s/53.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/53.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/53.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/54.png b/legacyworlds-web-main/Content/Raw/img/pp/s/54.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/54.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/54.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/55.png b/legacyworlds-web-main/Content/Raw/img/pp/s/55.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/55.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/55.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/56.png b/legacyworlds-web-main/Content/Raw/img/pp/s/56.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/56.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/56.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/57.png b/legacyworlds-web-main/Content/Raw/img/pp/s/57.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/57.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/57.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/58.png b/legacyworlds-web-main/Content/Raw/img/pp/s/58.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/58.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/58.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/59.png b/legacyworlds-web-main/Content/Raw/img/pp/s/59.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/59.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/59.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/6.png b/legacyworlds-web-main/Content/Raw/img/pp/s/6.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/6.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/6.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/60.png b/legacyworlds-web-main/Content/Raw/img/pp/s/60.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/60.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/60.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/61.png b/legacyworlds-web-main/Content/Raw/img/pp/s/61.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/61.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/61.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/62.png b/legacyworlds-web-main/Content/Raw/img/pp/s/62.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/62.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/62.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/63.png b/legacyworlds-web-main/Content/Raw/img/pp/s/63.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/63.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/63.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/64.png b/legacyworlds-web-main/Content/Raw/img/pp/s/64.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/64.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/64.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/65.png b/legacyworlds-web-main/Content/Raw/img/pp/s/65.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/65.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/65.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/66.png b/legacyworlds-web-main/Content/Raw/img/pp/s/66.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/66.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/66.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/67.png b/legacyworlds-web-main/Content/Raw/img/pp/s/67.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/67.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/67.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/68.png b/legacyworlds-web-main/Content/Raw/img/pp/s/68.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/68.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/68.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/69.png b/legacyworlds-web-main/Content/Raw/img/pp/s/69.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/69.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/69.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/7.png b/legacyworlds-web-main/Content/Raw/img/pp/s/7.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/7.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/7.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/70.png b/legacyworlds-web-main/Content/Raw/img/pp/s/70.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/70.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/70.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/71.png b/legacyworlds-web-main/Content/Raw/img/pp/s/71.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/71.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/71.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/72.png b/legacyworlds-web-main/Content/Raw/img/pp/s/72.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/72.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/72.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/73.png b/legacyworlds-web-main/Content/Raw/img/pp/s/73.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/73.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/73.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/74.png b/legacyworlds-web-main/Content/Raw/img/pp/s/74.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/74.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/74.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/75.png b/legacyworlds-web-main/Content/Raw/img/pp/s/75.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/75.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/75.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/76.png b/legacyworlds-web-main/Content/Raw/img/pp/s/76.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/76.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/76.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/77.png b/legacyworlds-web-main/Content/Raw/img/pp/s/77.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/77.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/77.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/78.png b/legacyworlds-web-main/Content/Raw/img/pp/s/78.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/78.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/78.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/79.png b/legacyworlds-web-main/Content/Raw/img/pp/s/79.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/79.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/79.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/8.png b/legacyworlds-web-main/Content/Raw/img/pp/s/8.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/8.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/8.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/80.png b/legacyworlds-web-main/Content/Raw/img/pp/s/80.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/80.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/80.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/81.png b/legacyworlds-web-main/Content/Raw/img/pp/s/81.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/81.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/81.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/82.png b/legacyworlds-web-main/Content/Raw/img/pp/s/82.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/82.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/82.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/83.png b/legacyworlds-web-main/Content/Raw/img/pp/s/83.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/83.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/83.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/84.png b/legacyworlds-web-main/Content/Raw/img/pp/s/84.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/84.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/84.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/85.png b/legacyworlds-web-main/Content/Raw/img/pp/s/85.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/85.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/85.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/86.png b/legacyworlds-web-main/Content/Raw/img/pp/s/86.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/86.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/86.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/87.png b/legacyworlds-web-main/Content/Raw/img/pp/s/87.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/87.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/87.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/88.png b/legacyworlds-web-main/Content/Raw/img/pp/s/88.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/88.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/88.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/89.png b/legacyworlds-web-main/Content/Raw/img/pp/s/89.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/89.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/89.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/9.png b/legacyworlds-web-main/Content/Raw/img/pp/s/9.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/9.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/9.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/90.png b/legacyworlds-web-main/Content/Raw/img/pp/s/90.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/90.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/90.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/91.png b/legacyworlds-web-main/Content/Raw/img/pp/s/91.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/91.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/91.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/92.png b/legacyworlds-web-main/Content/Raw/img/pp/s/92.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/92.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/92.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/93.png b/legacyworlds-web-main/Content/Raw/img/pp/s/93.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/93.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/93.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/94.png b/legacyworlds-web-main/Content/Raw/img/pp/s/94.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/94.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/94.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/95.png b/legacyworlds-web-main/Content/Raw/img/pp/s/95.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/95.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/95.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/96.png b/legacyworlds-web-main/Content/Raw/img/pp/s/96.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/96.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/96.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/97.png b/legacyworlds-web-main/Content/Raw/img/pp/s/97.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/97.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/97.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/98.png b/legacyworlds-web-main/Content/Raw/img/pp/s/98.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/98.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/98.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/99.png b/legacyworlds-web-main/Content/Raw/img/pp/s/99.png
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/img/pp/s/99.png
rename to legacyworlds-web-main/Content/Raw/img/pp/s/99.png
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/js/jquery-1.4.2.min.js b/legacyworlds-web-main/Content/Raw/js/jquery-1.4.2.min.js
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/js/jquery-1.4.2.min.js
rename to legacyworlds-web-main/Content/Raw/js/jquery-1.4.2.min.js
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/js/main.js b/legacyworlds-web-main/Content/Raw/js/main.js
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/js/main.js
rename to legacyworlds-web-main/Content/Raw/js/main.js
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/IRCApplet.class b/legacyworlds-web-main/Content/Raw/pjirc/IRCApplet.class
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/IRCApplet.class
rename to legacyworlds-web-main/Content/Raw/pjirc/IRCApplet.class
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/background.gif b/legacyworlds-web-main/Content/Raw/pjirc/background.gif
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/background.gif
rename to legacyworlds-web-main/Content/Raw/pjirc/background.gif
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/english.lng b/legacyworlds-web-main/Content/Raw/pjirc/english.lng
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/english.lng
rename to legacyworlds-web-main/Content/Raw/pjirc/english.lng
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/french.lng b/legacyworlds-web-main/Content/Raw/pjirc/french.lng
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/french.lng
rename to legacyworlds-web-main/Content/Raw/pjirc/french.lng
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/irc.cab b/legacyworlds-web-main/Content/Raw/pjirc/irc.cab
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/irc.cab
rename to legacyworlds-web-main/Content/Raw/pjirc/irc.cab
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/irc.jar b/legacyworlds-web-main/Content/Raw/pjirc/irc.jar
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/irc.jar
rename to legacyworlds-web-main/Content/Raw/pjirc/irc.jar
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/pixx-english.lng b/legacyworlds-web-main/Content/Raw/pjirc/pixx-english.lng
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/pixx-english.lng
rename to legacyworlds-web-main/Content/Raw/pjirc/pixx-english.lng
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/pixx-french.lng b/legacyworlds-web-main/Content/Raw/pjirc/pixx-french.lng
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/pixx-french.lng
rename to legacyworlds-web-main/Content/Raw/pjirc/pixx-french.lng
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/pixx.cab b/legacyworlds-web-main/Content/Raw/pjirc/pixx.cab
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/pixx.cab
rename to legacyworlds-web-main/Content/Raw/pjirc/pixx.cab
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/pixx.jar b/legacyworlds-web-main/Content/Raw/pjirc/pixx.jar
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/pixx.jar
rename to legacyworlds-web-main/Content/Raw/pjirc/pixx.jar
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/securedirc.cab b/legacyworlds-web-main/Content/Raw/pjirc/securedirc.cab
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/WebContent/pjirc/securedirc.cab
rename to legacyworlds-web-main/Content/Raw/pjirc/securedirc.cab
diff --git a/legacyworlds-web-main/pom.xml b/legacyworlds-web-main/pom.xml
new file mode 100644
index 0000000..61cb214
--- /dev/null
+++ b/legacyworlds-web-main/pom.xml
@@ -0,0 +1,45 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds-web</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-web/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-web-main</artifactId>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<packaging>war</packaging>
+	<name>Legacy Worlds - Web - Game</name>
+
+	<dependencies>
+		<dependency>
+			<groupId>com.deepclone.lw</groupId>
+			<artifactId>legacyworlds-web-beans</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.freemarker</groupId>
+			<artifactId>freemarker</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-war-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>tomcat-maven-plugin</artifactId>
+				<configuration>
+					<path>/lwmain</path>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/BannedPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/BannedPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/BannedPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/BannedPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/CommonPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/CommonPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/CommonPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/CommonPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ErrorHandlerBean.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ErrorHandlerBean.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ErrorHandlerBean.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ErrorHandlerBean.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ExternalPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ExternalPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ExternalPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ExternalPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/LoginPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/LoginPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/LoginPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/LoginPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/LogoutPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/LogoutPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/LogoutPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/LogoutPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/PasswordRecoveryPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/PasswordRecoveryPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/PasswordRecoveryPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/PasswordRecoveryPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/PlayerSessionRedirector.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/PlayerSessionRedirector.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/PlayerSessionRedirector.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/PlayerSessionRedirector.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ReactivationPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ReactivationPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ReactivationPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ReactivationPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/RegistrationPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/RegistrationPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/RegistrationPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/RegistrationPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ValidationPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ValidationPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ValidationPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/ValidationPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/AccountPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/AccountPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/AccountPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/AccountPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/AlliancePage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/AlliancePage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/AlliancePage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/AlliancePage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/BattlePages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/BattlePages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/BattlePages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/BattlePages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/BugTrackerPages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/BugTrackerPages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/BugTrackerPages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/BugTrackerPages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ChatPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ChatPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ChatPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ChatPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/EnemiesPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/EnemiesPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/EnemiesPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/EnemiesPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/FleetsPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/FleetsPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/FleetsPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/FleetsPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/GetPlanetPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/GetPlanetPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/GetPlanetPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/GetPlanetPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MapPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MapPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MapPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MapPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MessageBoxView.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MessageBoxView.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MessageBoxView.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MessageBoxView.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MessagePages.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MessagePages.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MessagePages.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/MessagePages.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetListPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetListPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetListPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetListPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java
rename to legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/resources/log4j.properties b/legacyworlds-web-main/src/main/resources/log4j.properties
similarity index 100%
rename from legacyworlds-web/legacyworlds-web-main/src/main/resources/log4j.properties
rename to legacyworlds-web-main/src/main/resources/log4j.properties
diff --git a/legacyworlds-web-main/src/test/java/.empty b/legacyworlds-web-main/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-main/src/test/resources/.empty b/legacyworlds-web-main/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web/.project b/legacyworlds-web/.project
deleted file mode 100644
index b64463a..0000000
--- a/legacyworlds-web/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-web</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-web/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-web/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index 32dc0c3..0000000
--- a/legacyworlds-web/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Thu Apr 15 18:51:39 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-web/legacyworlds-web-admin/.classpath b/legacyworlds-web/legacyworlds-web-admin/.classpath
deleted file mode 100644
index 3d4e21c..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.classpath
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry exported="true" kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER">
-		<attributes>
-			<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
-	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
-	<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v6.0">
-		<attributes>
-			<attribute name="owner.project.facets" value="jst.web"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-6-openjdk">
-		<attributes>
-			<attribute name="owner.project.facets" value="jst.java"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-web/legacyworlds-web-admin/.project b/legacyworlds-web/legacyworlds-web-admin/.project
deleted file mode 100644
index 073b138..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.project
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-web-admin</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.wst.validation.validationbuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
-		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
-		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-		<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-web/legacyworlds-web-admin/.settings/.jsdtscope b/legacyworlds-web/legacyworlds-web-admin/.settings/.jsdtscope
deleted file mode 100644
index bbb8e68..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.settings/.jsdtscope
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.WebProject">
-		<attributes>
-			<attribute name="hide" value="true"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
-	<classpathentry kind="output" path=""/>
-</classpath>
diff --git a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 4e54f2c..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,8 +0,0 @@
-#Thu Apr 15 19:33:04 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.common.component b/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.common.component
deleted file mode 100644
index 45b17b5..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.common.component
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project-modules id="moduleCoreId" project-version="1.5.0">
-    <wb-module deploy-name="legacyworlds-web-admin">
-        <wb-resource deploy-path="/" source-path="/WebContent"/>
-        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
-        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
-        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
-        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/resources"/>
-        <property name="context-root" value="legacyworlds-web-admin"/>
-        <property name="java-output-path" value="/legacyworlds-web-admin/target/classes"/>
-    </wb-module>
-</project-modules>
diff --git a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.common.project.facet.core.xml b/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.common.project.facet.core.xml
deleted file mode 100644
index 9680654..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.common.project.facet.core.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<faceted-project>
-  <runtime name="Apache Tomcat v6.0"/>
-  <installed facet="jst.java" version="6.0"/>
-  <installed facet="jst.web" version="2.4"/>
-</faceted-project>
diff --git a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.jsdt.ui.superType.container b/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.jsdt.ui.superType.container
deleted file mode 100644
index 3bd5d0a..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.jsdt.ui.superType.container
+++ /dev/null
@@ -1 +0,0 @@
-org.eclipse.wst.jsdt.launching.baseBrowserLibrary
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.jsdt.ui.superType.name b/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.jsdt.ui.superType.name
deleted file mode 100644
index 05bd71b..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.jsdt.ui.superType.name
+++ /dev/null
@@ -1 +0,0 @@
-Window
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.ws.service.policy.prefs b/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.ws.service.policy.prefs
deleted file mode 100644
index 5095f8b..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.settings/org.eclipse.wst.ws.service.policy.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Mon Apr 19 12:39:12 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.wst.ws.service.policy.projectEnabled=false
diff --git a/legacyworlds-web/legacyworlds-web-admin/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-web/legacyworlds-web-admin/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index bb79aef..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Thu Apr 15 19:11:01 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/version.ftl b/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/version.ftl
deleted file mode 100644
index c9406cf..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/version.ftl
+++ /dev/null
@@ -1,2 +0,0 @@
-<#macro version>Milestone 1</#macro>
-<#macro full_version>Beta 6 milestone 1 (5.99.1)</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-admin/pom.xml b/legacyworlds-web/legacyworlds-web-admin/pom.xml
deleted file mode 100644
index 5d1f361..0000000
--- a/legacyworlds-web/legacyworlds-web-admin/pom.xml
+++ /dev/null
@@ -1,95 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<artifactId>legacyworlds-web</artifactId>
-		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
-	</parent>
-	
-	<groupId>com.deepclone.lw</groupId>
-	<artifactId>legacyworlds-web-admin</artifactId>
-	<version>5.99.1</version>
-	<packaging>war</packaging>
-	<name>Legacy Worlds administration site</name>
-
-	<dependencies>
-		<dependency>
-			<groupId>com.deepclone.lw</groupId>
-			<artifactId>legacyworlds-web-beans</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-
-		<dependency>
-			<groupId>javax.servlet</groupId>
-			<artifactId>servlet-api</artifactId>
-			<version>2.5</version>
-			<scope>provided</scope>
-		</dependency>
-		
-		<dependency>
-			<groupId>org.freemarker</groupId>
-			<artifactId>freemarker</artifactId>
-			<version>${org.freemarker.version}</version>
-			<scope>runtime</scope>
-		</dependency>
-		
-		<dependency>
-			<groupId>com.thoughtworks.xstream</groupId>
-			<artifactId>xstream</artifactId>
-			<version>${com.thoughtworks.xstream.version}</version>
-			<type>jar</type>
-		</dependency>
-	</dependencies>
-
-	<build>
-		<plugins>
-
-			<!-- 
-			<plugin>
-				<groupId>org.codehaus.mojo</groupId>
-				<artifactId>tomcat-maven-plugin</artifactId>
-				<version>1.0-beta-1</version>
-				<configuration>
-					<url>http://localhost:8080/manager</url>
-					<path>/lwadmin</path>
-					<update>true</update>
-				</configuration>
-				<executions>
-					<execution>
-						<id>deploy-admin-site</id>
-						<phase>package</phase>
-						<goals>
-							<goal>deploy</goal>
-						</goals>
-					</execution>
-				</executions>
-			</plugin>
-			-->
-
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-war-plugin</artifactId>
-				<version>2.0</version>
-				<configuration>
-					<webResources>
-						<resource>
-							<directory>legacyworlds-web/legacyworlds-web-admin/WebContent</directory>
-						</resource>
-					</webResources>
-				</configuration>
-				<executions>
-					<execution>
-						<id>build-admin-war</id>
-						<phase>package</phase>
-						<goals>
-							<goal>war</goal>
-						</goals>
-					</execution>
-				</executions>
-			</plugin>
-
-		</plugins>
-	</build>
-
-</project>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-beans/.classpath b/legacyworlds-web/legacyworlds-web-beans/.classpath
deleted file mode 100644
index 7c3d14f..0000000
--- a/legacyworlds-web/legacyworlds-web-beans/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-web/legacyworlds-web-beans/.project b/legacyworlds-web/legacyworlds-web-beans/.project
deleted file mode 100644
index 5702a99..0000000
--- a/legacyworlds-web/legacyworlds-web-beans/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-web-beans</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-web/legacyworlds-web-beans/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-web/legacyworlds-web-beans/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 34724f2..0000000
--- a/legacyworlds-web/legacyworlds-web-beans/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Apr 16 07:54:29 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-web/legacyworlds-web-beans/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-web/legacyworlds-web-beans/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index b3bba2f..0000000
--- a/legacyworlds-web/legacyworlds-web-beans/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Fri Apr 16 07:54:27 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-web/legacyworlds-web-main/.classpath b/legacyworlds-web/legacyworlds-web-main/.classpath
deleted file mode 100644
index 3f554db..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.classpath
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
-	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
-	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER">
-		<attributes>
-			<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v6.0">
-		<attributes>
-			<attribute name="owner.project.facets" value="jst.web"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
-	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
-	<classpathentry kind="output" path="target/classes"/>
-</classpath>
diff --git a/legacyworlds-web/legacyworlds-web-main/.project b/legacyworlds-web/legacyworlds-web-main/.project
deleted file mode 100644
index 348118f..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.project
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>legacyworlds-web-main</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.maven.ide.eclipse.maven2Builder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.wst.validation.validationbuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
-		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>org.maven.ide.eclipse.maven2Nature</nature>
-		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
-		<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
-	</natures>
-</projectDescription>
diff --git a/legacyworlds-web/legacyworlds-web-main/.settings/.jsdtscope b/legacyworlds-web/legacyworlds-web-main/.settings/.jsdtscope
deleted file mode 100644
index bbb8e68..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.settings/.jsdtscope
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.WebProject">
-		<attributes>
-			<attribute name="hide" value="true"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
-	<classpathentry kind="output" path=""/>
-</classpath>
diff --git a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 30a3c16..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,8 +0,0 @@
-#Thu Apr 15 19:50:22 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.common.component b/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.common.component
deleted file mode 100644
index 9c506c0..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.common.component
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project-modules id="moduleCoreId" project-version="1.5.0">
-    <wb-module deploy-name="legacyworlds-web-main">
-        <wb-resource deploy-path="/" source-path="/WebContent"/>
-        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
-        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
-        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
-        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/resources"/>
-        <property name="context-root" value="legacyworlds-web-main"/>
-        <property name="java-output-path" value="/legacyworlds-web-main/target/classes"/>
-    </wb-module>
-</project-modules>
diff --git a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.common.project.facet.core.xml b/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.common.project.facet.core.xml
deleted file mode 100644
index 9680654..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.common.project.facet.core.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<faceted-project>
-  <runtime name="Apache Tomcat v6.0"/>
-  <installed facet="jst.java" version="6.0"/>
-  <installed facet="jst.web" version="2.4"/>
-</faceted-project>
diff --git a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.jsdt.ui.superType.container b/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.jsdt.ui.superType.container
deleted file mode 100644
index 3bd5d0a..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.jsdt.ui.superType.container
+++ /dev/null
@@ -1 +0,0 @@
-org.eclipse.wst.jsdt.launching.baseBrowserLibrary
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.jsdt.ui.superType.name b/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.jsdt.ui.superType.name
deleted file mode 100644
index 05bd71b..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.settings/org.eclipse.wst.jsdt.ui.superType.name
+++ /dev/null
@@ -1 +0,0 @@
-Window
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-main/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-web/legacyworlds-web-main/.settings/org.maven.ide.eclipse.prefs
deleted file mode 100644
index a40fb0f..0000000
--- a/legacyworlds-web/legacyworlds-web-main/.settings/org.maven.ide.eclipse.prefs
+++ /dev/null
@@ -1,9 +0,0 @@
-#Thu Apr 15 18:52:28 CEST 2010
-activeProfiles=
-eclipse.preferences.version=1
-fullBuildGoals=process-test-resources
-includeModules=false
-resolveWorkspaceProjects=true
-resourceFilterGoals=process-resources resources\:testResources
-skipCompilerPlugin=true
-version=1
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/version.ftl b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/version.ftl
deleted file mode 100644
index c9406cf..0000000
--- a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/version.ftl
+++ /dev/null
@@ -1,2 +0,0 @@
-<#macro version>Milestone 1</#macro>
-<#macro full_version>Beta 6 milestone 1 (5.99.1)</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-main/pom.xml b/legacyworlds-web/legacyworlds-web-main/pom.xml
deleted file mode 100644
index 099a1af..0000000
--- a/legacyworlds-web/legacyworlds-web-main/pom.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<artifactId>legacyworlds-web</artifactId>
-		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
-	</parent>
-
-	<groupId>com.deepclone.lw</groupId>
-	<artifactId>legacyworlds-web-main</artifactId>
-	<version>5.99.1</version>
-	<packaging>war</packaging>
-	<name>Legacy Worlds main site</name>
-
-	<dependencies>
-		<dependency>
-			<groupId>com.deepclone.lw</groupId>
-			<artifactId>legacyworlds-web-beans</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-
-		<dependency>
-			<groupId>javax.servlet</groupId>
-			<artifactId>servlet-api</artifactId>
-			<version>2.5</version>
-			<scope>provided</scope>
-		</dependency>
-
-		<dependency>
-			<groupId>org.freemarker</groupId>
-			<artifactId>freemarker</artifactId>
-			<version>${org.freemarker.version}</version>
-			<scope>runtime</scope>
-		</dependency>
-	</dependencies>
-
-	<build>
-		<plugins>
-			<!-- 
-			<plugin>
-				<groupId>org.codehaus.mojo</groupId>
-				<artifactId>tomcat-maven-plugin</artifactId>
-				<version>1.0-beta-1</version>
-				<configuration>
-					<url>http://localhost:8080/manager</url>
-					<path>/lwmain</path>
-					<update>true</update>
-				</configuration>
-				<executions>
-					<execution>
-						<id>deploy-main-site</id>
-						<phase>package</phase>
-						<goals>
-							<goal>deploy</goal>
-						</goals>
-					</execution>
-				</executions>
-			</plugin>
-			 -->
-
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-war-plugin</artifactId>
-				<version>2.0</version>
-				<configuration>
-					<attachClasses>true</attachClasses>
-					<archiveClasses>true</archiveClasses>
-					<webResources>
-						<resource>
-							<directory>legacyworlds-web/legacyworlds-web-main/WebContent</directory>
-						</resource>
-					</webResources>
-				</configuration>
-				<executions>
-					<execution>
-						<id>build-main-war</id>
-						<phase>package</phase>
-						<goals>
-							<goal>war</goal>
-						</goals>
-					</execution>
-				</executions>
-			</plugin>
-
-		</plugins>
-	</build>
-
-</project>
\ No newline at end of file
diff --git a/legacyworlds-web/pom.xml b/legacyworlds-web/pom.xml
index 67a4c72..5f9b883 100644
--- a/legacyworlds-web/pom.xml
+++ b/legacyworlds-web/pom.xml
@@ -4,19 +4,60 @@
 	<parent>
 		<artifactId>legacyworlds</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds/pom.xml</relativePath>
 	</parent>
 
-	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-web</artifactId>
-	<version>5.99.1</version>
 	<packaging>pom</packaging>
-	<name>Legacy Worlds web sites</name>
+	<name>Legacy Worlds - Web</name>
 	<description>Root module for Legacy Worlds web sites</description>
 
 	<modules>
-		<module>legacyworlds-web-main</module>
-		<module>legacyworlds-web-admin</module>
-		<module>legacyworlds-web-beans</module>
+		<module>../legacyworlds-web-main</module>
+		<module>../legacyworlds-web-admin</module>
+		<module>../legacyworlds-web-beans</module>
 	</modules>
-</project>
\ No newline at end of file
+
+
+	<build>
+		<pluginManagement>
+			<plugins>
+			
+				<!--
+				   - Default configuration for the WAR plug-in
+				  -->
+				<plugin>
+					<artifactId>maven-war-plugin</artifactId>
+					<configuration>
+						<attachClasses>true</attachClasses>
+						<archiveClasses>true</archiveClasses>
+						<webResources>
+							<!-- Copy resources that do not need filtering -->
+							<resource>
+								<directory>Content/Raw</directory>
+								<filtering>false</filtering>
+							</resource>
+							<!-- Copy resources that need filtering to WEB-INF/ -->
+							<resource>
+								<directory>Content/Filtered</directory>
+								<targetPath>WEB-INF</targetPath>
+								<filtering>true</filtering>
+							</resource>
+						</webResources>
+					</configuration>
+					<executions>
+						<execution>
+							<id>build-war</id>
+							<phase>package</phase>
+							<goals>
+								<goal>war</goal>
+							</goals>
+						</execution>
+					</executions>
+				</plugin>
+
+			</plugins>
+		</pluginManagement>
+	</build>
+</project>
diff --git a/legacyworlds/.gitignore b/legacyworlds/.gitignore
new file mode 100644
index 0000000..2fd5b12
--- /dev/null
+++ b/legacyworlds/.gitignore
@@ -0,0 +1 @@
+legacyworlds.tar.bz2
diff --git a/build-tools/execute-clit.sh b/legacyworlds/build-tools/execute-clit.sh
similarity index 100%
rename from build-tools/execute-clit.sh
rename to legacyworlds/build-tools/execute-clit.sh
diff --git a/build-tools/server-config-example.sh b/legacyworlds/build-tools/server-config-example.sh
similarity index 100%
rename from build-tools/server-config-example.sh
rename to legacyworlds/build-tools/server-config-example.sh
diff --git a/build-tools/start-server.sh b/legacyworlds/build-tools/start-server.sh
similarity index 100%
rename from build-tools/start-server.sh
rename to legacyworlds/build-tools/start-server.sh
diff --git a/build-tools/stop-server.sh b/legacyworlds/build-tools/stop-server.sh
similarity index 100%
rename from build-tools/stop-server.sh
rename to legacyworlds/build-tools/stop-server.sh
diff --git a/legacyworlds/doc/Eclipse-import.txt b/legacyworlds/doc/Eclipse-import.txt
new file mode 100644
index 0000000..5948161
--- /dev/null
+++ b/legacyworlds/doc/Eclipse-import.txt
@@ -0,0 +1,58 @@
+Legacy Worlds Eclipse projects import
+======================================
+
+While it is not strictly necessary, it is recommended to import the Legacy
+Worlds code into a separate workspace, as it will be easier to use e.g. the LW
+code formatting configuration without interfering with other projects.
+
+Before you start, make sure the following Eclipse plug-ins are active:
+* M2E (the Maven integration plug-in)
+* EGit or equivalent
+
+This guide assumes that you've already cloned the source code from the
+server's Git repositories.
+
+
+Importing the projects
+-----------------------
+
+1) Select the Java perspective (the Java EE perspective tries to be clever and
+fails).
+2) In the Package Explorer, right-click then select Import.
+3) Select Maven > Existing Maven projects.
+4) Select the root directory of your local Git repository. It will list all
+parts of the Legacy Worlds code. Make sure they're all selected, then click the
+Finish button.
+
+The projects should be imported, and after a while, they should all build
+successfully. This may take a few minutes, as Maven will download all
+dependencies and plug-ins.
+
+At this point, Eclipse knows of the projects, but has no clue they're in a Git
+repository.
+
+
+Git support
+------------
+
+1) Open the Git Repositories perspective, and add the local repository to the
+list.
+2) Go back to the Java perspective, select all projects, right-click then select
+Team > Share Project
+3) Select the Git repository type,
+4) Select the local repository then click Finish.
+
+Eclipse will proceed to "link" the local repository with the projects in the
+workspace.
+
+
+Editor set-up
+--------------
+
+1) Select Window > Preferences in the main menu
+2) Select Java > Code Style > Formatter
+3) Click the Import button and select the eclipse-code-format.xml file found in
+the legacyworlds/ directory.
+4) Select Java > Code Style > Clean Up
+5) Click the Import button and select the eclipse-code-cleanup.xml file found
+in the legacyworlds/ directory.
diff --git a/legacyworlds/doc/TODO.txt b/legacyworlds/doc/TODO.txt
new file mode 100644
index 0000000..1e00dba
--- /dev/null
+++ b/legacyworlds/doc/TODO.txt
@@ -0,0 +1,48 @@
+NOTES:
+ -> This is *NOT* a complete list. Some of the tasks here will be decomposed
+    later and new tasks will be added as we go.
+ -> If you feel like trying to take on some task, talk to me about it.
+ -> Tasks that start with '!' are urgent, tasks that start with '?' are
+    low-priority.
+
+
+PROJECT:
+
+  ! Write the new build system
+
+  * Update all dependencies to the latest versions
+
+  
+SERVER & DATABASE:
+  
+  ! Migrate to PostgreSQL 9.1
+		-> add logging to some of the bigger stored procedures through an
+			external connection 
+
+  ! Add some form of database version control to allow easier updates
+		-> once migrated to Pg9.1, there are some interesting extensions that
+			may be satisfactory
+			
+  * Replace all single-precision reals with double precision reals
+
+  * Add a tool to initialise the database
+  
+  * I18N loader: improve text file loading (use relative paths)
+
+  * Replace current authentication information (pair of hashes) with a
+	salted SHA512 hash.
+		-> Make sure it is still possible to import old passwords using the
+			new implementation.
+
+  ? Mailer configuration shouldn't be hardcoded
+
+				
+GENERAL:
+
+  ! Add comments wherever necessary
+	-> that would be "everywhere"
+	
+  * Write unit tests
+	  ? Check out PostgreSQL extensions to test stored procedures
+	  * Write unit tests for new code
+	  ? add more tests if possible	
\ No newline at end of file
diff --git a/legacyworlds/doc/local-deployment.txt b/legacyworlds/doc/local-deployment.txt
new file mode 100644
index 0000000..bddaa9b
--- /dev/null
+++ b/legacyworlds/doc/local-deployment.txt
@@ -0,0 +1,86 @@
+Deploying a local Legacy Worlds server
+=======================================
+
+
+1) Before the server can be deployed, it is necessary to build the whole thing.
+Enter the legacyworlds/ directory, and run:
+
+	mvn package
+
+The server's distribution will be prepared in the
+legacyworlds-server-DIST/target/legacyworlds-server-1.0.0-0/ directory. It
+includes the server's main archive and libraries, the SQL scripts that
+initialise the database and the initial game data.
+
+
+
+2) The database needs to be created. In order to do that, you need to have a
+PostgreSQL instance onto which you can connect locally, without password, as an
+administrative user.
+
+In the server distribution's sql/ directory, copy the db-config.sample.txt file
+to db-config.txt, and edit it. The "admin" field should be the name of the
+administrative user, the "db" field the name of the database, the "user" field
+the name of the Legacy Worlds user, and the "password" field the Legacy Worlds
+user's password.
+
+Once this is done, connect to PostgreSQL and run:
+
+	\i database.sql
+
+
+3) Configure the server's data source by copying the data-source.sample.xml file
+to data-source.xml, and modifying the JDBC URL, user name and password to match
+the values used at step 2.  
+
+
+4) Load the game's base data. In order to do that, run the following commands
+from the root of the server's distribution: 
+
+	java -jar legacyworlds-server-main-1.0.0-0.jar \
+		--run-tool ImportText data/i18n-text.xml
+	java -jar legacyworlds-server-main-1.0.0-0.jar \
+		--run-tool ImportTechs data/techs.xml
+	java -jar legacyworlds-server-main-1.0.0-0.jar \
+		--run-tool ImportBuildables data/buildables.xml 
+
+
+5) Run the server for a few seconds by typing:
+
+	java -jar legacyworlds-server-main-1.0.0-0.jar
+
+Stop it by running
+
+	java -jar legacyworlds-server-main-1.0.0-0.jar  --run-tool Stop
+
+in another terminal.
+
+
+6) Create the server's super user. First, create an inactive user account:
+
+	java -jar legacyworlds-server-main-0.0-0.jar --run-tool CreateUser
+	
+The tool will prompt for an email address, password (which /will/ appear on the
+console) and language identifier ('en' or 'fr' are supported). Please note that
+the usual password strength restrictions apply.
+
+Once this has been completed, make this user a superuser by running:
+
+	java -jar legacyworlds-server-main-1.0.0-0.jar \
+		--run-tool CreateSuperuser 'user@example.org VisibleName'
+
+Replace 'user@example.org' with the user's actual email address, and
+'VisibleName' with the name of the administrator as seen by the users.
+
+
+7) Web sites:
+	* Set up a local Tomcat instance.
+	* Make sure its manager application is enabled.
+	* Make sure there is a local, XML-based user database configured.
+	* Create an "admin" user with an empty password and the "manager" role.
+	* From the legacyworlds/ directory, run:
+		mvn package tomcat:deploy
+	This will deploy the main and administrative sites to /lwmain and /lwadmin,
+respectively.
+	* If you need to redeploy the sites later, you need to run
+		mvn package tomcat:redeploy
\ No newline at end of file
diff --git a/legacyworlds/eclipse-code-cleanup.xml b/legacyworlds/eclipse-code-cleanup.xml
new file mode 100644
index 0000000..699d5f8
--- /dev/null
+++ b/legacyworlds/eclipse-code-cleanup.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<profiles version="2">
+<profile kind="CleanUpProfile" name="Legacy Worlds" version="2">
+<setting id="cleanup.remove_unused_private_fields" value="true"/>
+<setting id="cleanup.always_use_parentheses_in_expressions" value="false"/>
+<setting id="cleanup.never_use_blocks" value="false"/>
+<setting id="cleanup.add_missing_deprecated_annotations" value="true"/>
+<setting id="cleanup.remove_unused_private_methods" value="true"/>
+<setting id="cleanup.convert_to_enhanced_for_loop" value="false"/>
+<setting id="cleanup.remove_unnecessary_nls_tags" value="true"/>
+<setting id="cleanup.sort_members" value="false"/>
+<setting id="cleanup.remove_unused_local_variables" value="false"/>
+<setting id="cleanup.remove_unused_private_members" value="false"/>
+<setting id="cleanup.never_use_parentheses_in_expressions" value="true"/>
+<setting id="cleanup.remove_unnecessary_casts" value="true"/>
+<setting id="cleanup.make_parameters_final" value="false"/>
+<setting id="cleanup.use_this_for_non_static_field_access" value="true"/>
+<setting id="cleanup.use_blocks" value="true"/>
+<setting id="cleanup.remove_private_constructors" value="true"/>
+<setting id="cleanup.always_use_this_for_non_static_method_access" value="true"/>
+<setting id="cleanup.remove_trailing_whitespaces_all" value="true"/>
+<setting id="cleanup.always_use_this_for_non_static_field_access" value="true"/>
+<setting id="cleanup.use_this_for_non_static_field_access_only_if_necessary" value="false"/>
+<setting id="cleanup.add_default_serial_version_id" value="true"/>
+<setting id="cleanup.make_type_abstract_if_missing_method" value="false"/>
+<setting id="cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class" value="true"/>
+<setting id="cleanup.make_variable_declarations_final" value="true"/>
+<setting id="cleanup.add_missing_nls_tags" value="false"/>
+<setting id="cleanup.format_source_code" value="true"/>
+<setting id="cleanup.add_missing_override_annotations" value="true"/>
+<setting id="cleanup.qualify_static_method_accesses_with_declaring_class" value="false"/>
+<setting id="cleanup.remove_unused_private_types" value="true"/>
+<setting id="cleanup.make_local_variable_final" value="false"/>
+<setting id="cleanup.add_missing_methods" value="false"/>
+<setting id="cleanup.add_missing_override_annotations_interface_methods" value="true"/>
+<setting id="cleanup.correct_indentation" value="false"/>
+<setting id="cleanup.remove_unused_imports" value="true"/>
+<setting id="cleanup.remove_trailing_whitespaces_ignore_empty" value="false"/>
+<setting id="cleanup.make_private_fields_final" value="true"/>
+<setting id="cleanup.add_generated_serial_version_id" value="false"/>
+<setting id="cleanup.organize_imports" value="true"/>
+<setting id="cleanup.sort_members_all" value="false"/>
+<setting id="cleanup.remove_trailing_whitespaces" value="true"/>
+<setting id="cleanup.use_blocks_only_for_return_and_throw" value="false"/>
+<setting id="cleanup.use_parentheses_in_expressions" value="false"/>
+<setting id="cleanup.add_missing_annotations" value="true"/>
+<setting id="cleanup.qualify_static_field_accesses_with_declaring_class" value="false"/>
+<setting id="cleanup.use_this_for_non_static_method_access_only_if_necessary" value="false"/>
+<setting id="cleanup.use_this_for_non_static_method_access" value="true"/>
+<setting id="cleanup.qualify_static_member_accesses_through_instances_with_declaring_class" value="true"/>
+<setting id="cleanup.add_serial_version_id" value="false"/>
+<setting id="cleanup.always_use_blocks" value="true"/>
+<setting id="cleanup.qualify_static_member_accesses_with_declaring_class" value="true"/>
+<setting id="cleanup.format_source_code_changes_only" value="false"/>
+</profile>
+</profiles>
diff --git a/eclipse-code-format.xml b/legacyworlds/eclipse-code-format.xml
similarity index 100%
rename from eclipse-code-format.xml
rename to legacyworlds/eclipse-code-format.xml
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
new file mode 100644
index 0000000..fd00897
--- /dev/null
+++ b/legacyworlds/pom.xml
@@ -0,0 +1,344 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>com.deepclone.lw</groupId>
+	<artifactId>legacyworlds</artifactId>
+	<version>1.0.0</version>
+	<packaging>pom</packaging>
+	<name>Legacy Worlds</name>
+	<description>Main Maven project for LW</description>
+
+	<!--
+	   - BUILD CONFIGURATION
+	   -
+	   - Make sure everything is built for and using Java 6, and declare all
+	   - other plug-ins to ease upgrades.
+	 -->
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>2.3.2</version>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+		
+		<pluginManagement>
+			<plugins>
+
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-assembly-plugin</artifactId>
+					<version>2.2.2</version>
+				</plugin>
+
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-jar-plugin</artifactId>
+					<version>2.3.2</version>
+				</plugin>
+
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-war-plugin</artifactId>
+					<version>2.1.1</version>
+				</plugin>
+				
+				<plugin>
+					<groupId>org.codehaus.mojo</groupId>
+					<artifactId>tomcat-maven-plugin</artifactId>
+					<version>1.1</version>
+				</plugin>
+
+			</plugins>
+		</pluginManagement>
+	</build>
+
+	<!--
+	   - REPOSITORIES
+	   -
+	   - We need the Spring framework's own repository
+	  -->
+	<repositories>
+
+		<repository>
+			<id>com.springsource.repository.bundles.release</id>
+			<name>Spring framework</name>
+			<url>http://repository.springsource.com/maven/bundles/release</url>
+		</repository>
+		<repository>
+			<id>com.springsource.repository.bundles.external</id>
+			<name>Spring framework - external</name>
+			<url>http://repository.springsource.com/maven/bundles/external</url>
+		</repository>
+
+	</repositories>
+
+
+	<properties>
+		<!-- Make sure the source is seen as UTF8 -->
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+		<!--
+		   - VERSION NUMBERS
+		   -
+		   - These properties are used in sub-modules to identify the
+		   - current version and build number of the files.
+		   -
+		   - They are set by the build system, and should NEVER be changed
+		   - manually.
+		  -->
+		<legacyworlds.version.main>1.0</legacyworlds.version.main>
+		<legacyworlds.version.release>0</legacyworlds.version.release>
+		<legacyworlds.version.build>0</legacyworlds.version.build>
+		<legacyworlds.version.string>DEV</legacyworlds.version.string>
+
+		<!--
+		   - DEPENDENCY VERSIONS
+		  -->
+		<dep.springframework>3.0.3.RELEASE</dep.springframework>
+		<dep.slf4j>1.5.11</dep.slf4j>
+		<dep.log4j>1.2.16</dep.log4j>
+		<dep.dbcp>1.2.2</dep.dbcp>
+		<dep.codecs>1.4</dep.codecs>
+		<dep.cglib>2.2</dep.cglib>
+		<dep.mail>1.4.1</dep.mail>
+		<dep.xstream>1.3.1</dep.xstream>
+		<dep.junit>4.7</dep.junit>
+		<dep.freemarker>2.3.16</dep.freemarker>
+		<dep.servlet>2.5</dep.servlet>
+		<dep.postgresql>8.4-701.jdbc4</dep.postgresql>
+	</properties>
+
+
+	<!--
+	   - DEPENDENCY MANAGEMENT
+	  -->
+	<dependencyManagement>
+		<dependencies>
+
+			<!-- INTERNAL DEPENDENCIES -->
+			<dependency>
+				<artifactId>legacyworlds-server-beans-accounts</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-bt</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-eventlog</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-i18n</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-mailer</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-naming</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-simple</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-system</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-user</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.deepclone.lw</groupId>
+				<artifactId>legacyworlds-server-data</artifactId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.deepclone.lw</groupId>
+				<artifactId>legacyworlds-server-interfaces</artifactId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.deepclone.lw</groupId>
+				<artifactId>legacyworlds-session</artifactId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.deepclone.lw</groupId>
+				<artifactId>legacyworlds-server-utils</artifactId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.deepclone.lw</groupId>
+				<artifactId>legacyworlds-session</artifactId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.deepclone.lw</groupId>
+				<artifactId>legacyworlds-utils</artifactId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.deepclone.lw</groupId>
+				<artifactId>legacyworlds-web-beans</artifactId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
+
+			<!-- EXTERNAL DEPENDENCIES -->
+
+			<!-- XStream -->
+			<dependency>
+				<groupId>com.thoughtworks.xstream</groupId>
+				<artifactId>xstream</artifactId>
+				<version>${dep.xstream}</version>
+			</dependency>
+
+			<!-- Spring framework -->
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-core</artifactId>
+				<version>${dep.springframework}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-context</artifactId>
+				<version>${dep.springframework}</version>
+				<exclusions>
+					<exclusion>
+						<groupId>commons-logging</groupId>
+						<artifactId>commons-logging</artifactId>
+					</exclusion>
+				</exclusions>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-jdbc</artifactId>
+				<version>${dep.springframework}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-context-support</artifactId>
+				<version>${dep.springframework}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-test</artifactId>
+				<version>${dep.springframework}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-webmvc</artifactId>
+				<version>${dep.springframework}</version>
+			</dependency>
+
+			<!-- Logging (SLF4J + Log4J) -->
+			<dependency>
+				<groupId>org.slf4j</groupId>
+				<artifactId>jcl-over-slf4j</artifactId>
+				<version>${dep.slf4j}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.slf4j</groupId>
+				<artifactId>slf4j-api</artifactId>
+				<version>${dep.slf4j}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.slf4j</groupId>
+				<artifactId>slf4j-log4j12</artifactId>
+				<version>${dep.slf4j}</version>
+			</dependency>
+
+			<dependency>
+				<groupId>log4j</groupId>
+				<artifactId>log4j</artifactId>
+				<version>${dep.log4j}</version>
+			</dependency>
+
+			<!-- Apache commons -->
+			<dependency>
+				<groupId>commons-codec</groupId>
+				<artifactId>commons-codec</artifactId>
+				<version>${dep.codecs}</version>
+			</dependency>
+			<dependency>
+				<groupId>commons-dbcp</groupId>
+				<artifactId>commons-dbcp</artifactId>
+				<version>${dep.dbcp}</version>
+			</dependency>
+
+			<!-- Code generation library -->
+			<dependency>
+				<groupId>cglib</groupId>
+				<artifactId>cglib</artifactId>
+				<version>${dep.cglib}</version>
+			</dependency>
+
+			<!-- Java Mail extension -->
+			<dependency>
+				<groupId>javax.mail</groupId>
+				<artifactId>mail</artifactId>
+				<version>${dep.mail}</version>
+			</dependency>
+
+			<!-- PostgreSQL -->
+			<dependency>
+				<groupId>postgresql</groupId>
+				<artifactId>postgresql</artifactId>
+				<version>${dep.postgresql}</version>
+			</dependency>
+
+
+			<!-- JUnit -->
+			<dependency>
+				<groupId>junit</groupId>
+				<artifactId>junit</artifactId>
+				<version>${dep.junit}</version>
+			</dependency>
+
+
+			<!-- Web stuff (FreeMarker, servlet specs, etc...) -->
+			<dependency>
+				<groupId>javax.servlet</groupId>
+				<artifactId>servlet-api</artifactId>
+				<version>${dep.servlet}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.freemarker</groupId>
+				<artifactId>freemarker</artifactId>
+				<version>${dep.freemarker}</version>
+			</dependency>
+
+		</dependencies>
+	</dependencyManagement>
+
+
+	<!--
+	   - MODULES
+	  -->
+	<modules>
+		<module>../legacyworlds-server</module>
+		<module>../legacyworlds-session</module>
+		<module>../legacyworlds-web</module>
+		<module>../legacyworlds-utils</module>
+	</modules>
+
+</project>
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index 8f8a680..0000000
--- a/pom.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<groupId>com.deepclone.lw</groupId>
-	<artifactId>legacyworlds</artifactId>
-	<version>5.99.1</version>
-	<packaging>pom</packaging>
-	<name>Legacy Worlds</name>
-	<description>Main Maven project for LW</description>
-
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-compiler-plugin</artifactId>
-				<version>2.2</version>
-				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
-				</configuration>
-			</plugin>
-		</plugins>
-	</build>
-
-	<repositories>
-
-		<repository>
-			<id>com.springsource.repository.bundles.release</id>
-			<name>Spring framework</name>
-			<url>http://repository.springsource.com/maven/bundles/release</url>
-		</repository>
-		<repository>
-			<id>com.springsource.repository.bundles.external</id>
-			<name>Spring framework - external</name>
-			<url>http://repository.springsource.com/maven/bundles/external</url>
-		</repository>
-
-		<repository>
-			<id>jboss</id>
-			<url>http://repository.jboss.com/maven2</url>
-			<releases>
-				<enabled>true</enabled>
-			</releases>
-			<snapshots>
-				<enabled>false</enabled>
-			</snapshots>
-		</repository>
-
-	</repositories>
-
-	<properties>
-		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-		<org.springframework.version>3.0.3.RELEASE</org.springframework.version>
-		<log4j.version>1.2.16</log4j.version>
-		<org.slf4j.version>1.5.11</org.slf4j.version>
-		<commons.dbcp.version>1.2.2</commons.dbcp.version>
-		<commons.codec.version>1.4</commons.codec.version>
-		<cglib.version>2.2</cglib.version>
-		<javax.mail.version>1.4.1</javax.mail.version>
-		<com.thoughtworks.xstream.version>1.3.1</com.thoughtworks.xstream.version>
-		<junit.version>4.7</junit.version>
-		<org.freemarker.version>2.3.16</org.freemarker.version>
-	</properties>
-
-	<modules>
-		<module>legacyworlds-server</module>
-		<module>legacyworlds-session</module>
-		<module>legacyworlds-web</module>
-		<module>legacyworlds-utils</module>
-	</modules>
-
-</project>
\ No newline at end of file
diff --git a/runsrv.sh b/runsrv.sh
deleted file mode 100755
index d761187..0000000
--- a/runsrv.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-BASE="`dirname $0`"
-cd "$BASE/legacyworlds-server/legacyworlds-server-main"
-java -jar target/legacyworlds-server-main-5.99.1.jar
diff --git a/runtool.sh b/runtool.sh
deleted file mode 100755
index 30ba8b1..0000000
--- a/runtool.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-BASE="`dirname $0`"
-cd "$BASE/legacyworlds-server/legacyworlds-server-main"
-java -jar target/legacyworlds-server-main-5.99.1.jar --run-tool $1 "$2"

From f682594cbd743b079b822e2db0854986be4b57ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 15 Dec 2011 13:41:27 +0100
Subject: [PATCH 03/94] Finished migration to Git and the build system

* Added web interface packaging
* Added documentation about the development workflow
---
 legacyworlds-web-DIST/pom.xml     | 60 +++++++++++++++++++++++++++++++
 legacyworlds-web-DIST/src/web.xml | 28 +++++++++++++++
 legacyworlds-web/pom.xml          |  1 +
 legacyworlds/doc/TODO.txt         |  4 +--
 legacyworlds/doc/workflow.txt     | 53 +++++++++++++++++++++++++++
 5 files changed, 143 insertions(+), 3 deletions(-)
 create mode 100644 legacyworlds-web-DIST/pom.xml
 create mode 100644 legacyworlds-web-DIST/src/web.xml
 create mode 100644 legacyworlds/doc/workflow.txt

diff --git a/legacyworlds-web-DIST/pom.xml b/legacyworlds-web-DIST/pom.xml
new file mode 100644
index 0000000..e5d3890
--- /dev/null
+++ b/legacyworlds-web-DIST/pom.xml
@@ -0,0 +1,60 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds-web</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-web/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-web-DIST</artifactId>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+
+	<packaging>pom</packaging>
+	<name>Legacy Worlds - Web - Packaging</name>
+	<description>This Maven module is responsible for creating the Legacy Worlds web interface's packaging.</description>
+
+
+	<!-- Depend on both WAR projects -->
+	<dependencies>
+		<dependency>
+			<groupId>com.deepclone.lw</groupId>
+			<artifactId>legacyworlds-web-main</artifactId>
+			<version>${project.version}</version>
+			<type>war</type>
+		</dependency>
+		<dependency>
+			<groupId>com.deepclone.lw</groupId>
+			<artifactId>legacyworlds-web-admin</artifactId>
+			<version>${project.version}</version>
+			<type>war</type>
+		</dependency>
+	</dependencies>
+
+	<!-- Use the assembly plug-in to generate the distribution -->
+	<build>
+		<plugins>
+			<plugin>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>distribution-assembly</id>
+						<phase>package</phase>
+						<goals>
+							<goal>single</goal>
+						</goals>
+						<configuration>
+							<attach>false</attach>
+							<finalName>legacyworlds-web-${project.version}</finalName>
+							<descriptors>
+								<descriptor>src/web.xml</descriptor>
+							</descriptors>
+							<appendAssemblyId>false</appendAssemblyId>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/legacyworlds-web-DIST/src/web.xml b/legacyworlds-web-DIST/src/web.xml
new file mode 100644
index 0000000..f7ffb97
--- /dev/null
+++ b/legacyworlds-web-DIST/src/web.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly
+	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+
+	<!-- Legacy Worlds web interface assembly -->
+
+	<id>legacyworlds-web</id>
+	<formats>
+		<format>dir</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+
+	<dependencySets>
+
+		<dependencySet>
+			<useProjectArtifact>false</useProjectArtifact>
+			<outputDirectory>.</outputDirectory>
+			<unpack>false</unpack>
+			<includes>
+				<include>*:war</include>
+			</includes>
+		</dependencySet>
+
+	</dependencySets>
+
+</assembly>
diff --git a/legacyworlds-web/pom.xml b/legacyworlds-web/pom.xml
index 5f9b883..4f90e51 100644
--- a/legacyworlds-web/pom.xml
+++ b/legacyworlds-web/pom.xml
@@ -17,6 +17,7 @@
 		<module>../legacyworlds-web-main</module>
 		<module>../legacyworlds-web-admin</module>
 		<module>../legacyworlds-web-beans</module>
+		<module>../legacyworlds-web-DIST</module>
 	</modules>
 
 
diff --git a/legacyworlds/doc/TODO.txt b/legacyworlds/doc/TODO.txt
index 1e00dba..771e724 100644
--- a/legacyworlds/doc/TODO.txt
+++ b/legacyworlds/doc/TODO.txt
@@ -8,8 +8,6 @@ NOTES:
 
 PROJECT:
 
-  ! Write the new build system
-
   * Update all dependencies to the latest versions
 
   
@@ -45,4 +43,4 @@ GENERAL:
   * Write unit tests
 	  ? Check out PostgreSQL extensions to test stored procedures
 	  * Write unit tests for new code
-	  ? add more tests if possible	
\ No newline at end of file
+	  ? add more tests if possible	
diff --git a/legacyworlds/doc/workflow.txt b/legacyworlds/doc/workflow.txt
new file mode 100644
index 0000000..77a2d4d
--- /dev/null
+++ b/legacyworlds/doc/workflow.txt
@@ -0,0 +1,53 @@
+Legacy Worlds development work flow
+====================================
+
+Development is based off a set of Git repositories on the web-based repository
+server. All repositories can be found under https://svn.legacyworlds.com/git/
+
+
+Repositories
+-------------
+
+* Developer repositories: each developer has access to a personal repository,
+  under dev/{developer name} on the server. All developers have read access to
+  the others' repositories.
+
+  Address: https://svn.legacyworlds.com/git/dev/{developer name}
+
+* Staging repository: this repository is meant to be used as the source for
+  all local repositories. All developers may read from it. However, only
+  TSeeker can push code to it.
+
+  Address: https://svn.legacyworlds.com/git/staging
+
+* Main repository: this repository contains the code that has been processed
+  by the build system, along with the binaries if the last build succeeded.
+  While all developers can read from it, it should not be used as the source
+  for local copies.
+
+  Address: https://svn.legacyworlds.com/git/main
+
+
+Branches
+---------
+
+On the staging and main repositories, the following branches exist:
+
+* master - this is the main development branch. All code destined to end up
+  in the next version of Legacy Worlds lives there.
+
+* milestone1 - maintenance branch for Legacy Worlds Beta 6 milestone 1.
+
+
+Development process
+--------------------
+
+Work on some branch on your local copy.
+	-> Don't hesitate to push to your developer repository on the server
+	   as a means of backup.
+	-> Rebase to the original branch whenever it changes.
+
+When you are done with your work on a branch:
+ 1) push to some branch on your developer repository on the server,
+ 2) notify TSeeker that the branch should be pulled to the staging repository.
+While this is easier to handle on IRC, a mail is fine as well.

From be3106c46312d18409c082b48fbdd4b7b8cbef74 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 15 Dec 2011 15:38:46 +0100
Subject: [PATCH 04/94] Database management changes

* Added in-base logging through a foreign data wrapper, which is only
possible using PostgreSQL 9.1
* Renamed database-related configuration files to indicate that they are
samples, and added the "real" names to the Git ignore list. Server
distribution modified accordingly.
* Removed PL/PgSQL registration (it was only necessary on 8.4)
* Added pgTAP SQL definitions and a script which will (hopefully) be
executed by the build system after the main Java build in order to
execute database unit tests. The script supports both admin- and user-
level testing. I also added a few tests to make sure the testing framework
actually runs them.
* Added documentation about the database definitions structure
---
 .gitignore                                    |    2 +
 build/post-build.sh                           |   55 +
 legacyworlds-server-DIST/src/server.xml       |   21 +-
 .../db-structure/database.sql                 |   19 +-
 .../{db-config.txt => db-config.sample.txt}   |    0
 .../parts/functions/002-sys-functions.sql     |    2 +-
 .../parts/functions/005-logs-functions.sql    |   36 +
 .../parts/functions/070-users-functions.sql   |    4 +-
 .../parts/functions/140-planets-functions.sql |    6 +-
 .../parts/functions/150-battle-functions.sql  |    2 +-
 .../parts/functions/165-fleets-functions.sql  |   10 +-
 .../functions/180-messages-functions.sql      |    8 +-
 .../parts/functions/190-admin-functions.sql   |    4 +-
 .../db-structure/tests/pgtap.sql              | 7402 +++++++++++++++++
 .../db-structure/tests/user/priv/sys/logs.sql |   39 +
 ...data-source.xml => data-source.sample.xml} |    0
 legacyworlds/doc/TODO.txt                     |   15 +-
 legacyworlds/doc/database.txt                 |   91 +
 18 files changed, 7669 insertions(+), 47 deletions(-)
 create mode 100755 build/post-build.sh
 rename legacyworlds-server-data/db-structure/{db-config.txt => db-config.sample.txt} (100%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/pgtap.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/sys/logs.sql
 rename legacyworlds-server-main/{data-source.xml => data-source.sample.xml} (100%)
 create mode 100644 legacyworlds/doc/database.txt

diff --git a/.gitignore b/.gitignore
index 1e07caf..94fbf38 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@ target
 .settings
 .classpath
 .project
+legacyworlds-server-main/data-source.xml
+legacyworlds-server-data/db-structure/db-config.txt
diff --git a/build/post-build.sh b/build/post-build.sh
new file mode 100755
index 0000000..3fb786e
--- /dev/null
+++ b/build/post-build.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+cd legacyworlds-server-data/db-structure
+if ! [ -f db-config.txt ]; then
+	cat > db-config.txt <<EOF
+admin=$USER
+db=tests
+user=tests
+password=tests
+EOF
+fi
+TEST_DATABASE="`grep ^db= db-config.txt | sed -e s/.*=//`"
+TEST_USER="`grep ^user= db-config.txt | sed -e s/.*=//`"
+
+echo
+echo
+echo "======================================================================"
+echo "LOADING DATABASE DEFINITION ..."
+echo "======================================================================"
+echo
+echo
+psql -vQUIET=1 -vON_ERROR_STOP=1 -e --file database.sql || exit 1
+
+if ! [ -d tests/admin ] && ! [ -d tests/user ]; then
+	echo
+	echo
+	echo "WARNING: no unit tests to run"
+	echo
+	echo
+	exit 0
+fi
+
+echo
+echo
+echo "======================================================================"
+echo "LOADING TEST FRAMEWORK ..."
+echo "======================================================================"
+echo
+echo
+psql -vQUIET=1 -f tests/pgtap.sql $TEST_DATABASE || exit 1
+
+echo
+echo
+echo "======================================================================"
+echo "RUNNING DATABASE TESTS ..."
+echo "======================================================================"
+echo
+echo
+cd tests
+if [ -d admin ]; then
+	pg_prove -d $TEST_DATABASE `find admin/ -type f -name '*.sql'` || exit 1
+fi
+if [ -d user ]; then
+	pg_prove -U $TEST_USER -d $TEST_DATABASE `find user/ -type f -name '*.sql'` || exit 1
+fi
diff --git a/legacyworlds-server-DIST/src/server.xml b/legacyworlds-server-DIST/src/server.xml
index 4250052..20a5255 100644
--- a/legacyworlds-server-DIST/src/server.xml
+++ b/legacyworlds-server-DIST/src/server.xml
@@ -46,13 +46,9 @@
 			<directory>../legacyworlds-server-data/db-structure</directory>
 			<outputDirectory>sql</outputDirectory>
 			<includes>
-				<include>*.sql</include>
-				<include>*/*.sql</include>
-				<include>*/*/*.sql</include>
+				<include>**.sql</include>
+				<include>db-config.sample.txt</include>
 			</includes>
-			<excludes>
-				<exclude>*.txt</exclude>
-			</excludes>
 		</fileSet>
 
 		<!-- Default data -->
@@ -71,21 +67,12 @@
 
 		<!-- Data source configuration -->
 		<file>
-			<source>../legacyworlds-server-main/data-source.xml</source>
-			<destName>data-source.sample.xml</destName>
+			<source>../legacyworlds-server-main/data-source.sample.xml</source>
 			<fileMode>0600</fileMode>
 			<outputDirectory>.</outputDirectory>
 		</file>
 		
-		<!-- Database definition variables -->
-		<file>
-			<source>../legacyworlds-server-data/db-structure/db-config.txt</source>
-			<destName>db-config.sample.txt</destName>
-			<fileMode>0600</fileMode>
-			<outputDirectory>sql</outputDirectory>
-		</file>
-
 	</files>
 
 
-</assembly>
\ No newline at end of file
+</assembly>
diff --git a/legacyworlds-server-data/db-structure/database.sql b/legacyworlds-server-data/db-structure/database.sql
index 675eeb6..7b8f951 100644
--- a/legacyworlds-server-data/db-structure/database.sql
+++ b/legacyworlds-server-data/db-structure/database.sql
@@ -12,7 +12,9 @@
 -- Read configuration from file
 \set pgadmin `grep ^admin= db-config.txt | sed -e s/.*=//`
 \set dbname `grep ^db= db-config.txt | sed -e s/.*=//`
+\set dbname_string '''':dbname''''
 \set dbuser `grep ^user= db-config.txt | sed -e s/.*=//`
+\set dbuser_string '''':dbuser''''
 \set dbupass ''''`grep ^password= db-config.txt | sed -e s/.*=// -e "s/'/''/g"`''''
 
 
@@ -34,8 +36,21 @@ GRANT CONNECT ON DATABASE :dbname TO :dbuser;
 -- Connect to the LW database with the PostgreSQL admin user
 \c :dbname :pgadmin
 
--- Register PL/PgSQL
-CREATE TRUSTED PROCEDURAL LANGUAGE plpgsql;
+-- Register the dblink extension
+CREATE EXTENSION dblink;
+
+-- Create foreign data wrapper and server used to write logs from within
+-- transanctions
+CREATE FOREIGN DATA WRAPPER pgsql
+	VALIDATOR postgresql_fdw_validator;
+CREATE SERVER srv_logging
+	FOREIGN DATA WRAPPER pgsql
+	OPTIONS ( hostaddr '127.0.0.1' , dbname :dbname_string );
+CREATE USER MAPPING FOR :dbuser
+	SERVER srv_logging
+	OPTIONS ( user :dbuser_string , password :dbupass );
+GRANT USAGE ON FOREIGN SERVER srv_logging TO :dbuser;
+
 
 BEGIN;
 
diff --git a/legacyworlds-server-data/db-structure/db-config.txt b/legacyworlds-server-data/db-structure/db-config.sample.txt
similarity index 100%
rename from legacyworlds-server-data/db-structure/db-config.txt
rename to legacyworlds-server-data/db-structure/db-config.sample.txt
diff --git a/legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql
index 79a3271..6f75303 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql
@@ -61,7 +61,7 @@ BEGIN
 	THEN
 		UPDATE sys.ticker SET status = 'RUNNING' , auto_start = NULL
 			WHERE id = task_id;
-		PERFORM sys.write_log( 'Ticker' , 'INFO'::log_level , 'Scheduled task ''' || t_name
+		PERFORM sys.write_sql_log( 'Ticker' , 'INFO'::log_level , 'Scheduled task ''' || t_name
 			|| ''' has been enabled' );
 	END IF;
 END;
diff --git a/legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql
index f7a7c0c..65bd5b7 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql
@@ -124,6 +124,42 @@ $$ LANGUAGE plpgsql;
 GRANT EXECUTE ON FUNCTION sys.write_log( TEXT , log_level , TEXT ) TO :dbuser;
 
 
+--
+-- Remotely append a system log entry
+--
+-- This function is called from within transactions in order to write to the
+-- system log. Unlike sys.write_log(), entries added through this function
+-- will not be lost if the transaction is rolled back.
+--
+-- Since the function is meant to be called from SQL code, it does not return
+-- anything as the identifier of the new entry is not required.
+--
+-- Parameters:
+--	_component	The component that is appending to the log
+--	_level		The log level
+--	_message	The message to write
+--
+
+CREATE OR REPLACE FUNCTION sys.write_sql_log( _component TEXT , _level log_level , _message TEXT )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $write_sql_log$
+BEGIN
+	PERFORM dblink_connect( 'cn_logging' , 'srv_logging' );
+	PERFORM * FROM dblink( 'cn_logging' ,
+		'SELECT * FROM sys.write_log( '
+			|| quote_literal( _component ) || ' , '''
+			|| _level::text || '''::log_level , '
+			|| quote_literal( _message ) || ' )'
+	) AS ( entry_id bigint );
+	PERFORM dblink_disconnect( 'cn_logging' );
+END;
+$write_sql_log$ LANGUAGE plpgsql;
+
+GRANT EXECUTE ON FUNCTION sys.write_sql_log( TEXT , log_level , TEXT ) TO :dbuser;
+
+
 
 --
 -- Append an exception log entry
diff --git a/legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql
index 46bb65f..0e666db 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql
@@ -181,7 +181,7 @@ BEGIN
 	ELSE
 		RAISE EXCEPTION 'Invalid account status % (account #%)' , st , a_id;
 	END IF;
-	PERFORM sys.write_log( 'AccountManagement' , 'ERROR'::log_level , 'Account re-activation mail could not be sent for account #' || a_id || ', deleting validation key' );
+	PERFORM sys.write_sql_log( 'AccountManagement' , 'ERROR'::log_level , 'Account re-activation mail could not be sent for account #' || a_id || ', deleting validation key' );
 END;
 $$ LANGUAGE plpgsql;
 
@@ -470,7 +470,7 @@ BEGIN
 		WHERE a.address = addr
 		FOR UPDATE;
 	IF NOT FOUND THEN
-		PERFORM sys.write_log( 'AccountManagement' , 'WARNING'::log_level , 'account for "'
+		PERFORM sys.write_sql_log( 'AccountManagement' , 'WARNING'::log_level , 'account for "'
 			|| addr || '" not found' );
 		account_error := 1;
 		RETURN;
diff --git a/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
index 06247c1..53bca46 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
@@ -990,7 +990,7 @@ DECLARE
 	st_dmg	REAL;
 	n_dest	INT;
 BEGIN
-	PERFORM sys.write_log( 'BattleUpdate' , 'TRACE'::log_level , 'Inflicting ' || dmg
+	PERFORM sys.write_sql_log( 'BattleUpdate' , 'TRACE'::log_level , 'Inflicting ' || dmg
 		|| ' damage to planet #' || p_id );
 
 	bp_id := NULL;
@@ -1008,7 +1008,7 @@ BEGIN
 			st_dmg := 0;
 		END IF;
 		
-		PERFORM sys.write_log( 'BattleUpdate' , 'TRACE'::log_level , 'Building type #' || rec.building_id
+		PERFORM sys.write_sql_log( 'BattleUpdate' , 'TRACE'::log_level , 'Building type #' || rec.building_id
 			|| ' - Damage ' || st_dmg || '; destruction: ' || n_dest );
 
 		-- Apply damage
@@ -1054,7 +1054,7 @@ DECLARE
 BEGIN
 	tick := sys.get_tick( ) - 1;
 	tot_damage := t_upkeep * d_ratio / debt;
-	PERFORM sys.write_log( 'EmpireDebt' , 'DEBUG'::log_level , 'Inflicting debt damage to buildings; total upkeep: '
+	PERFORM sys.write_sql_log( 'EmpireDebt' , 'DEBUG'::log_level , 'Inflicting debt damage to buildings; total upkeep: '
 		|| t_upkeep || ', damage ratio: ' || d_ratio || ', total damage: ' || tot_damage );
 
 	FOR p_rec IN SELECT ep.planet_id AS planet , b.id AS battle
diff --git a/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
index ef34b5e..d6a4607 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
@@ -653,7 +653,7 @@ BEGIN
 	IF NOT att THEN
 		st_power := battles.get_defence_power( b_id , tick );
 		tot_power := tot_power + st_power;
-		PERFORM sys.write_log( 'BattleUpdate' , 'TRACE'::log_level , 'About to inflict planet damage; total power: ' || tot_power
+		PERFORM sys.write_sql_log( 'BattleUpdate' , 'TRACE'::log_level , 'About to inflict planet damage; total power: ' || tot_power
 			|| '; planet power: ' || st_power || '; computed damage: ' || ( dmg * st_power / tot_power )::REAL );
 		IF st_power <> 0 THEN
 			PERFORM verse.inflict_battle_damage( planet , st_power , ( dmg * st_power / tot_power )::REAL , b_id , tick );
diff --git a/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
index f7d802c..7bb87dd 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
@@ -415,17 +415,17 @@ BEGIN
 			-- Fleet redirection
 			IF rec.is_ref_point IS NULL THEN
 				-- Fleet moving in outer space
-				PERFORM sys.write_log( 'Fleets' , 'TRACE'::log_level , 'About to perform outer space redirect; '
+				PERFORM sys.write_sql_log( 'Fleets' , 'TRACE'::log_level , 'About to perform outer space redirect; '
 					|| 'OOFT/2 = ' || rec.flight_time || '; start(x;y)= (' || rec.os_start_x || ';' || rec.os_start_y
 					|| '); dest(x;y)= (' || rec.x || ';' || rec.y || '); time left: ' || rec.mv_state_time );
 				SELECT INTO cx , cy c_x , c_y FROM fleets.compute_current_location(
 						rec.flight_time , rec.os_start_x , rec.os_start_y , rec.x , rec.y ,
 						rec.mv_state_time );
-				PERFORM sys.write_log( 'Fleets' , 'TRACE'::log_level , 'Computed current coordinates: (' || cx
+				PERFORM sys.write_sql_log( 'Fleets' , 'TRACE'::log_level , 'Computed current coordinates: (' || cx
 					|| ';' || cy || ')' );
 				SELECT INTO dur , s_dur duration , s_duration
 					FROM fleets.compute_outerspace_redirect( rec.flight_time , cx , cy , dest_id );
-				PERFORM sys.write_log( 'Fleets' , 'TRACE'::log_level , 'Computed new total/state duration: '
+				PERFORM sys.write_sql_log( 'Fleets' , 'TRACE'::log_level , 'Computed new total/state duration: '
 					|| dur || ' / ' || s_dur );
 				UPDATE fleets.ms_space SET start_x = cx , start_y = cy
 					WHERE movement_id = rec.id; 
@@ -1000,7 +1000,7 @@ DECLARE
 	found	INT;
 	deleted	INT;
 BEGIN
-	PERFORM sys.write_log( 'BattleUpdate' , 'TRACE'::log_level , 'Inflicting '
+	PERFORM sys.write_sql_log( 'BattleUpdate' , 'TRACE'::log_level , 'Inflicting '
 		|| dmg || ' damage to fleet #' || f_id );
 
 	-- Get total fleet power and battle protagonist
@@ -1094,7 +1094,7 @@ DECLARE
 BEGIN
 	tick := sys.get_tick( ) - 1;
 	tot_damage := t_upkeep * d_ratio / debt;
-	PERFORM sys.write_log( 'EmpireDebt' , 'DEBUG'::log_level , 'Inflicting debt damage to fleets; total upkeep: '
+	PERFORM sys.write_sql_log( 'EmpireDebt' , 'DEBUG'::log_level , 'Inflicting debt damage to fleets; total upkeep: '
 		|| t_upkeep || ', damage ratio: ' || d_ratio || ', total damage: ' || tot_damage );
 
 	FOR f_rec IN SELECT f.id AS fleet , f.status , f.location_id AS location ,
diff --git a/legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql
index e7319b0..65010e5 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql
@@ -585,13 +585,13 @@ BEGIN
 	-- Send message (or not)
 	IF do_send
 	THEN
-		PERFORM sys.write_log( 'Messages' , 'DEBUG'::log_level , 'Delivering message from empire #' || e_id
+		PERFORM sys.write_sql_log( 'Messages' , 'DEBUG'::log_level , 'Delivering message from empire #' || e_id
 			|| ' using method ' || s_meth || ' with target identifier ' || tg_id || ' (title text null? '
 			|| ( ttl_txt IS NULL ) || ' ; content text null? ' || (cnt_txt IS NULL) || ')' );
 		EXECUTE 'SELECT msgs.deliver_' || s_meth || '( $1 , $2 , $3 , $4 )'
 			USING e_id , tg_id , ttl_txt , cnt_txt;
 	ELSE
-		PERFORM sys.write_log( 'Messages' , 'DEBUG'::log_level , 'Simulated message delivery from empire #' || e_id
+		PERFORM sys.write_sql_log( 'Messages' , 'DEBUG'::log_level , 'Simulated message delivery from empire #' || e_id
 			|| ' using method ' || s_meth || ' with target identifier ' || tg_id || ' (title text null? '
 			|| ( ttl_txt IS NULL ) || ' ; content text null? ' || (cnt_txt IS NULL) || ')' );
 	END IF;
@@ -860,13 +860,13 @@ BEGIN
 	-- Send message (or not)
 	IF do_send
 	THEN
-		PERFORM sys.write_log( 'Messages' , 'DEBUG'::log_level , 'Delivering message from admin #' || a_id
+		PERFORM sys.write_sql_log( 'Messages' , 'DEBUG'::log_level , 'Delivering message from admin #' || a_id
 			|| ' using method ' || s_meth || ' with target identifier ' || tg_id || ' (title text null? '
 			|| ( ttl_txt IS NULL ) || ' ; content text null? ' || (cnt_txt IS NULL) || ')' );
 		EXECUTE 'SELECT msgs.deliver_admin_' || s_meth || '( $1 , $2 , $3 , $4 )'
 			USING a_id , tg_id , ttl_txt , cnt_txt;
 	ELSE
-		PERFORM sys.write_log( 'Messages' , 'DEBUG'::log_level , 'Simulated message delivery from admin #' || a_id
+		PERFORM sys.write_sql_log( 'Messages' , 'DEBUG'::log_level , 'Simulated message delivery from admin #' || a_id
 			|| ' using method ' || s_meth || ' with target identifier ' || tg_id || ' (title text null? '
 			|| ( ttl_txt IS NULL ) || ' ; content text null? ' || (cnt_txt IS NULL) || ')' );
 	END IF;
diff --git a/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
index b5bbf6a..d7221d9 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
@@ -663,7 +663,7 @@ BEGIN
 		INSERT INTO admin.archived_ban_requests ( request_id , credentials_id )
 			VALUES ( b_id , u_id );
 		DELETE FROM admin.active_ban_requests WHERE request_id = b_id;
-		PERFORM sys.write_log( 'Bans' , 'INFO'::log_level , 'Ban request #' || b_id
+		PERFORM sys.write_sql_log( 'Bans' , 'INFO'::log_level , 'Ban request #' || b_id
 			|| ' (user account #' || u_id || ') expired' );
 	END LOOP;
 END;
@@ -707,7 +707,7 @@ BEGIN
 	-- Delete empire and active account record
 	PERFORM emp.delete_empire( e_id );
 	DELETE FROM users.active_accounts WHERE credentials_id = a_id;
-	PERFORM sys.write_log( 'Bans' , 'INFO'::log_level , 'Deleted empire #' || e_id
+	PERFORM sys.write_sql_log( 'Bans' , 'INFO'::log_level , 'Deleted empire #' || e_id
 		|| ' (user account #' || a_id || ')' );
 
 	RETURN TRUE;
diff --git a/legacyworlds-server-data/db-structure/tests/pgtap.sql b/legacyworlds-server-data/db-structure/tests/pgtap.sql
new file mode 100644
index 0000000..5a14976
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/pgtap.sql
@@ -0,0 +1,7402 @@
+-- This file defines pgTAP, a collection of functions for TAP-based unit
+-- testing. It is distributed under the revised FreeBSD license. You can
+-- find the original here:
+--
+-- http://github.com/theory/pgtap/raw/master/sql/pgtap.sql.in
+--
+-- The home page for the pgTAP project is:
+--
+-- http://pgtap.org/
+
+CREATE OR REPLACE FUNCTION pg_version()
+RETURNS text AS 'SELECT current_setting(''server_version'')'
+LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION pg_version_num()
+RETURNS integer AS $$
+    SELECT s.a[1]::int * 10000
+           + COALESCE(substring(s.a[2] FROM '[[:digit:]]+')::int, 0) * 100
+           + COALESCE(substring(s.a[3] FROM '[[:digit:]]+')::int, 0)
+      FROM (
+          SELECT string_to_array(current_setting('server_version'), '.') AS a
+      ) AS s;
+$$ LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION os_name()
+RETURNS TEXT AS 'SELECT ''''::text;'
+LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION pgtap_version()
+RETURNS NUMERIC AS 'SELECT 0.90;'
+LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION plan( integer )
+RETURNS TEXT AS $$
+DECLARE
+    rcount INTEGER;
+BEGIN
+    BEGIN
+        EXECUTE '
+            CREATE TEMP SEQUENCE __tcache___id_seq;
+            CREATE TEMP TABLE __tcache__ (
+                id    INTEGER NOT NULL DEFAULT nextval(''__tcache___id_seq''),
+                label TEXT    NOT NULL,
+                value INTEGER NOT NULL,
+                note  TEXT    NOT NULL DEFAULT ''''
+            );
+            CREATE UNIQUE INDEX __tcache___key ON __tcache__(id);
+            GRANT ALL ON TABLE __tcache__ TO PUBLIC;
+            GRANT ALL ON TABLE __tcache___id_seq TO PUBLIC;
+
+            CREATE TEMP SEQUENCE __tresults___numb_seq;
+            CREATE TEMP TABLE __tresults__ (
+                numb   INTEGER NOT NULL DEFAULT nextval(''__tresults___numb_seq''),
+                ok     BOOLEAN NOT NULL DEFAULT TRUE,
+                aok    BOOLEAN NOT NULL DEFAULT TRUE,
+                descr  TEXT    NOT NULL DEFAULT '''',
+                type   TEXT    NOT NULL DEFAULT '''',
+                reason TEXT    NOT NULL DEFAULT ''''
+            );
+            CREATE UNIQUE INDEX __tresults___key ON __tresults__(numb);
+            GRANT ALL ON TABLE __tresults__ TO PUBLIC;
+            GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC;
+        ';
+
+    EXCEPTION WHEN duplicate_table THEN
+        -- Raise an exception if there's already a plan.
+        EXECUTE 'SELECT TRUE FROM __tcache__ WHERE label = ''plan''';
+      GET DIAGNOSTICS rcount = ROW_COUNT;
+        IF rcount > 0 THEN
+           RAISE EXCEPTION 'You tried to plan twice!';
+        END IF;
+    END;
+
+    -- Save the plan and return.
+    PERFORM _set('plan', $1 );
+    RETURN '1..' || $1;
+END;
+$$ LANGUAGE plpgsql strict;
+
+CREATE OR REPLACE FUNCTION no_plan()
+RETURNS SETOF boolean AS $$
+BEGIN
+    PERFORM plan(0);
+    RETURN;
+END;
+$$ LANGUAGE plpgsql strict;
+
+CREATE OR REPLACE FUNCTION _get ( text )
+RETURNS integer AS $$
+DECLARE
+    ret integer;
+BEGIN
+    EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret;
+    RETURN ret;
+END;
+$$ LANGUAGE plpgsql strict;
+
+CREATE OR REPLACE FUNCTION _get_latest ( text )
+RETURNS integer[] AS $$
+DECLARE
+    ret integer[];
+BEGIN
+    EXECUTE 'SELECT ARRAY[ id, value] FROM __tcache__ WHERE label = ' ||
+    quote_literal($1) || ' AND id = (SELECT MAX(id) FROM __tcache__ WHERE label = ' ||
+    quote_literal($1) || ') LIMIT 1' INTO ret;
+    RETURN ret;
+END;
+$$ LANGUAGE plpgsql strict;
+
+CREATE OR REPLACE FUNCTION _get_latest ( text, integer )
+RETURNS integer AS $$
+DECLARE
+    ret integer;
+BEGIN
+    EXECUTE 'SELECT MAX(id) FROM __tcache__ WHERE label = ' ||
+    quote_literal($1) || ' AND value = ' || $2 INTO ret;
+    RETURN ret;
+END;
+$$ LANGUAGE plpgsql strict;
+
+CREATE OR REPLACE FUNCTION _get_note ( text )
+RETURNS text AS $$
+DECLARE
+    ret text;
+BEGIN
+    EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret;
+    RETURN ret;
+END;
+$$ LANGUAGE plpgsql strict;
+
+CREATE OR REPLACE FUNCTION _get_note ( integer )
+RETURNS text AS $$
+DECLARE
+    ret text;
+BEGIN
+    EXECUTE 'SELECT note FROM __tcache__ WHERE id = ' || $1 || ' LIMIT 1' INTO ret;
+    RETURN ret;
+END;
+$$ LANGUAGE plpgsql strict;
+
+CREATE OR REPLACE FUNCTION _set ( text, integer, text )
+RETURNS integer AS $$
+DECLARE
+    rcount integer;
+BEGIN
+    EXECUTE 'UPDATE __tcache__ SET value = ' || $2
+        || CASE WHEN $3 IS NULL THEN '' ELSE ', note = ' || quote_literal($3) END
+        || ' WHERE label = ' || quote_literal($1);
+    GET DIAGNOSTICS rcount = ROW_COUNT;
+    IF rcount = 0 THEN
+       RETURN _add( $1, $2, $3 );
+    END IF;
+    RETURN $2;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _set ( text, integer )
+RETURNS integer AS $$
+    SELECT _set($1, $2, '')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _set ( integer, integer )
+RETURNS integer AS $$
+BEGIN
+    EXECUTE 'UPDATE __tcache__ SET value = ' || $2
+        || ' WHERE id = ' || $1;
+    RETURN $2;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _add ( text, integer, text )
+RETURNS integer AS $$
+BEGIN
+    EXECUTE 'INSERT INTO __tcache__ (label, value, note) values (' ||
+    quote_literal($1) || ', ' || $2 || ', ' || quote_literal(COALESCE($3, '')) || ')';
+    RETURN $2;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _add ( text, integer )
+RETURNS integer AS $$
+    SELECT _add($1, $2, '')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text )
+RETURNS integer AS $$
+BEGIN
+    EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason )
+    VALUES( ' || $1 || ', '
+              || $2 || ', '
+              || quote_literal(COALESCE($3, '')) || ', '
+              || quote_literal($4) || ', '
+              || quote_literal($5) || ' )';
+    RETURN currval('__tresults___numb_seq');
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION num_failed ()
+RETURNS INTEGER AS $$
+DECLARE
+    ret integer;
+BEGIN
+    EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret;
+    RETURN ret;
+END;
+$$ LANGUAGE plpgsql strict;
+
+CREATE OR REPLACE FUNCTION _finish ( INTEGER, INTEGER, INTEGER)
+RETURNS SETOF TEXT AS $$
+DECLARE
+    curr_test ALIAS FOR $1;
+    exp_tests INTEGER := $2;
+    num_faild ALIAS FOR $3;
+    plural    CHAR;
+BEGIN
+    plural    := CASE exp_tests WHEN 1 THEN '' ELSE 's' END;
+
+    IF curr_test IS NULL THEN
+        RAISE EXCEPTION '# No tests run!';
+    END IF;
+
+    IF exp_tests = 0 OR exp_tests IS NULL THEN
+         -- No plan. Output one now.
+        exp_tests = curr_test;
+        RETURN NEXT '1..' || exp_tests;
+    END IF;
+
+    IF curr_test <> exp_tests THEN
+        RETURN NEXT diag(
+            'Looks like you planned ' || exp_tests || ' test' ||
+            plural || ' but ran ' || curr_test
+        );
+    ELSIF num_faild > 0 THEN
+        RETURN NEXT diag(
+            'Looks like you failed ' || num_faild || ' test' ||
+            CASE num_faild WHEN 1 THEN '' ELSE 's' END
+            || ' of ' || exp_tests
+        );
+    ELSE
+
+    END IF;
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION finish ()
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM _finish(
+        _get('curr_test'),
+        _get('plan'),
+        num_failed()
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION diag ( msg text )
+RETURNS TEXT AS $$
+    SELECT '# ' || replace(
+       replace(
+            replace( $1, E'\r\n', E'\n# ' ),
+            E'\n',
+            E'\n# '
+        ),
+        E'\r',
+        E'\n# '
+    );
+$$ LANGUAGE sql strict;
+
+CREATE OR REPLACE FUNCTION diag ( msg anyelement )
+RETURNS TEXT AS $$
+    SELECT diag($1::text);
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION diag( VARIADIC text[] )
+RETURNS TEXT AS $$
+    SELECT diag(array_to_string($1, ''));
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION diag( VARIADIC anyarray )
+RETURNS TEXT AS $$
+    SELECT diag(array_to_string($1, ''));
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION ok ( boolean, text )
+RETURNS TEXT AS $$
+DECLARE
+   aok      ALIAS FOR $1;
+   descr    text := $2;
+   test_num INTEGER;
+   todo_why TEXT;
+   ok       BOOL;
+BEGIN
+   todo_why := _todo();
+   ok       := CASE
+       WHEN aok = TRUE THEN aok
+       WHEN todo_why IS NULL THEN COALESCE(aok, false)
+       ELSE TRUE
+    END;
+    IF _get('plan') IS NULL THEN
+        RAISE EXCEPTION 'You tried to run a test without a plan! Gotta have a plan';
+    END IF;
+
+    test_num := add_result(
+        ok,
+        COALESCE(aok, false),
+        descr,
+        CASE WHEN todo_why IS NULL THEN '' ELSE 'todo' END,
+        COALESCE(todo_why, '')
+    );
+
+    RETURN (CASE aok WHEN TRUE THEN '' ELSE 'not ' END)
+           || 'ok ' || _set( 'curr_test', test_num )
+           || CASE descr WHEN '' THEN '' ELSE COALESCE( ' - ' || substr(diag( descr ), 3), '' ) END
+           || COALESCE( ' ' || diag( 'TODO ' || todo_why ), '')
+           || CASE aok WHEN TRUE THEN '' ELSE E'\n' ||
+                diag('Failed ' ||
+                CASE WHEN todo_why IS NULL THEN '' ELSE '(TODO) ' END ||
+                'test ' || test_num ||
+                CASE descr WHEN '' THEN '' ELSE COALESCE(': "' || descr || '"', '') END ) ||
+                CASE WHEN aok IS NULL THEN E'\n' || diag('    (test result was NULL)') ELSE '' END
+           END;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION ok ( boolean )
+RETURNS TEXT AS $$
+    SELECT ok( $1, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION is (anyelement, anyelement, text)
+RETURNS TEXT AS $$
+DECLARE
+    result BOOLEAN;
+    output TEXT;
+BEGIN
+    -- Would prefer $1 IS NOT DISTINCT FROM, but that's not supported by 8.1.
+    result := NOT $1 IS DISTINCT FROM $2;
+    output := ok( result, $3 );
+    RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag(
+           '        have: ' || CASE WHEN $1 IS NULL THEN 'NULL' ELSE $1::text END ||
+        E'\n        want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE $2::text END
+    ) END;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION is (anyelement, anyelement)
+RETURNS TEXT AS $$
+    SELECT is( $1, $2, NULL);
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement, text)
+RETURNS TEXT AS $$
+DECLARE
+    result BOOLEAN;
+    output TEXT;
+BEGIN
+    result := $1 IS DISTINCT FROM $2;
+    output := ok( result, $3 );
+    RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag(
+           '        have: ' || COALESCE( $1::text, 'NULL' ) ||
+        E'\n        want: anything else'
+    ) END;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement)
+RETURNS TEXT AS $$
+    SELECT isnt( $1, $2, NULL);
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _alike ( BOOLEAN, ANYELEMENT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    result ALIAS FOR $1;
+    got    ALIAS FOR $2;
+    rx     ALIAS FOR $3;
+    descr  ALIAS FOR $4;
+    output TEXT;
+BEGIN
+    output := ok( result, descr );
+    RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag(
+           '                  ' || COALESCE( quote_literal(got), 'NULL' ) ||
+       E'\n   doesn''t match: ' || COALESCE( quote_literal(rx), 'NULL' )
+    ) END;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION matches ( anyelement, text, text )
+RETURNS TEXT AS $$
+    SELECT _alike( $1 ~ $2, $1, $2, $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION matches ( anyelement, text )
+RETURNS TEXT AS $$
+    SELECT _alike( $1 ~ $2, $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION imatches ( anyelement, text, text )
+RETURNS TEXT AS $$
+    SELECT _alike( $1 ~* $2, $1, $2, $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION imatches ( anyelement, text )
+RETURNS TEXT AS $$
+    SELECT _alike( $1 ~* $2, $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION alike ( anyelement, text, text )
+RETURNS TEXT AS $$
+    SELECT _alike( $1 ~~ $2, $1, $2, $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION alike ( anyelement, text )
+RETURNS TEXT AS $$
+    SELECT _alike( $1 ~~ $2, $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION ialike ( anyelement, text, text )
+RETURNS TEXT AS $$
+    SELECT _alike( $1 ~~* $2, $1, $2, $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION ialike ( anyelement, text )
+RETURNS TEXT AS $$
+    SELECT _alike( $1 ~~* $2, $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _unalike ( BOOLEAN, ANYELEMENT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    result ALIAS FOR $1;
+    got    ALIAS FOR $2;
+    rx     ALIAS FOR $3;
+    descr  ALIAS FOR $4;
+    output TEXT;
+BEGIN
+    output := ok( result, descr );
+    RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag(
+           '                  ' || COALESCE( quote_literal(got), 'NULL' ) ||
+        E'\n         matches: ' || COALESCE( quote_literal(rx), 'NULL' )
+    ) END;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION doesnt_match ( anyelement, text, text )
+RETURNS TEXT AS $$
+    SELECT _unalike( $1 !~ $2, $1, $2, $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION doesnt_match ( anyelement, text )
+RETURNS TEXT AS $$
+    SELECT _unalike( $1 !~ $2, $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION doesnt_imatch ( anyelement, text, text )
+RETURNS TEXT AS $$
+    SELECT _unalike( $1 !~* $2, $1, $2, $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION doesnt_imatch ( anyelement, text )
+RETURNS TEXT AS $$
+    SELECT _unalike( $1 !~* $2, $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION unalike ( anyelement, text, text )
+RETURNS TEXT AS $$
+    SELECT _unalike( $1 !~~ $2, $1, $2, $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION unalike ( anyelement, text )
+RETURNS TEXT AS $$
+    SELECT _unalike( $1 !~~ $2, $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION unialike ( anyelement, text, text )
+RETURNS TEXT AS $$
+    SELECT _unalike( $1 !~~* $2, $1, $2, $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION unialike ( anyelement, text )
+RETURNS TEXT AS $$
+    SELECT _unalike( $1 !~~* $2, $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement, text)
+RETURNS TEXT AS $$
+DECLARE
+    have   ALIAS FOR $1;
+    op     ALIAS FOR $2;
+    want   ALIAS FOR $3;
+    descr  ALIAS FOR $4;
+    result BOOLEAN;
+    output TEXT;
+BEGIN
+    EXECUTE 'SELECT ' ||
+            COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have) || ' '
+            || op || ' ' ||
+            COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want)
+       INTO result;
+    output := ok( COALESCE(result, FALSE), descr );
+    RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag(
+           '    ' || COALESCE( quote_literal(have), 'NULL' ) ||
+           E'\n        ' || op ||
+           E'\n    ' || COALESCE( quote_literal(want), 'NULL' )
+    ) END;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement)
+RETURNS TEXT AS $$
+    SELECT cmp_ok( $1, $2, $3, NULL );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION pass ( text )
+RETURNS TEXT AS $$
+    SELECT ok( TRUE, $1 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION pass ()
+RETURNS TEXT AS $$
+    SELECT ok( TRUE, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION fail ( text )
+RETURNS TEXT AS $$
+    SELECT ok( FALSE, $1 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION fail ()
+RETURNS TEXT AS $$
+    SELECT ok( FALSE, NULL );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION todo ( why text, how_many int )
+RETURNS SETOF BOOLEAN AS $$
+BEGIN
+    PERFORM _add('todo', COALESCE(how_many, 1), COALESCE(why, ''));
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION todo ( how_many int, why text )
+RETURNS SETOF BOOLEAN AS $$
+BEGIN
+    PERFORM _add('todo', COALESCE(how_many, 1), COALESCE(why, ''));
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION todo ( why text )
+RETURNS SETOF BOOLEAN AS $$
+BEGIN
+    PERFORM _add('todo', 1, COALESCE(why, ''));
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION todo ( how_many int )
+RETURNS SETOF BOOLEAN AS $$
+BEGIN
+    PERFORM _add('todo', COALESCE(how_many, 1), '');
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION todo_start (text)
+RETURNS SETOF BOOLEAN AS $$
+BEGIN
+    PERFORM _add('todo', -1, COALESCE($1, ''));
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION todo_start ()
+RETURNS SETOF BOOLEAN AS $$
+BEGIN
+    PERFORM _add('todo', -1, '');
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION in_todo ()
+RETURNS BOOLEAN AS $$
+DECLARE
+    todos integer;
+BEGIN
+    todos := _get('todo');
+    RETURN CASE WHEN todos IS NULL THEN FALSE ELSE TRUE END;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION todo_end ()
+RETURNS SETOF BOOLEAN AS $$
+DECLARE
+    id integer;
+BEGIN
+    id := _get_latest( 'todo', -1 );
+    IF id IS NULL THEN
+        RAISE EXCEPTION 'todo_end() called without todo_start()';
+    END IF;
+    EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || id;
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _todo()
+RETURNS TEXT AS $$
+DECLARE
+    todos INT[];
+    note text;
+BEGIN
+    -- Get the latest id and value, because todo() might have been called
+    -- again before the todos ran out for the first call to todo(). This
+    -- allows them to nest.
+    todos := _get_latest('todo');
+    IF todos IS NULL THEN
+        -- No todos.
+        RETURN NULL;
+    END IF;
+    IF todos[2] = 0 THEN
+        -- Todos depleted. Clean up.
+        EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || todos[1];
+        RETURN NULL;
+    END IF;
+    -- Decrement the count of counted todos and return the reason.
+    IF todos[2] <> -1 THEN
+        PERFORM _set(todos[1], todos[2] - 1);
+    END IF;
+    note := _get_note(todos[1]);
+
+    IF todos[2] = 1 THEN
+        -- This was the last todo, so delete the record.
+        EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || todos[1];
+    END IF;
+
+    RETURN note;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION skip ( why text, how_many int )
+RETURNS TEXT AS $$
+DECLARE
+    output TEXT[];
+BEGIN
+    output := '{}';
+    FOR i IN 1..how_many LOOP
+        output = array_append(output, ok( TRUE, 'SKIP: ' || COALESCE( why, '') ) );
+    END LOOP;
+    RETURN array_to_string(output, E'\n');
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION skip ( text )
+RETURNS TEXT AS $$
+    SELECT ok( TRUE, 'SKIP: ' || $1 );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION skip( int, text )
+RETURNS TEXT AS 'SELECT skip($2, $1)'
+LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION skip( int )
+RETURNS TEXT AS 'SELECT skip(NULL, $1)'
+LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _query( TEXT )
+RETURNS TEXT AS $$
+    SELECT CASE
+        WHEN $1 LIKE '"%' OR $1 !~ '[[:space:]]' THEN 'EXECUTE ' || $1
+        ELSE $1
+    END;
+$$ LANGUAGE SQL;
+
+-- throws_ok ( sql, errcode, errmsg, description )
+CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    query     TEXT := _query($1);
+    errcode   ALIAS FOR $2;
+    errmsg    ALIAS FOR $3;
+    desctext  ALIAS FOR $4;
+    descr     TEXT;
+BEGIN
+    descr := COALESCE(
+          desctext,
+          'threw ' || errcode || ': ' || errmsg,
+          'threw ' || errcode,
+          'threw ' || errmsg,
+          'threw an exception'
+    );
+    EXECUTE query;
+    RETURN ok( FALSE, descr ) || E'\n' || diag(
+           '      caught: no exception' ||
+        E'\n      wanted: ' || COALESCE( errcode, 'an exception' )
+    );
+EXCEPTION WHEN OTHERS THEN
+    IF (errcode IS NULL OR SQLSTATE = errcode)
+        AND ( errmsg IS NULL OR SQLERRM = errmsg)
+    THEN
+        -- The expected errcode and/or message was thrown.
+        RETURN ok( TRUE, descr );
+    ELSE
+        -- This was not the expected errcode or errmsg.
+        RETURN ok( FALSE, descr ) || E'\n' || diag(
+               '      caught: ' || SQLSTATE || ': ' || SQLERRM ||
+            E'\n      wanted: ' || COALESCE( errcode, 'an exception' ) ||
+            COALESCE( ': ' || errmsg, '')
+        );
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- throws_ok ( sql, errcode, errmsg )
+-- throws_ok ( sql, errmsg, description )
+CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    IF octet_length($2) = 5 THEN
+        RETURN throws_ok( $1, $2::char(5), $3, NULL );
+    ELSE
+        RETURN throws_ok( $1, NULL, $2, $3 );
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- throws_ok ( query, errcode )
+-- throws_ok ( query, errmsg )
+CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    IF octet_length($2) = 5 THEN
+        RETURN throws_ok( $1, $2::char(5), NULL, NULL );
+    ELSE
+        RETURN throws_ok( $1, NULL, $2, NULL );
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- throws_ok ( sql )
+CREATE OR REPLACE FUNCTION throws_ok ( TEXT )
+RETURNS TEXT AS $$
+    SELECT throws_ok( $1, NULL, NULL, NULL );
+$$ LANGUAGE SQL;
+
+-- Magically cast integer error codes.
+-- throws_ok ( sql, errcode, errmsg, description )
+CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT throws_ok( $1, $2::char(5), $3, $4 );
+$$ LANGUAGE SQL;
+
+-- throws_ok ( sql, errcode, errmsg )
+CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT )
+RETURNS TEXT AS $$
+    SELECT throws_ok( $1, $2::char(5), $3, NULL );
+$$ LANGUAGE SQL;
+
+-- throws_ok ( sql, errcode )
+CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 )
+RETURNS TEXT AS $$
+    SELECT throws_ok( $1, $2::char(5), NULL, NULL );
+$$ LANGUAGE SQL;
+
+-- lives_ok( sql, description )
+CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    code  TEXT := _query($1);
+    descr ALIAS FOR $2;
+BEGIN
+    EXECUTE code;
+    RETURN ok( TRUE, descr );
+EXCEPTION WHEN OTHERS THEN
+    -- There should have been no exception.
+    RETURN ok( FALSE, descr ) || E'\n' || diag(
+           '        died: ' || SQLSTATE || ': ' || SQLERRM
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- lives_ok( sql )
+CREATE OR REPLACE FUNCTION lives_ok ( TEXT )
+RETURNS TEXT AS $$
+    SELECT lives_ok( $1, NULL );
+$$ LANGUAGE SQL;
+
+-- performs_ok ( sql, milliseconds, description )
+CREATE OR REPLACE FUNCTION performs_ok ( TEXT, NUMERIC, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    query     TEXT := _query($1);
+    max_time  ALIAS FOR $2;
+    descr     ALIAS FOR $3;
+    starts_at TEXT;
+    act_time  NUMERIC;
+BEGIN
+    starts_at := timeofday();
+    EXECUTE query;
+    act_time := extract( millisecond from timeofday()::timestamptz - starts_at::timestamptz);
+    IF act_time < max_time THEN RETURN ok(TRUE, descr); END IF;
+    RETURN ok( FALSE, descr ) || E'\n' || diag(
+           '      runtime: ' || act_time || ' ms' ||
+        E'\n      exceeds: ' || max_time || ' ms'
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- performs_ok ( sql, milliseconds )
+CREATE OR REPLACE FUNCTION performs_ok ( TEXT, NUMERIC )
+RETURNS TEXT AS $$
+    SELECT performs_ok(
+        $1, $2, 'Should run in less than ' || $2 || ' ms'
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_namespace n
+          JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+         WHERE c.relkind = $1
+           AND n.nspname = $2
+           AND c.relname = $3
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_class c
+         WHERE c.relkind = $1
+           AND pg_catalog.pg_table_is_visible(c.oid)
+           AND c.relname = $2
+    );
+$$ LANGUAGE SQL;
+
+-- has_table( schema, table, description )
+CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _rexists( 'r', $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- has_table( table, description )
+CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _rexists( 'r', $1 ), $2 );
+$$ LANGUAGE SQL;
+
+-- has_table( table )
+CREATE OR REPLACE FUNCTION has_table ( NAME )
+RETURNS TEXT AS $$
+    SELECT has_table( $1, 'Table ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- hasnt_table( schema, table, description )
+CREATE OR REPLACE FUNCTION hasnt_table ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _rexists( 'r', $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_table( table, description )
+CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _rexists( 'r', $1 ), $2 );
+$$ LANGUAGE SQL;
+
+-- hasnt_table( table )
+CREATE OR REPLACE FUNCTION hasnt_table ( NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_table( $1, 'Table ' || quote_ident($1) || ' should not exist' );
+$$ LANGUAGE SQL;
+
+-- has_view( schema, view, description )
+CREATE OR REPLACE FUNCTION has_view ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _rexists( 'v', $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- has_view( view, description )
+CREATE OR REPLACE FUNCTION has_view ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _rexists( 'v', $1 ), $2 );
+$$ LANGUAGE SQL;
+
+-- has_view( view )
+CREATE OR REPLACE FUNCTION has_view ( NAME )
+RETURNS TEXT AS $$
+    SELECT has_view( $1, 'View ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- hasnt_view( schema, view, description )
+CREATE OR REPLACE FUNCTION hasnt_view ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _rexists( 'v', $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_view( view, description )
+CREATE OR REPLACE FUNCTION hasnt_view ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _rexists( 'v', $1 ), $2 );
+$$ LANGUAGE SQL;
+
+-- hasnt_view( view )
+CREATE OR REPLACE FUNCTION hasnt_view ( NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_view( $1, 'View ' || quote_ident($1) || ' should not exist' );
+$$ LANGUAGE SQL;
+
+-- has_sequence( schema, sequence, description )
+CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _rexists( 'S', $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- has_sequence( sequence, description )
+CREATE OR REPLACE FUNCTION has_sequence ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _rexists( 'S', $1 ), $2 );
+$$ LANGUAGE SQL;
+
+-- has_sequence( sequence )
+CREATE OR REPLACE FUNCTION has_sequence ( NAME )
+RETURNS TEXT AS $$
+    SELECT has_sequence( $1, 'Sequence ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- hasnt_sequence( schema, sequence, description )
+CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _rexists( 'S', $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_sequence( sequence, description )
+CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _rexists( 'S', $1 ), $2 );
+$$ LANGUAGE SQL;
+
+-- hasnt_sequence( sequence )
+CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_sequence( $1, 'Sequence ' || quote_ident($1) || ' should not exist' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_namespace n
+          JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+          JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+         WHERE n.nspname = $1
+           AND c.relname = $2
+           AND a.attnum > 0
+           AND NOT a.attisdropped
+           AND a.attname = $3
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_class c
+          JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+         WHERE c.relname = $1
+           AND pg_catalog.pg_table_is_visible(c.oid)
+           AND a.attnum > 0
+           AND NOT a.attisdropped
+           AND a.attname = $2
+    );
+$$ LANGUAGE SQL;
+
+-- has_column( schema, table, column, description )
+CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _cexists( $1, $2, $3 ), $4 );
+$$ LANGUAGE SQL;
+
+-- has_column( table, column, description )
+CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _cexists( $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- has_column( table, column )
+CREATE OR REPLACE FUNCTION has_column ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT has_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- hasnt_column( schema, table, column, description )
+CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _cexists( $1, $2, $3 ), $4 );
+$$ LANGUAGE SQL;
+
+-- hasnt_column( table, column, description )
+CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _cexists( $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_column( table, column )
+CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' );
+$$ LANGUAGE SQL;
+
+-- _col_is_null( schema, table, column, desc, null )
+CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool )
+RETURNS TEXT AS $$
+BEGIN
+    IF NOT _cexists( $1, $2, $3 ) THEN
+        RETURN fail( $4 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' );
+    END IF;
+    RETURN ok(
+        EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_namespace n
+              JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+              JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+             WHERE n.nspname = $1
+               AND c.relname = $2
+               AND a.attnum  > 0
+               AND NOT a.attisdropped
+               AND a.attname    = $3
+               AND a.attnotnull = $5
+        ), $4
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- _col_is_null( table, column, desc, null )
+CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool )
+RETURNS TEXT AS $$
+BEGIN
+    IF NOT _cexists( $1, $2 ) THEN
+        RETURN fail( $3 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' );
+    END IF;
+    RETURN ok(
+        EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_class c
+              JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+             WHERE pg_catalog.pg_table_is_visible(c.oid)
+               AND c.relname = $1
+               AND a.attnum > 0
+               AND NOT a.attisdropped
+               AND a.attname    = $2
+               AND a.attnotnull = $4
+        ), $3
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_not_null( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _col_is_null( $1, $2, $3, $4, true );
+$$ LANGUAGE SQL;
+
+-- col_not_null( table, column, description )
+CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _col_is_null( $1, $2, $3, true );
+$$ LANGUAGE SQL;
+
+-- col_not_null( table, column )
+CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true );
+$$ LANGUAGE SQL;
+
+-- col_is_null( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _col_is_null( $1, $2, $3, $4, false );
+$$ LANGUAGE SQL;
+
+-- col_is_null( schema, table, column )
+CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT _col_is_null( $1, $2, $3, false );
+$$ LANGUAGE SQL;
+
+-- col_is_null( table, column )
+CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER )
+RETURNS TEXT AS $$
+    SELECT COALESCE(substring(
+        pg_catalog.format_type($1, $2),
+        '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$'
+    ), '')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER )
+RETURNS TEXT AS $$
+    SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END
+        || display_type($2, $3)
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT display_type(a.atttypid, a.atttypmod)
+      FROM pg_catalog.pg_namespace n
+      JOIN pg_catalog.pg_class c     ON n.oid = c.relnamespace
+      JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+     WHERE n.nspname = $1
+       AND c.relname = $2
+       AND a.attname = $3
+       AND attnum    > 0
+       AND NOT a.attisdropped
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT display_type(a.atttypid, a.atttypmod)
+      FROM pg_catalog.pg_attribute a
+      JOIN pg_catalog.pg_class c ON  a.attrelid = c.oid
+     WHERE pg_table_is_visible(c.oid)
+       AND c.relname = $1
+       AND a.attname = $2
+       AND attnum    > 0
+       AND NOT a.attisdropped
+       AND pg_type_is_visible(a.atttypid)
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT display_type(tn.nspname, a.atttypid, a.atttypmod)
+      FROM pg_catalog.pg_namespace n
+      JOIN pg_catalog.pg_class c      ON n.oid = c.relnamespace
+      JOIN pg_catalog.pg_attribute a  ON c.oid = a.attrelid
+      JOIN pg_catalog.pg_type t       ON a.atttypid = t.oid
+      JOIN pg_catalog.pg_namespace tn ON t.typnamespace = tn.oid
+     WHERE n.nspname = $1
+       AND c.relname = $2
+       AND a.attname = $3
+       AND attnum    > 0
+       AND NOT a.attisdropped
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _quote_ident_like(TEXT, TEXT)
+RETURNS TEXT AS $$
+DECLARE
+    have    TEXT;
+    pcision TEXT;
+BEGIN
+    -- Just return it if rhs isn't quoted.
+    IF $2 !~ '"' THEN RETURN $1; END IF;
+
+    -- If it's quoted ident without precision, return it quoted.
+    IF $2 ~ '"$' THEN RETURN quote_ident($1); END IF;
+
+    pcision := substring($1 FROM '[(][^")]+[)]$');
+
+    -- Just quote it if thre is no precision.
+    if pcision IS NULL THEN RETURN quote_ident($1); END IF;
+
+    -- Quote the non-precision part and concatenate with precision.
+    RETURN quote_ident(substring($1 FOR char_length($1) - char_length(pcision)))
+        || pcision;
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_type_is( schema, table, column, schema, type, description )
+CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have_type TEXT := _get_col_ns_type($1, $2, $3);
+    want_type TEXT;
+BEGIN
+    IF have_type IS NULL THEN
+        RETURN fail( $6 ) || E'\n' || diag (
+            '   Column ' || COALESCE(quote_ident($1) || '.', '')
+            || quote_ident($2) || '.' || quote_ident($3) || ' does not exist'
+        );
+    END IF;
+
+    want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type);
+    IF have_type = want_type THEN
+        -- We're good to go.
+        RETURN ok( true, $6 );
+    END IF;
+
+    -- Wrong data type. tell 'em what we really got.
+    RETURN ok( false, $6 ) || E'\n' || diag(
+           '        have: ' || have_type ||
+        E'\n        want: ' || want_type
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_type_is( schema, table, column, schema, type )
+CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_type_is( $1, $2, $3, $4, $5, 'Column ' || quote_ident($1) || '.' || quote_ident($2)
+        || '.' || quote_ident($3) || ' should be type ' || quote_ident($4) || '.' || $5);
+$$ LANGUAGE SQL;
+
+-- col_type_is( schema, table, column, type, description )
+CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have_type TEXT;
+    want_type TEXT;
+BEGIN
+    -- Get the data type.
+    IF $1 IS NULL THEN
+        have_type := _get_col_type($2, $3);
+    ELSE
+        have_type := _get_col_type($1, $2, $3);
+    END IF;
+
+    IF have_type IS NULL THEN
+        RETURN fail( $5 ) || E'\n' || diag (
+            '   Column ' || COALESCE(quote_ident($1) || '.', '')
+            || quote_ident($2) || '.' || quote_ident($3) || ' does not exist'
+        );
+    END IF;
+
+    want_type := _quote_ident_like($4, have_type);
+    IF have_type = want_type THEN
+        -- We're good to go.
+        RETURN ok( true, $5 );
+    END IF;
+
+    -- Wrong data type. tell 'em what we really got.
+    RETURN ok( false, $5 ) || E'\n' || diag(
+           '        have: ' || have_type ||
+        E'\n        want: ' || want_type
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_type_is( schema, table, column, type )
+CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_type_is( $1, $2, $3, $4, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' should be type ' || $4 );
+$$ LANGUAGE SQL;
+
+-- col_type_is( table, column, type, description )
+CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_type_is( NULL, $1, $2, $3, $4 );
+$$ LANGUAGE SQL;
+
+-- col_type_is( table, column, type )
+CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_type_is( $1, $2, $3, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be type ' || $3 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _has_def ( NAME, NAME, NAME )
+RETURNS boolean AS $$
+    SELECT a.atthasdef
+      FROM pg_catalog.pg_namespace n
+      JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+      JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+     WHERE n.nspname = $1
+       AND c.relname = $2
+       AND a.attnum > 0
+       AND NOT a.attisdropped
+       AND a.attname = $3
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _has_def ( NAME, NAME )
+RETURNS boolean AS $$
+    SELECT a.atthasdef
+      FROM pg_catalog.pg_class c
+      JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+     WHERE c.relname = $1
+       AND a.attnum > 0
+       AND NOT a.attisdropped
+       AND a.attname = $2
+$$ LANGUAGE sql;
+
+-- col_has_default( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    IF NOT _cexists( $1, $2, $3 ) THEN
+        RETURN fail( $4 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' );
+    END IF;
+    RETURN ok( _has_def( $1, $2, $3 ), $4 );
+END
+$$ LANGUAGE plpgsql;
+
+-- col_has_default( table, column, description )
+CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    IF NOT _cexists( $1, $2 ) THEN
+        RETURN fail( $3 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' );
+    END IF;
+    RETURN ok( _has_def( $1, $2 ), $3 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_has_default( table, column )
+CREATE OR REPLACE FUNCTION col_has_default ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT col_has_default( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should have a default' );
+$$ LANGUAGE SQL;
+
+-- col_hasnt_default( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    IF NOT _cexists( $1, $2, $3 ) THEN
+        RETURN fail( $4 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' );
+    END IF;
+    RETURN ok( NOT _has_def( $1, $2, $3 ), $4 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_hasnt_default( table, column, description )
+CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    IF NOT _cexists( $1, $2 ) THEN
+        RETURN fail( $3 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' );
+    END IF;
+    RETURN ok( NOT _has_def( $1, $2 ), $3 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_hasnt_default( table, column )
+CREATE OR REPLACE FUNCTION col_hasnt_default ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT col_hasnt_default( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have a default' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    thing text;
+BEGIN
+    IF $1 ~ '^[^'']+[(]' THEN
+        -- It's a functional default.
+        RETURN is( $1, $3, $4 );
+    END IF;
+
+    EXECUTE 'SELECT is('
+             || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', '
+             || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', '
+             || COALESCE(quote_literal($4), 'NULL')
+    || ')' INTO thing;
+    RETURN thing;
+END;
+$$ LANGUAGE plpgsql;
+
+-- _cdi( schema, table, column, default, description )
+CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, NAME, anyelement, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    IF NOT _cexists( $1, $2, $3 ) THEN
+        RETURN fail( $5 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' );
+    END IF;
+
+    IF NOT _has_def( $1, $2, $3 ) THEN
+        RETURN fail( $5 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' has no default' );
+    END IF;
+
+    RETURN _def_is(
+        pg_catalog.pg_get_expr(d.adbin, d.adrelid),
+        display_type(a.atttypid, a.atttypmod),
+        $4, $5
+    )
+      FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a,
+           pg_catalog.pg_attrdef d
+     WHERE n.oid = c.relnamespace
+       AND c.oid = a.attrelid
+       AND a.atthasdef
+       AND a.attrelid = d.adrelid
+       AND a.attnum = d.adnum
+       AND n.nspname = $1
+       AND c.relname = $2
+       AND a.attnum > 0
+       AND NOT a.attisdropped
+       AND a.attname = $3;
+END;
+$$ LANGUAGE plpgsql;
+
+-- _cdi( table, column, default, description )
+CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, anyelement, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    IF NOT _cexists( $1, $2 ) THEN
+        RETURN fail( $4 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' );
+    END IF;
+
+    IF NOT _has_def( $1, $2 ) THEN
+        RETURN fail( $4 ) || E'\n'
+            || diag ('    Column ' || quote_ident($1) || '.' || quote_ident($2) || ' has no default' );
+    END IF;
+
+    RETURN _def_is(
+        pg_catalog.pg_get_expr(d.adbin, d.adrelid),
+        display_type(a.atttypid, a.atttypmod),
+        $3, $4
+    )
+      FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_attrdef d
+     WHERE c.oid = a.attrelid
+       AND pg_table_is_visible(c.oid)
+       AND a.atthasdef
+       AND a.attrelid = d.adrelid
+       AND a.attnum = d.adnum
+       AND c.relname = $1
+       AND a.attnum > 0
+       AND NOT a.attisdropped
+       AND a.attname = $2;
+END;
+$$ LANGUAGE plpgsql;
+
+-- _cdi( table, column, default )
+CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, anyelement )
+RETURNS TEXT AS $$
+    SELECT col_default_is(
+        $1, $2, $3,
+        'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should default to '
+        || COALESCE( quote_literal($3), 'NULL')
+    );
+$$ LANGUAGE sql;
+
+-- col_default_is( schema, table, column, default, description )
+CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT )
+RETURNS TEXT AS $$
+    SELECT _cdi( $1, $2, $3, $4, $5 );
+$$ LANGUAGE sql;
+
+-- col_default_is( schema, table, column, default, description )
+CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _cdi( $1, $2, $3, $4, $5 );
+$$ LANGUAGE sql;
+
+-- col_default_is( table, column, default, description )
+CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT )
+RETURNS TEXT AS $$
+    SELECT _cdi( $1, $2, $3, $4 );
+$$ LANGUAGE sql;
+
+-- col_default_is( table, column, default, description )
+CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _cdi( $1, $2, $3, $4 );
+$$ LANGUAGE sql;
+
+-- col_default_is( table, column, default )
+CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement )
+RETURNS TEXT AS $$
+    SELECT _cdi( $1, $2, $3 );
+$$ LANGUAGE sql;
+
+-- col_default_is( table, column, default::text )
+CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, text )
+RETURNS TEXT AS $$
+    SELECT _cdi( $1, $2, $3 );
+$$ LANGUAGE sql;
+
+-- _hasc( schema, table, constraint_type )
+CREATE OR REPLACE FUNCTION _hasc ( NAME, NAME, CHAR )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_namespace n
+              JOIN pg_catalog.pg_class c      ON c.relnamespace = n.oid
+              JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid
+             WHERE c.relhaspkey = true
+               AND n.nspname = $1
+               AND c.relname = $2
+               AND x.contype = $3
+    );
+$$ LANGUAGE sql;
+
+-- _hasc( table, constraint_type )
+CREATE OR REPLACE FUNCTION _hasc ( NAME, CHAR )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_class c
+              JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid
+             WHERE c.relhaspkey = true
+               AND pg_table_is_visible(c.oid)
+               AND c.relname = $1
+               AND x.contype = $2
+    );
+$$ LANGUAGE sql;
+
+-- has_pk( schema, table, description )
+CREATE OR REPLACE FUNCTION has_pk ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _hasc( $1, $2, 'p' ), $3 );
+$$ LANGUAGE sql;
+
+-- has_pk( table, description )
+CREATE OR REPLACE FUNCTION has_pk ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _hasc( $1, 'p' ), $2 );
+$$ LANGUAGE sql;
+
+-- has_pk( table )
+CREATE OR REPLACE FUNCTION has_pk ( NAME )
+RETURNS TEXT AS $$
+    SELECT has_pk( $1, 'Table ' || quote_ident($1) || ' should have a primary key' );
+$$ LANGUAGE sql;
+
+-- hasnt_pk( schema, table, description )
+CREATE OR REPLACE FUNCTION hasnt_pk ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _hasc( $1, $2, 'p' ), $3 );
+$$ LANGUAGE sql;
+
+-- hasnt_pk( table, description )
+CREATE OR REPLACE FUNCTION hasnt_pk ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _hasc( $1, 'p' ), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_pk( table )
+CREATE OR REPLACE FUNCTION hasnt_pk ( NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_pk( $1, 'Table ' || quote_ident($1) || ' should not have a primary key' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _ident_array_to_string( name[], text )
+RETURNS text AS $$
+    SELECT array_to_string(ARRAY(
+        SELECT quote_ident($1[i])
+          FROM generate_series(1, array_upper($1, 1)) s(i)
+         ORDER BY i
+    ), $2);
+$$ LANGUAGE SQL immutable;
+
+-- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/
+CREATE OR REPLACE FUNCTION _pg_sv_column_array( OID, SMALLINT[] )
+RETURNS NAME[] AS $$
+    SELECT ARRAY(
+        SELECT a.attname
+          FROM pg_catalog.pg_attribute a
+          JOIN generate_series(1, array_upper($2, 1)) s(i) ON a.attnum = $2[i]
+         WHERE attrelid = $1
+         ORDER BY i
+    )
+$$ LANGUAGE SQL stable;
+
+-- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/
+CREATE OR REPLACE FUNCTION _pg_sv_table_accessible( OID, OID )
+RETURNS BOOLEAN AS $$
+    SELECT CASE WHEN has_schema_privilege($1, 'USAGE') THEN (
+                  has_table_privilege($2, 'SELECT')
+               OR has_table_privilege($2, 'INSERT')
+               or has_table_privilege($2, 'UPDATE')
+               OR has_table_privilege($2, 'DELETE')
+               OR has_table_privilege($2, 'RULE')
+               OR has_table_privilege($2, 'REFERENCES')
+               OR has_table_privilege($2, 'TRIGGER')
+           ) ELSE FALSE
+    END;
+$$ LANGUAGE SQL immutable strict;
+
+-- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/
+CREATE OR REPLACE VIEW pg_all_foreign_keys
+AS
+  SELECT n1.nspname                                   AS fk_schema_name,
+         c1.relname                                   AS fk_table_name,
+         k1.conname                                   AS fk_constraint_name,
+         c1.oid                                       AS fk_table_oid,
+         _pg_sv_column_array(k1.conrelid,k1.conkey)   AS fk_columns,
+         n2.nspname                                   AS pk_schema_name,
+         c2.relname                                   AS pk_table_name,
+         k2.conname                                   AS pk_constraint_name,
+         c2.oid                                       AS pk_table_oid,
+         ci.relname                                   AS pk_index_name,
+         _pg_sv_column_array(k1.confrelid,k1.confkey) AS pk_columns,
+         CASE k1.confmatchtype WHEN 'f' THEN 'FULL'
+                               WHEN 'p' THEN 'PARTIAL'
+                               WHEN 'u' THEN 'NONE'
+                               else null
+         END AS match_type,
+         CASE k1.confdeltype WHEN 'a' THEN 'NO ACTION'
+                             WHEN 'c' THEN 'CASCADE'
+                             WHEN 'd' THEN 'SET DEFAULT'
+                             WHEN 'n' THEN 'SET NULL'
+                             WHEN 'r' THEN 'RESTRICT'
+                             else null
+         END AS on_delete,
+         CASE k1.confupdtype WHEN 'a' THEN 'NO ACTION'
+                             WHEN 'c' THEN 'CASCADE'
+                             WHEN 'd' THEN 'SET DEFAULT'
+                             WHEN 'n' THEN 'SET NULL'
+                             WHEN 'r' THEN 'RESTRICT'
+                             ELSE NULL
+         END AS on_update,
+         k1.condeferrable AS is_deferrable,
+         k1.condeferred   AS is_deferred
+    FROM pg_catalog.pg_constraint k1
+    JOIN pg_catalog.pg_namespace n1 ON (n1.oid = k1.connamespace)
+    JOIN pg_catalog.pg_class c1     ON (c1.oid = k1.conrelid)
+    JOIN pg_catalog.pg_class c2     ON (c2.oid = k1.confrelid)
+    JOIN pg_catalog.pg_namespace n2 ON (n2.oid = c2.relnamespace)
+    JOIN pg_catalog.pg_depend d     ON (
+                 d.classid = 'pg_constraint'::regclass
+             AND d.objid = k1.oid
+             AND d.objsubid = 0
+             AND d.deptype = 'n'
+             AND d.refclassid = 'pg_class'::regclass
+             AND d.refobjsubid=0
+         )
+    JOIN pg_catalog.pg_class ci ON (ci.oid = d.refobjid AND ci.relkind = 'i')
+    LEFT JOIN pg_depend d2      ON (
+                 d2.classid = 'pg_class'::regclass
+             AND d2.objid = ci.oid
+             AND d2.objsubid = 0
+             AND d2.deptype = 'i'
+             AND d2.refclassid = 'pg_constraint'::regclass
+             AND d2.refobjsubid = 0
+         )
+    LEFT JOIN pg_catalog.pg_constraint k2 ON (
+                 k2.oid = d2.refobjid
+             AND k2.contype IN ('p', 'u')
+         )
+   WHERE k1.conrelid != 0
+     AND k1.confrelid != 0
+     AND k1.contype = 'f'
+     AND _pg_sv_table_accessible(n1.oid, c1.oid);
+
+-- _keys( schema, table, constraint_type )
+CREATE OR REPLACE FUNCTION _keys ( NAME, NAME, CHAR )
+RETURNS SETOF NAME[] AS $$
+    SELECT _pg_sv_column_array(x.conrelid,x.conkey)
+      FROM pg_catalog.pg_namespace n
+      JOIN pg_catalog.pg_class c       ON n.oid = c.relnamespace
+      JOIN pg_catalog.pg_constraint x  ON c.oid = x.conrelid
+     WHERE n.nspname = $1
+       AND c.relname = $2
+       AND x.contype = $3
+$$ LANGUAGE sql;
+
+-- _keys( table, constraint_type )
+CREATE OR REPLACE FUNCTION _keys ( NAME, CHAR )
+RETURNS SETOF NAME[] AS $$
+    SELECT _pg_sv_column_array(x.conrelid,x.conkey)
+      FROM pg_catalog.pg_class c
+      JOIN pg_catalog.pg_constraint x  ON c.oid = x.conrelid
+       AND c.relname = $1
+       AND x.contype = $2
+$$ LANGUAGE sql;
+
+-- _ckeys( schema, table, constraint_type )
+CREATE OR REPLACE FUNCTION _ckeys ( NAME, NAME, CHAR )
+RETURNS NAME[] AS $$
+    SELECT * FROM _keys($1, $2, $3) LIMIT 1;
+$$ LANGUAGE sql;
+
+-- _ckeys( table, constraint_type )
+CREATE OR REPLACE FUNCTION _ckeys ( NAME, CHAR )
+RETURNS NAME[] AS $$
+    SELECT * FROM _keys($1, $2) LIMIT 1;
+$$ LANGUAGE sql;
+
+-- col_is_pk( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT is( _ckeys( $1, $2, 'p' ), $3, $4 );
+$$ LANGUAGE sql;
+
+-- col_is_pk( table, column, description )
+CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT is( _ckeys( $1, 'p' ), $2, $3 );
+$$ LANGUAGE sql;
+
+-- col_is_pk( table, column[] )
+CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT col_is_pk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should be a primary key' );
+$$ LANGUAGE sql;
+
+-- col_is_pk( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_is_pk( $1, $2, ARRAY[$3], $4 );
+$$ LANGUAGE sql;
+
+-- col_is_pk( table, column, description )
+CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_is_pk( $1, ARRAY[$2], $3 );
+$$ LANGUAGE sql;
+
+-- col_is_pk( table, column )
+CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT col_is_pk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should be a primary key' );
+$$ LANGUAGE sql;
+
+-- col_isnt_pk( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT isnt( _ckeys( $1, $2, 'p' ), $3, $4 );
+$$ LANGUAGE sql;
+
+-- col_isnt_pk( table, column, description )
+CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT isnt( _ckeys( $1, 'p' ), $2, $3 );
+$$ LANGUAGE sql;
+
+-- col_isnt_pk( table, column[] )
+CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT col_isnt_pk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should not be a primary key' );
+$$ LANGUAGE sql;
+
+-- col_isnt_pk( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_isnt_pk( $1, $2, ARRAY[$3], $4 );
+$$ LANGUAGE sql;
+
+-- col_isnt_pk( table, column, description )
+CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_isnt_pk( $1, ARRAY[$2], $3 );
+$$ LANGUAGE sql;
+
+-- col_isnt_pk( table, column )
+CREATE OR REPLACE FUNCTION col_isnt_pk ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT col_isnt_pk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should not be a primary key' );
+$$ LANGUAGE sql;
+
+-- has_fk( schema, table, description )
+CREATE OR REPLACE FUNCTION has_fk ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _hasc( $1, $2, 'f' ), $3 );
+$$ LANGUAGE sql;
+
+-- has_fk( table, description )
+CREATE OR REPLACE FUNCTION has_fk ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _hasc( $1, 'f' ), $2 );
+$$ LANGUAGE sql;
+
+-- has_fk( table )
+CREATE OR REPLACE FUNCTION has_fk ( NAME )
+RETURNS TEXT AS $$
+    SELECT has_fk( $1, 'Table ' || quote_ident($1) || ' should have a foreign key constraint' );
+$$ LANGUAGE sql;
+
+-- hasnt_fk( schema, table, description )
+CREATE OR REPLACE FUNCTION hasnt_fk ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _hasc( $1, $2, 'f' ), $3 );
+$$ LANGUAGE sql;
+
+-- hasnt_fk( table, description )
+CREATE OR REPLACE FUNCTION hasnt_fk ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _hasc( $1, 'f' ), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_fk( table )
+CREATE OR REPLACE FUNCTION hasnt_fk ( NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_fk( $1, 'Table ' || quote_ident($1) || ' should not have a foreign key constraint' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _fkexists ( NAME, NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT TRUE
+           FROM pg_all_foreign_keys
+          WHERE fk_schema_name    = $1
+            AND quote_ident(fk_table_name)     = quote_ident($2)
+            AND fk_columns = $3
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _fkexists ( NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT TRUE
+           FROM pg_all_foreign_keys
+          WHERE quote_ident(fk_table_name)     = quote_ident($1)
+            AND fk_columns = $2
+    );
+$$ LANGUAGE SQL;
+
+-- col_is_fk( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    names text[];
+BEGIN
+    IF _fkexists($1, $2, $3) THEN
+        RETURN pass( $4 );
+    END IF;
+
+    -- Try to show the columns.
+    SELECT ARRAY(
+        SELECT _ident_array_to_string(fk_columns, ', ')
+          FROM pg_all_foreign_keys
+         WHERE fk_schema_name = $1
+           AND fk_table_name  = $2
+         ORDER BY fk_columns
+    ) INTO names;
+
+    IF names[1] IS NOT NULL THEN
+        RETURN fail($4) || E'\n' || diag(
+            '    Table ' || quote_ident($1) || '.' || quote_ident($2) || E' has foreign key constraints on these columns:\n        '
+            ||  array_to_string( names, E'\n        ' )
+        );
+    END IF;
+
+    -- No FKs in this table.
+    RETURN fail($4) || E'\n' || diag(
+        '    Table ' || quote_ident($1) || '.' || quote_ident($2) || ' has no foreign key columns'
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_is_fk( table, column, description )
+CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    names text[];
+BEGIN
+    IF _fkexists($1, $2) THEN
+        RETURN pass( $3 );
+    END IF;
+
+    -- Try to show the columns.
+    SELECT ARRAY(
+        SELECT _ident_array_to_string(fk_columns, ', ')
+          FROM pg_all_foreign_keys
+         WHERE fk_table_name  = $1
+         ORDER BY fk_columns
+    ) INTO names;
+
+    IF NAMES[1] IS NOT NULL THEN
+        RETURN fail($3) || E'\n' || diag(
+            '    Table ' || quote_ident($1) || E' has foreign key constraints on these columns:\n        '
+            || array_to_string( names, E'\n        ' )
+        );
+    END IF;
+
+    -- No FKs in this table.
+    RETURN fail($3) || E'\n' || diag(
+        '    Table ' || quote_ident($1) || ' has no foreign key columns'
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_is_fk( table, column[] )
+CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT col_is_fk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should be a foreign key' );
+$$ LANGUAGE sql;
+
+-- col_is_fk( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_is_fk( $1, $2, ARRAY[$3], $4 );
+$$ LANGUAGE sql;
+
+-- col_is_fk( table, column, description )
+CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_is_fk( $1, ARRAY[$2], $3 );
+$$ LANGUAGE sql;
+
+-- col_is_fk( table, column )
+CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT col_is_fk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should be a foreign key' );
+$$ LANGUAGE sql;
+
+-- col_isnt_fk( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _fkexists( $1, $2, $3 ), $4 );
+$$ LANGUAGE SQL;
+
+-- col_isnt_fk( table, column, description )
+CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _fkexists( $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- col_isnt_fk( table, column[] )
+CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT col_isnt_fk( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should not be a foreign key' );
+$$ LANGUAGE sql;
+
+-- col_isnt_fk( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_isnt_fk( $1, $2, ARRAY[$3], $4 );
+$$ LANGUAGE sql;
+
+-- col_isnt_fk( table, column, description )
+CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_isnt_fk( $1, ARRAY[$2], $3 );
+$$ LANGUAGE sql;
+
+-- col_isnt_fk( table, column )
+CREATE OR REPLACE FUNCTION col_isnt_fk ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT col_isnt_fk( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should not be a foreign key' );
+$$ LANGUAGE sql;
+
+-- has_unique( schema, table, description )
+CREATE OR REPLACE FUNCTION has_unique ( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _hasc( $1, $2, 'u' ), $3 );
+$$ LANGUAGE sql;
+
+-- has_unique( table, description )
+CREATE OR REPLACE FUNCTION has_unique ( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _hasc( $1, 'u' ), $2 );
+$$ LANGUAGE sql;
+
+-- has_unique( table )
+CREATE OR REPLACE FUNCTION has_unique ( TEXT )
+RETURNS TEXT AS $$
+    SELECT has_unique( $1, 'Table ' || quote_ident($1) || ' should have a unique constraint' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    akey NAME[];
+    keys TEXT[] := '{}';
+    have TEXT;
+BEGIN
+    FOR akey IN SELECT * FROM _keys($1, $2, $3) LOOP
+        IF akey = $4 THEN RETURN pass($5); END IF;
+        keys = keys || akey::text;
+    END LOOP;
+    IF array_upper(keys, 0) = 1 THEN
+        have := 'No ' || $6 || ' constriants';
+    ELSE
+        have := array_to_string(keys, E'\n              ');
+    END IF;
+
+    RETURN fail($5) || E'\n' || diag(
+             '        have: ' || have
+       || E'\n        want: ' || CASE WHEN $4 IS NULL THEN 'NULL' ELSE $4::text END
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    akey NAME[];
+    keys TEXT[] := '{}';
+    have TEXT;
+BEGIN
+    FOR akey IN SELECT * FROM _keys($1, $2) LOOP
+        IF akey = $3 THEN RETURN pass($4); END IF;
+        keys = keys || akey::text;
+    END LOOP;
+    IF array_upper(keys, 0) = 1 THEN
+        have := 'No ' || $5 || ' constriants';
+    ELSE
+        have := array_to_string(keys, E'\n              ');
+    END IF;
+
+    RETURN fail($4) || E'\n' || diag(
+             '        have: ' || have
+       || E'\n        want: ' || CASE WHEN $3 IS NULL THEN 'NULL' ELSE $3::text END
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- col_is_unique( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _constraint( $1, $2, 'u', $3, $4, 'unique' );
+$$ LANGUAGE sql;
+
+-- col_is_unique( table, column, description )
+CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _constraint( $1, 'u', $2, $3, 'unique' );
+$$ LANGUAGE sql;
+
+-- col_is_unique( table, column[] )
+CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT col_is_unique( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should have a unique constraint' );
+$$ LANGUAGE sql;
+
+-- col_is_unique( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_is_unique( $1, $2, ARRAY[$3], $4 );
+$$ LANGUAGE sql;
+
+-- col_is_unique( table, column, description )
+CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_is_unique( $1, ARRAY[$2], $3 );
+$$ LANGUAGE sql;
+
+-- col_is_unique( table, column )
+CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT col_is_unique( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should have a unique constraint' );
+$$ LANGUAGE sql;
+
+-- has_check( schema, table, description )
+CREATE OR REPLACE FUNCTION has_check ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _hasc( $1, $2, 'c' ), $3 );
+$$ LANGUAGE sql;
+
+-- has_check( table, description )
+CREATE OR REPLACE FUNCTION has_check ( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _hasc( $1, 'c' ), $2 );
+$$ LANGUAGE sql;
+
+-- has_check( table )
+CREATE OR REPLACE FUNCTION has_check ( NAME )
+RETURNS TEXT AS $$
+    SELECT has_check( $1, 'Table ' || quote_ident($1) || ' should have a check constraint' );
+$$ LANGUAGE sql;
+
+-- col_has_check( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _constraint( $1, $2, 'c', $3, $4, 'check' );
+$$ LANGUAGE sql;
+
+-- col_has_check( table, column, description )
+CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _constraint( $1, 'c', $2, $3, 'check' );
+$$ LANGUAGE sql;
+
+-- col_has_check( table, column[] )
+CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT col_has_check( $1, $2, 'Columns ' || quote_ident($1) || '(' || _ident_array_to_string($2, ', ') || ') should have a check constraint' );
+$$ LANGUAGE sql;
+
+-- col_has_check( schema, table, column, description )
+CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_has_check( $1, $2, ARRAY[$3], $4 );
+$$ LANGUAGE sql;
+
+-- col_has_check( table, column, description )
+CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT col_has_check( $1, ARRAY[$2], $3 );
+$$ LANGUAGE sql;
+
+-- col_has_check( table, column )
+CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT col_has_check( $1, $2, 'Column ' || quote_ident($1) || '(' || quote_ident($2) || ') should have a check constraint' );
+$$ LANGUAGE sql;
+
+-- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description )
+CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    sch  name;
+    tab  name;
+    cols name[];
+BEGIN
+    SELECT pk_schema_name, pk_table_name, pk_columns
+      FROM pg_all_foreign_keys
+      WHERE fk_schema_name = $1
+        AND fk_table_name  = $2
+        AND fk_columns     = $3
+      INTO sch, tab, cols;
+
+    RETURN is(
+        -- have
+        quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' )
+        || ') REFERENCES ' || COALESCE ( sch || '.' || tab || '(' || _ident_array_to_string( cols, ', ' ) || ')', 'NOTHING' ),
+        -- want
+        quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' )
+        || ') REFERENCES ' ||
+        $4 || '.' || $5 || '(' || _ident_array_to_string( $6, ', ' ) || ')',
+        $7
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- fk_ok( fk_table, fk_column[], pk_table, pk_column[], description )
+CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    tab  name;
+    cols name[];
+BEGIN
+    SELECT pk_table_name, pk_columns
+      FROM pg_all_foreign_keys
+      WHERE fk_table_name = $1
+        AND fk_columns    = $2
+      INTO tab, cols;
+
+    RETURN is(
+        -- have
+        $1 || '(' || _ident_array_to_string( $2, ', ' )
+        || ') REFERENCES ' || COALESCE( tab || '(' || _ident_array_to_string( cols, ', ' ) || ')', 'NOTHING'),
+        -- want
+        $1 || '(' || _ident_array_to_string( $2, ', ' )
+        || ') REFERENCES ' ||
+        $3 || '(' || _ident_array_to_string( $4, ', ' ) || ')',
+        $5
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] )
+CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT fk_ok( $1, $2, $3, $4, $5, $6,
+        quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' )
+        || ') should reference ' ||
+        $4 || '.' || $5 || '(' || _ident_array_to_string( $6, ', ' ) || ')'
+    );
+$$ LANGUAGE sql;
+
+-- fk_ok( fk_table, fk_column[], pk_table, pk_column[] )
+CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT fk_ok( $1, $2, $3, $4,
+        $1 || '(' || _ident_array_to_string( $2, ', ' )
+        || ') should reference ' ||
+        $3 || '(' || _ident_array_to_string( $4, ', ' ) || ')'
+    );
+$$ LANGUAGE sql;
+
+-- fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description )
+CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT fk_ok( $1, $2, ARRAY[$3], $4, $5, ARRAY[$6], $7 );
+$$ LANGUAGE sql;
+
+-- fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column )
+CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT fk_ok( $1, $2, ARRAY[$3], $4, $5, ARRAY[$6] );
+$$ LANGUAGE sql;
+
+-- fk_ok( fk_table, fk_column, pk_table, pk_column, description )
+CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4], $5 );
+$$ LANGUAGE sql;
+
+-- fk_ok( fk_table, fk_column, pk_table, pk_column )
+CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE VIEW tap_funky
+ AS SELECT p.oid         AS oid,
+           n.nspname     AS schema,
+           p.proname     AS name,
+           array_to_string(p.proargtypes::regtype[], ',') AS args,
+           CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END
+             || p.prorettype::regtype AS returns,
+           p.prolang     AS langoid,
+           p.proisstrict AS is_strict,
+           p.proisagg    AS is_agg,
+           p.prosecdef   AS is_definer,
+           p.proretset   AS returns_set,
+           p.provolatile::char AS volatility,
+           pg_catalog.pg_function_is_visible(p.oid) AS is_visible
+      FROM pg_catalog.pg_proc p
+      JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid
+;
+
+CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT TRUE
+          FROM tap_funky
+         WHERE schema = $1
+           AND name   = $2
+           AND args   = array_to_string($3, ',')
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS( SELECT TRUE FROM tap_funky WHERE schema = $1 AND name = $2 );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT TRUE
+          FROM tap_funky
+         WHERE name = $1
+           AND args = array_to_string($2, ',')
+           AND is_visible
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _got_func ( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS( SELECT TRUE FROM tap_funky WHERE name = $1 AND is_visible);
+$$ LANGUAGE SQL;
+
+-- has_function( schema, function, args[], description )
+CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _got_func($1, $2, $3), $4 );
+$$ LANGUAGE SQL;
+
+-- has_function( schema, function, args[] )
+CREATE OR REPLACE FUNCTION has_function( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _got_func($1, $2, $3),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' ||
+        array_to_string($3, ', ') || ') should exist'
+    );
+$$ LANGUAGE sql;
+
+-- has_function( schema, function, description )
+CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _got_func($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- has_function( schema, function )
+CREATE OR REPLACE FUNCTION has_function( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _got_func($1, $2),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist'
+    );
+$$ LANGUAGE sql;
+
+-- has_function( function, args[], description )
+CREATE OR REPLACE FUNCTION has_function ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _got_func($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- has_function( function, args[] )
+CREATE OR REPLACE FUNCTION has_function( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _got_func($1, $2),
+        'Function ' || quote_ident($1) || '(' ||
+        array_to_string($2, ', ') || ') should exist'
+    );
+$$ LANGUAGE sql;
+
+-- has_function( function, description )
+CREATE OR REPLACE FUNCTION has_function( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _got_func($1), $2 );
+$$ LANGUAGE sql;
+
+-- has_function( function )
+CREATE OR REPLACE FUNCTION has_function( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _got_func($1), 'Function ' || quote_ident($1) || '() should exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_function( schema, function, args[], description )
+CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _got_func($1, $2, $3), $4 );
+$$ LANGUAGE SQL;
+
+-- hasnt_function( schema, function, args[] )
+CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT _got_func($1, $2, $3),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' ||
+        array_to_string($3, ', ') || ') should not exist'
+    );
+$$ LANGUAGE sql;
+
+-- hasnt_function( schema, function, description )
+CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _got_func($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_function( schema, function )
+CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT _got_func($1, $2),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not exist'
+    );
+$$ LANGUAGE sql;
+
+-- hasnt_function( function, args[], description )
+CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _got_func($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_function( function, args[] )
+CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT _got_func($1, $2),
+        'Function ' || quote_ident($1) || '(' ||
+        array_to_string($2, ', ') || ') should not exist'
+    );
+$$ LANGUAGE sql;
+
+-- hasnt_function( function, description )
+CREATE OR REPLACE FUNCTION hasnt_function( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _got_func($1), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_function( function )
+CREATE OR REPLACE FUNCTION hasnt_function( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _got_func($1), 'Function ' || quote_ident($1) || '() should not exist' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _pg_sv_type_array( OID[] )
+RETURNS NAME[] AS $$
+    SELECT ARRAY(
+        SELECT t.typname
+          FROM pg_catalog.pg_type t
+          JOIN generate_series(1, array_upper($1, 1)) s(i) ON t.oid = $1[i]
+         ORDER BY i
+    )
+$$ LANGUAGE SQL stable;
+
+-- can( schema, functions[], description )
+CREATE OR REPLACE FUNCTION can ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    missing text[];
+BEGIN
+    SELECT ARRAY(
+        SELECT quote_ident($2[i])
+          FROM generate_series(1, array_upper($2, 1)) s(i)
+          LEFT JOIN tap_funky ON name = $2[i] AND schema = $1
+         WHERE oid IS NULL
+         GROUP BY $2[i], s.i
+         ORDER BY MIN(s.i)
+    ) INTO missing;
+    IF missing[1] IS NULL THEN
+        RETURN ok( true, $3 );
+    END IF;
+    RETURN ok( false, $3 ) || E'\n' || diag(
+        '    ' || quote_ident($1) || '.' ||
+        array_to_string( missing, E'() missing\n    ' || quote_ident($1) || '.') ||
+        '() missing'
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- can( schema, functions[] )
+CREATE OR REPLACE FUNCTION can ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT can( $1, $2, 'Schema ' || quote_ident($1) || ' can' );
+$$ LANGUAGE sql;
+
+-- can( functions[], description )
+CREATE OR REPLACE FUNCTION can ( NAME[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    missing text[];
+BEGIN
+    SELECT ARRAY(
+        SELECT quote_ident($1[i])
+          FROM generate_series(1, array_upper($1, 1)) s(i)
+          LEFT JOIN pg_catalog.pg_proc p
+            ON $1[i] = p.proname
+           AND pg_catalog.pg_function_is_visible(p.oid)
+         WHERE p.oid IS NULL
+         ORDER BY s.i
+    ) INTO missing;
+    IF missing[1] IS NULL THEN
+        RETURN ok( true, $2 );
+    END IF;
+    RETURN ok( false, $2 ) || E'\n' || diag(
+        '    ' ||
+        array_to_string( missing, E'() missing\n    ') ||
+        '() missing'
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- can( functions[] )
+CREATE OR REPLACE FUNCTION can ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT can( $1, 'Schema ' || _ident_array_to_string(current_schemas(true), ' or ') || ' can' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME)
+RETURNS NAME[] AS $$
+    SELECT ARRAY(
+        SELECT a.attname
+          FROM pg_catalog.pg_index x
+          JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+          JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+          JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+          JOIN pg_catalog.pg_attribute a ON ct.oid = a.attrelid
+          JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i)
+            ON a.attnum = x.indkey[s.i]
+         WHERE ct.relname = $2
+           AND ci.relname = $3
+           AND n.nspname  = $1
+         ORDER BY s.i
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME)
+RETURNS NAME[] AS $$
+    SELECT ARRAY(
+        SELECT a.attname
+          FROM pg_catalog.pg_index x
+          JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+          JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+          JOIN pg_catalog.pg_attribute a ON ct.oid = a.attrelid
+          JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i)
+            ON a.attnum = x.indkey[s.i]
+         WHERE ct.relname = $1
+           AND ci.relname = $2
+           AND pg_catalog.pg_table_is_visible(ct.oid)
+         ORDER BY s.i
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _have_index( NAME, NAME, NAME)
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+    SELECT TRUE
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+     WHERE ct.relname = $2
+       AND ci.relname = $3
+       AND n.nspname  = $1
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _have_index( NAME, NAME)
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+    SELECT TRUE
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+     WHERE ct.relname = $1
+       AND ci.relname = $2
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME)
+RETURNS TEXT AS $$
+    SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid )
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+     WHERE ct.relname = $2
+       AND ci.relname = $3
+       AND n.nspname  = $1
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME)
+RETURNS TEXT AS $$
+    SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid )
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+     WHERE ct.relname = $1
+       AND ci.relname = $2
+       AND pg_catalog.pg_table_is_visible(ct.oid)
+$$ LANGUAGE sql;
+
+-- has_index( schema, table, index, columns[], description )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME[], text )
+RETURNS TEXT AS $$
+DECLARE
+     index_cols name[];
+BEGIN
+    index_cols := _ikeys($1, $2, $3 );
+
+    IF index_cols IS NULL OR index_cols = '{}'::name[] THEN
+        RETURN ok( false, $5 ) || E'\n'
+            || diag( 'Index ' || quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || ' not found');
+    END IF;
+
+    RETURN is(
+        quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')',
+        quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $4, ', ' ) || ')',
+        $5
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- has_index( schema, table, index, columns[] )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+   SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- has_index( schema, table, index, column/expression, description )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text )
+RETURNS TEXT AS $$
+DECLARE
+    expr text;
+BEGIN
+    IF $4 NOT LIKE '%(%' THEN
+        -- Not a functional index.
+        RETURN has_index( $1, $2, $3, ARRAY[$4], $5 );
+    END IF;
+
+    -- Get the functional expression.
+    expr := _iexpr($1, $2, $3);
+
+    IF expr IS NULL THEN
+        RETURN ok( false, $5 ) || E'\n'
+            || diag( 'Index ' || quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || ' not found');
+    END IF;
+
+    RETURN is(
+        quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || expr || ')',
+        quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')',
+        $5
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- has_index( schema, table, index, columns/expression )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+   SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- has_index( table, index, columns[], description )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME[], text )
+RETURNS TEXT AS $$
+DECLARE
+     index_cols name[];
+BEGIN
+    index_cols := _ikeys($1, $2 );
+
+    IF index_cols IS NULL OR index_cols = '{}'::name[] THEN
+        RETURN ok( false, $4 ) || E'\n'
+            || diag( 'Index ' || quote_ident($2) || ' ON ' || quote_ident($1) || ' not found');
+    END IF;
+
+    RETURN is(
+        quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')',
+        quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')',
+        $4
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- has_index( table, index, columns[], description )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+   SELECT has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- _is_schema( schema )
+CREATE OR REPLACE FUNCTION _is_schema( NAME )
+returns boolean AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_namespace
+          WHERE nspname = $1
+    );
+$$ LANGUAGE sql;
+
+-- has_index( table, index, column/expression, description )
+-- has_index( schema, table, index, column/expression )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, text )
+RETURNS TEXT AS $$
+DECLARE
+    want_expr text;
+    descr text;
+    have_expr text;
+    idx name;
+    tab text;
+BEGIN
+    IF $3 NOT LIKE '%(%' THEN
+        -- Not a functional index.
+        IF _is_schema( $1 ) THEN
+            -- Looking for schema.table index.
+            RETURN ok ( _have_index( $1, $2, $3 ), $4);
+        END IF;
+        -- Looking for particular columns.
+        RETURN has_index( $1, $2, ARRAY[$3], $4 );
+    END IF;
+
+    -- Get the functional expression.
+    IF _is_schema( $1 ) THEN
+        -- Looking for an index within a schema.
+        have_expr := _iexpr($1, $2, $3);
+        want_expr := $4;
+        descr     := 'Index ' || quote_ident($3) || ' should exist';
+        idx       := $3;
+        tab       := quote_ident($1) || '.' || quote_ident($2);
+    ELSE
+        -- Looking for an index without a schema spec.
+        have_expr := _iexpr($1, $2);
+        want_expr := $3;
+        descr     := $4;
+        idx       := $2;
+        tab       := quote_ident($1);
+    END IF;
+
+    IF have_expr IS NULL THEN
+        RETURN ok( false, descr ) || E'\n'
+            || diag( 'Index ' || idx || ' ON ' || tab || ' not found');
+    END IF;
+
+    RETURN is(
+        quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')',
+        quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')',
+        descr
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- has_index( table, index, column/expression )
+-- has_index( schema, table, index )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+BEGIN
+   IF _is_schema($1) THEN
+       -- ( schema, table, index )
+       RETURN ok( _have_index( $1, $2, $3 ), 'Index ' || quote_ident($3) || ' should exist' );
+   ELSE
+       -- ( table, index, column/expression )
+       RETURN has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' );
+   END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- has_index( table, index, description )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, text )
+RETURNS TEXT AS $$
+    SELECT CASE WHEN $3 LIKE '%(%'
+           THEN has_index( $1, $2, $3::name )
+           ELSE ok( _have_index( $1, $2 ), $3 )
+           END;
+$$ LANGUAGE sql;
+
+-- has_index( table, index )
+CREATE OR REPLACE FUNCTION has_index ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _have_index( $1, $2 ), 'Index ' || quote_ident($2) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_index( schema, table, index, description )
+CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    RETURN ok( NOT _have_index( $1, $2, $3 ), $4 );
+END;
+$$ LANGUAGE plpgSQL;
+
+-- hasnt_index( schema, table, index )
+CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT _have_index( $1, $2, $3 ),
+        'Index ' || quote_ident($3) || ' should not exist'
+    );
+$$ LANGUAGE SQL;
+
+-- hasnt_index( table, index, description )
+CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _have_index( $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_index( table, index )
+CREATE OR REPLACE FUNCTION hasnt_index ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT _have_index( $1, $2 ),
+        'Index ' || quote_ident($2) || ' should not exist'
+    );
+$$ LANGUAGE SQL;
+
+-- index_is_unique( schema, table, index, description )
+CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME, text )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisunique
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+     WHERE ct.relname = $2
+       AND ci.relname = $3
+       AND n.nspname  = $1
+      INTO res;
+
+      RETURN ok( COALESCE(res, false), $4 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- index_is_unique( schema, table, index )
+CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT index_is_unique(
+        $1, $2, $3,
+        'Index ' || quote_ident($3) || ' should be unique'
+    );
+$$ LANGUAGE sql;
+
+-- index_is_unique( table, index )
+CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisunique
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+     WHERE ct.relname = $1
+       AND ci.relname = $2
+       AND pg_catalog.pg_table_is_visible(ct.oid)
+      INTO res;
+
+      RETURN ok(
+          COALESCE(res, false),
+          'Index ' || quote_ident($2) || ' should be unique'
+      );
+END;
+$$ LANGUAGE plpgsql;
+
+-- index_is_unique( index )
+CREATE OR REPLACE FUNCTION index_is_unique ( NAME )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisunique
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+     WHERE ci.relname = $1
+       AND pg_catalog.pg_table_is_visible(ct.oid)
+      INTO res;
+
+      RETURN ok(
+          COALESCE(res, false),
+          'Index ' || quote_ident($1) || ' should be unique'
+      );
+END;
+$$ LANGUAGE plpgsql;
+
+-- index_is_primary( schema, table, index, description )
+CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME, NAME, text )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisprimary
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+     WHERE ct.relname = $2
+       AND ci.relname = $3
+       AND n.nspname  = $1
+      INTO res;
+
+      RETURN ok( COALESCE(res, false), $4 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- index_is_primary( schema, table, index )
+CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT index_is_primary(
+        $1, $2, $3,
+        'Index ' || quote_ident($3) || ' should be on a primary key'
+    );
+$$ LANGUAGE sql;
+
+-- index_is_primary( table, index )
+CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisprimary
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+     WHERE ct.relname = $1
+       AND ci.relname = $2
+       AND pg_catalog.pg_table_is_visible(ct.oid)
+     INTO res;
+
+      RETURN ok(
+          COALESCE(res, false),
+          'Index ' || quote_ident($2) || ' should be on a primary key'
+      );
+END;
+$$ LANGUAGE plpgsql;
+
+-- index_is_primary( index )
+CREATE OR REPLACE FUNCTION index_is_primary ( NAME )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisprimary
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+     WHERE ci.relname = $1
+       AND pg_catalog.pg_table_is_visible(ct.oid)
+      INTO res;
+
+      RETURN ok(
+          COALESCE(res, false),
+          'Index ' || quote_ident($1) || ' should be on a primary key'
+      );
+END;
+$$ LANGUAGE plpgsql;
+
+-- is_clustered( schema, table, index, description )
+CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME, NAME, text )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisclustered
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+     WHERE ct.relname = $2
+       AND ci.relname = $3
+       AND n.nspname  = $1
+      INTO res;
+
+      RETURN ok( COALESCE(res, false), $4 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- is_clustered( schema, table, index )
+CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT is_clustered(
+        $1, $2, $3,
+        'Table ' || quote_ident($1) || '.' || quote_ident($2) ||
+        ' should be clustered on index ' || quote_ident($3)
+    );
+$$ LANGUAGE sql;
+
+-- is_clustered( table, index )
+CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisclustered
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+     WHERE ct.relname = $1
+       AND ci.relname = $2
+      INTO res;
+
+      RETURN ok(
+          COALESCE(res, false),
+          'Table ' || quote_ident($1) || ' should be clustered on index ' || quote_ident($2)
+      );
+END;
+$$ LANGUAGE plpgsql;
+
+-- is_clustered( index )
+CREATE OR REPLACE FUNCTION is_clustered ( NAME )
+RETURNS TEXT AS $$
+DECLARE
+    res boolean;
+BEGIN
+    SELECT x.indisclustered
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+     WHERE ci.relname = $1
+      INTO res;
+
+      RETURN ok(
+          COALESCE(res, false),
+          'Table should be clustered on index ' || quote_ident($1)
+      );
+END;
+$$ LANGUAGE plpgsql;
+
+-- index_is_type( schema, table, index, type, description )
+CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME, NAME, text )
+RETURNS TEXT AS $$
+DECLARE
+    aname name;
+BEGIN
+    SELECT am.amname
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+      JOIN pg_catalog.pg_am am       ON ci.relam = am.oid
+     WHERE ct.relname = $2
+       AND ci.relname = $3
+       AND n.nspname  = $1
+      INTO aname;
+
+      return is( aname, $4, $5 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- index_is_type( schema, table, index, type )
+CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT index_is_type(
+        $1, $2, $3, $4,
+        'Index ' || quote_ident($3) || ' should be a ' || quote_ident($4) || ' index'
+    );
+$$ LANGUAGE SQL;
+
+-- index_is_type( table, index, type )
+CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+DECLARE
+    aname name;
+BEGIN
+    SELECT am.amname
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_am am    ON ci.relam = am.oid
+     WHERE ct.relname = $1
+       AND ci.relname = $2
+      INTO aname;
+
+      return is(
+          aname, $3,
+          'Index ' || quote_ident($2) || ' should be a ' || quote_ident($3) || ' index'
+      );
+END;
+$$ LANGUAGE plpgsql;
+
+-- index_is_type( index, type )
+CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME )
+RETURNS TEXT AS $$
+DECLARE
+    aname name;
+BEGIN
+    SELECT am.amname
+      FROM pg_catalog.pg_index x
+      JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+      JOIN pg_catalog.pg_am am    ON ci.relam = am.oid
+     WHERE ci.relname = $1
+      INTO aname;
+
+      return is(
+          aname, $2,
+          'Index ' || quote_ident($1) || ' should be a ' || quote_ident($2) || ' index'
+      );
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _trig ( NAME, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_trigger t
+          JOIN pg_catalog.pg_class c     ON c.oid = t.tgrelid
+          JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+         WHERE n.nspname = $1
+           AND c.relname = $2
+           AND t.tgname  = $3
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _trig ( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_trigger t
+          JOIN pg_catalog.pg_class c     ON c.oid = t.tgrelid
+         WHERE c.relname = $1
+           AND t.tgname  = $2
+    );
+$$ LANGUAGE SQL;
+
+-- has_trigger( schema, table, trigger, description )
+CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _trig($1, $2, $3), $4);
+$$ LANGUAGE SQL;
+
+-- has_trigger( schema, table, trigger )
+CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT has_trigger(
+        $1, $2, $3,
+        'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have trigger ' || quote_ident($3)
+    );
+$$ LANGUAGE sql;
+
+-- has_trigger( table, trigger, description )
+CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _trig($1, $2), $3);
+$$ LANGUAGE sql;
+
+-- has_trigger( table, trigger )
+CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _trig($1, $2), 'Table ' || quote_ident($1) || ' should have trigger ' || quote_ident($2));
+$$ LANGUAGE SQL;
+
+-- hasnt_trigger( schema, table, trigger, description )
+CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _trig($1, $2, $3), $4);
+$$ LANGUAGE SQL;
+
+-- hasnt_trigger( schema, table, trigger )
+CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT _trig($1, $2, $3),
+        'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have trigger ' || quote_ident($3)
+    );
+$$ LANGUAGE sql;
+
+-- hasnt_trigger( table, trigger, description )
+CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _trig($1, $2), $3);
+$$ LANGUAGE sql;
+
+-- hasnt_trigger( table, trigger )
+CREATE OR REPLACE FUNCTION hasnt_trigger ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _trig($1, $2), 'Table ' || quote_ident($1) || ' should not have trigger ' || quote_ident($2));
+$$ LANGUAGE SQL;
+
+-- trigger_is( schema, table, trigger, schema, function, description )
+CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text )
+RETURNS TEXT AS $$
+DECLARE
+    pname text;
+BEGIN
+    SELECT quote_ident(ni.nspname) || '.' || quote_ident(p.proname)
+      FROM pg_catalog.pg_trigger t
+      JOIN pg_catalog.pg_class ct     ON ct.oid = t.tgrelid
+      JOIN pg_catalog.pg_namespace nt ON nt.oid = ct.relnamespace
+      JOIN pg_catalog.pg_proc p       ON p.oid = t.tgfoid
+      JOIN pg_catalog.pg_namespace ni ON ni.oid = p.pronamespace
+     WHERE nt.nspname = $1
+       AND ct.relname = $2
+       AND t.tgname   = $3
+      INTO pname;
+
+    RETURN is( pname, quote_ident($4) || '.' || quote_ident($5), $6 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- trigger_is( schema, table, trigger, schema, function )
+CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT trigger_is(
+        $1, $2, $3, $4, $5,
+        'Trigger ' || quote_ident($3) || ' should call ' || quote_ident($4) || '.' || quote_ident($5) || '()'
+    );
+$$ LANGUAGE sql;
+
+-- trigger_is( table, trigger, function, description )
+CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, text )
+RETURNS TEXT AS $$
+DECLARE
+    pname text;
+BEGIN
+    SELECT p.proname
+      FROM pg_catalog.pg_trigger t
+      JOIN pg_catalog.pg_class ct ON ct.oid = t.tgrelid
+      JOIN pg_catalog.pg_proc p   ON p.oid = t.tgfoid
+     WHERE ct.relname = $1
+       AND t.tgname   = $2
+       AND pg_catalog.pg_table_is_visible(ct.oid)
+      INTO pname;
+
+    RETURN is( pname, $3::text, $4 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- trigger_is( table, trigger, function )
+CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT trigger_is(
+        $1, $2, $3,
+        'Trigger ' || quote_ident($2) || ' should call ' || quote_ident($3) || '()'
+    );
+$$ LANGUAGE sql;
+
+-- has_schema( schema, description )
+CREATE OR REPLACE FUNCTION has_schema( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok(
+        EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_namespace
+             WHERE nspname = $1
+        ), $2
+    );
+$$ LANGUAGE sql;
+
+-- has_schema( schema )
+CREATE OR REPLACE FUNCTION has_schema( NAME )
+RETURNS TEXT AS $$
+    SELECT has_schema( $1, 'Schema ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_schema( schema, description )
+CREATE OR REPLACE FUNCTION hasnt_schema( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_namespace
+             WHERE nspname = $1
+        ), $2
+    );
+$$ LANGUAGE sql;
+
+-- hasnt_schema( schema )
+CREATE OR REPLACE FUNCTION hasnt_schema( NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_schema( $1, 'Schema ' || quote_ident($1) || ' should not exist' );
+$$ LANGUAGE sql;
+
+-- has_tablespace( tablespace, location, description )
+CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok(
+        EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_tablespace
+             WHERE spcname = $1
+               AND spclocation = $2
+        ), $3
+    );
+$$ LANGUAGE sql;
+
+-- has_tablespace( tablespace, description )
+CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok(
+        EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_tablespace
+             WHERE spcname = $1
+        ), $2
+    );
+$$ LANGUAGE sql;
+
+-- has_tablespace( tablespace )
+CREATE OR REPLACE FUNCTION has_tablespace( NAME )
+RETURNS TEXT AS $$
+    SELECT has_tablespace( $1, 'Tablespace ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_tablespace( tablespace, description )
+CREATE OR REPLACE FUNCTION hasnt_tablespace( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT EXISTS(
+            SELECT true
+              FROM pg_catalog.pg_tablespace
+             WHERE spcname = $1
+        ), $2
+    );
+$$ LANGUAGE sql;
+
+-- hasnt_tablespace( tablespace )
+CREATE OR REPLACE FUNCTION hasnt_tablespace( NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_tablespace( $1, 'Tablespace ' || quote_ident($1) || ' should not exist' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _has_type( NAME, NAME, CHAR[] )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_type t
+          JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
+         WHERE t.typisdefined
+           AND n.nspname = $1
+           AND t.typname = $2
+           AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) )
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _has_type( NAME, CHAR[] )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_type t
+         WHERE t.typisdefined
+           AND pg_catalog.pg_type_is_visible(t.oid)
+           AND t.typname = $1
+           AND t.typtype = ANY( COALESCE($2, ARRAY['b', 'c', 'd', 'p', 'e']) )
+    );
+$$ LANGUAGE sql;
+
+-- has_type( schema, type, description )
+CREATE OR REPLACE FUNCTION has_type( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, $2, NULL ), $3 );
+$$ LANGUAGE sql;
+
+-- has_type( schema, type )
+CREATE OR REPLACE FUNCTION has_type( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT has_type( $1, $2, 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- has_type( type, description )
+CREATE OR REPLACE FUNCTION has_type( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, NULL ), $2 );
+$$ LANGUAGE sql;
+
+-- has_type( type )
+CREATE OR REPLACE FUNCTION has_type( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, NULL ), ('Type ' || quote_ident($1) || ' should exist')::text );
+$$ LANGUAGE sql;
+
+-- hasnt_type( schema, type, description )
+CREATE OR REPLACE FUNCTION hasnt_type( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, $2, NULL ), $3 );
+$$ LANGUAGE sql;
+
+-- hasnt_type( schema, type )
+CREATE OR REPLACE FUNCTION hasnt_type( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_type( $1, $2, 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_type( type, description )
+CREATE OR REPLACE FUNCTION hasnt_type( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, NULL ), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_type( type )
+CREATE OR REPLACE FUNCTION hasnt_type( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, NULL ), ('Type ' || quote_ident($1) || ' should not exist')::text );
+$$ LANGUAGE sql;
+
+-- has_domain( schema, domain, description )
+CREATE OR REPLACE FUNCTION has_domain( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, $2, ARRAY['d'] ), $3 );
+$$ LANGUAGE sql;
+
+-- has_domain( schema, domain )
+CREATE OR REPLACE FUNCTION has_domain( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT has_domain( $1, $2, 'Domain ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- has_domain( domain, description )
+CREATE OR REPLACE FUNCTION has_domain( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, ARRAY['d'] ), $2 );
+$$ LANGUAGE sql;
+
+-- has_domain( domain )
+CREATE OR REPLACE FUNCTION has_domain( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, ARRAY['d'] ), ('Domain ' || quote_ident($1) || ' should exist')::text );
+$$ LANGUAGE sql;
+
+-- hasnt_domain( schema, domain, description )
+CREATE OR REPLACE FUNCTION hasnt_domain( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, $2, ARRAY['d'] ), $3 );
+$$ LANGUAGE sql;
+
+-- hasnt_domain( schema, domain )
+CREATE OR REPLACE FUNCTION hasnt_domain( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_domain( $1, $2, 'Domain ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_domain( domain, description )
+CREATE OR REPLACE FUNCTION hasnt_domain( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, ARRAY['d'] ), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_domain( domain )
+CREATE OR REPLACE FUNCTION hasnt_domain( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, ARRAY['d'] ), ('Domain ' || quote_ident($1) || ' should not exist')::text );
+$$ LANGUAGE sql;
+
+-- has_enum( schema, enum, description )
+CREATE OR REPLACE FUNCTION has_enum( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, $2, ARRAY['e'] ), $3 );
+$$ LANGUAGE sql;
+
+-- has_enum( schema, enum )
+CREATE OR REPLACE FUNCTION has_enum( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT has_enum( $1, $2, 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- has_enum( enum, description )
+CREATE OR REPLACE FUNCTION has_enum( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, ARRAY['e'] ), $2 );
+$$ LANGUAGE sql;
+
+-- has_enum( enum )
+CREATE OR REPLACE FUNCTION has_enum( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should exist')::text );
+$$ LANGUAGE sql;
+
+-- hasnt_enum( schema, enum, description )
+CREATE OR REPLACE FUNCTION hasnt_enum( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, $2, ARRAY['e'] ), $3 );
+$$ LANGUAGE sql;
+
+-- hasnt_enum( schema, enum )
+CREATE OR REPLACE FUNCTION hasnt_enum( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT hasnt_enum( $1, $2, 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_enum( enum, description )
+CREATE OR REPLACE FUNCTION hasnt_enum( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, ARRAY['e'] ), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_enum( enum )
+CREATE OR REPLACE FUNCTION hasnt_enum( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text );
+$$ LANGUAGE sql;
+
+-- enum_has_labels( schema, enum, labels, description )
+CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT is(
+        ARRAY(
+            SELECT e.enumlabel
+              FROM pg_catalog.pg_type t
+              JOIN pg_catalog.pg_enum e      ON t.oid = e.enumtypid
+              JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
+              WHERE t.typisdefined
+               AND n.nspname = $1
+               AND t.typname = $2
+               AND t.typtype = 'e'
+             ORDER BY e.oid
+        ),
+        $3,
+        $4
+    );
+$$ LANGUAGE sql;
+
+-- enum_has_labels( schema, enum, labels )
+CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT enum_has_labels(
+        $1, $2, $3,
+        'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should have labels (' || array_to_string( $3, ', ' ) || ')'
+    );
+$$ LANGUAGE sql;
+
+-- enum_has_labels( enum, labels, description )
+CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT is(
+        ARRAY(
+            SELECT e.enumlabel
+              FROM pg_catalog.pg_type t
+              JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid
+              WHERE t.typisdefined
+               AND pg_catalog.pg_type_is_visible(t.oid)
+               AND t.typname = $1
+               AND t.typtype = 'e'
+             ORDER BY e.oid
+        ),
+        $2,
+        $3
+    );
+$$ LANGUAGE sql;
+
+-- enum_has_labels( enum, labels )
+CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT enum_has_labels(
+        $1, $2,
+        'Enum ' || quote_ident($1) || ' should have labels (' || array_to_string( $2, ', ' ) || ')'
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _has_role( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_roles
+         WHERE rolname = $1
+    );
+$$ LANGUAGE sql STRICT;
+
+-- has_role( role, description )
+CREATE OR REPLACE FUNCTION has_role( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_role($1), $2 );
+$$ LANGUAGE sql;
+
+-- has_role( role )
+CREATE OR REPLACE FUNCTION has_role( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _has_role($1), 'Role ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_role( role, description )
+CREATE OR REPLACE FUNCTION hasnt_role( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_role($1), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_role( role )
+CREATE OR REPLACE FUNCTION hasnt_role( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_role($1), 'Role ' || quote_ident($1) || ' should not exist' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _has_user( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS( SELECT true FROM pg_catalog.pg_user WHERE usename = $1);
+$$ LANGUAGE sql STRICT;
+
+-- has_user( user, description )
+CREATE OR REPLACE FUNCTION has_user( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_user($1), $2 );
+$$ LANGUAGE sql;
+
+-- has_user( user )
+CREATE OR REPLACE FUNCTION has_user( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _has_user( $1 ), 'User ' || quote_ident($1) || ' should exist');
+$$ LANGUAGE sql;
+
+-- hasnt_user( user, description )
+CREATE OR REPLACE FUNCTION hasnt_user( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_user($1), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_user( user )
+CREATE OR REPLACE FUNCTION hasnt_user( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_user( $1 ), 'User ' || quote_ident($1) || ' should not exist');
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _is_super( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT rolsuper
+      FROM pg_catalog.pg_roles
+     WHERE rolname = $1
+$$ LANGUAGE sql STRICT;
+
+-- is_superuser( user, description )
+CREATE OR REPLACE FUNCTION is_superuser( NAME, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    is_super boolean := _is_super($1);
+BEGIN
+    IF is_super IS NULL THEN
+        RETURN fail( $2 ) || E'\n' || diag( '    User ' || quote_ident($1) || ' does not exist') ;
+    END IF;
+    RETURN ok( is_super, $2 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- is_superuser( user )
+CREATE OR REPLACE FUNCTION is_superuser( NAME )
+RETURNS TEXT AS $$
+    SELECT is_superuser( $1, 'User ' || quote_ident($1) || ' should be a super user' );
+$$ LANGUAGE sql;
+
+-- isnt_superuser( user, description )
+CREATE OR REPLACE FUNCTION isnt_superuser( NAME, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    is_super boolean := _is_super($1);
+BEGIN
+    IF is_super IS NULL THEN
+        RETURN fail( $2 ) || E'\n' || diag( '    User ' || quote_ident($1) || ' does not exist') ;
+    END IF;
+    RETURN ok( NOT is_super, $2 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- isnt_superuser( user )
+CREATE OR REPLACE FUNCTION isnt_superuser( NAME )
+RETURNS TEXT AS $$
+    SELECT isnt_superuser( $1, 'User ' || quote_ident($1) || ' should not be a super user' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _has_group( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS(
+        SELECT true
+          FROM pg_catalog.pg_group
+         WHERE groname = $1
+    );
+$$ LANGUAGE sql STRICT;
+
+-- has_group( group, description )
+CREATE OR REPLACE FUNCTION has_group( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _has_group($1), $2 );
+$$ LANGUAGE sql;
+
+-- has_group( group )
+CREATE OR REPLACE FUNCTION has_group( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _has_group($1), 'Group ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE sql;
+
+-- hasnt_group( group, description )
+CREATE OR REPLACE FUNCTION hasnt_group( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_group($1), $2 );
+$$ LANGUAGE sql;
+
+-- hasnt_group( group )
+CREATE OR REPLACE FUNCTION hasnt_group( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _has_group($1), 'Group ' || quote_ident($1) || ' should not exist' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _grolist ( NAME )
+RETURNS oid[] AS $$
+    SELECT ARRAY(
+        SELECT member
+          FROM pg_catalog.pg_auth_members m
+          JOIN pg_catalog.pg_roles r ON m.roleid = r.oid
+         WHERE r.rolname =  $1
+    );
+$$ LANGUAGE sql;
+
+-- is_member_of( group, user[], description )
+CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    missing text[];
+BEGIN
+    IF NOT _has_role($1) THEN
+        RETURN fail( $3 ) || E'\n' || diag (
+            '    Role ' || quote_ident($1) || ' does not exist'
+        );
+    END IF;
+
+    SELECT ARRAY(
+        SELECT quote_ident($2[i])
+          FROM generate_series(1, array_upper($2, 1)) s(i)
+          LEFT JOIN pg_catalog.pg_user ON usename = $2[i]
+         WHERE usesysid IS NULL
+            OR NOT usesysid = ANY ( _grolist($1) )
+         ORDER BY s.i
+    ) INTO missing;
+    IF missing[1] IS NULL THEN
+        RETURN ok( true, $3 );
+    END IF;
+    RETURN ok( false, $3 ) || E'\n' || diag(
+        '    Users missing from the ' || quote_ident($1) || E' group:\n        ' ||
+        array_to_string( missing, E'\n        ')
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- is_member_of( group, user, description )
+CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT is_member_of( $1, ARRAY[$2], $3 );
+$$ LANGUAGE SQL;
+
+-- is_member_of( group, user[] )
+CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) );
+$$ LANGUAGE SQL;
+
+-- is_member_of( group, user )
+CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT is_member_of( $1, ARRAY[$2] );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _cmp_types(oid, name)
+RETURNS BOOLEAN AS $$
+DECLARE
+    dtype TEXT := display_type($1, NULL);
+BEGIN
+    RETURN dtype = _quote_ident_like($2, dtype);
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+       SELECT TRUE
+         FROM pg_catalog.pg_cast c
+         JOIN pg_catalog.pg_proc p ON c.castfunc = p.oid
+         JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid
+        WHERE _cmp_types(castsource, $1)
+          AND _cmp_types(casttarget, $2)
+          AND n.nspname   = $3
+          AND p.proname   = $4
+   );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+       SELECT TRUE
+         FROM pg_catalog.pg_cast c
+         JOIN pg_catalog.pg_proc p ON c.castfunc = p.oid
+        WHERE _cmp_types(castsource, $1)
+          AND _cmp_types(casttarget, $2)
+          AND p.proname   = $3
+   );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+       SELECT TRUE
+         FROM pg_catalog.pg_cast c
+        WHERE _cmp_types(castsource, $1)
+          AND _cmp_types(casttarget, $2)
+   );
+$$ LANGUAGE SQL;
+
+-- has_cast( source_type, target_type, schema, function, description )
+CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+   SELECT ok( _cast_exists( $1, $2, $3, $4 ), $5 );
+$$ LANGUAGE SQL;
+
+-- has_cast( source_type, target_type, schema, function )
+CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+   SELECT ok(
+       _cast_exists( $1, $2, $3, $4 ),
+        'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2)
+        || ') WITH FUNCTION ' || quote_ident($3)
+        || '.' || quote_ident($4) || '() should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_cast( source_type, target_type, function, description )
+CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+   SELECT ok( _cast_exists( $1, $2, $3 ), $4 );
+$$ LANGUAGE SQL;
+
+-- has_cast( source_type, target_type, function )
+CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+   SELECT ok(
+        _cast_exists( $1, $2, $3 ),
+        'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2)
+        || ') WITH FUNCTION ' || quote_ident($3) || '() should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_cast( source_type, target_type, description )
+CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _cast_exists( $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- has_cast( source_type, target_type )
+CREATE OR REPLACE FUNCTION has_cast ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _cast_exists( $1, $2 ),
+        'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2)
+        || ') should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- hasnt_cast( source_type, target_type, schema, function, description )
+CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+   SELECT ok( NOT _cast_exists( $1, $2, $3, $4 ), $5 );
+$$ LANGUAGE SQL;
+
+-- hasnt_cast( source_type, target_type, schema, function )
+CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+   SELECT ok(
+       NOT _cast_exists( $1, $2, $3, $4 ),
+        'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2)
+        || ') WITH FUNCTION ' || quote_ident($3)
+        || '.' || quote_ident($4) || '() should not exist'
+    );
+$$ LANGUAGE SQL;
+
+-- hasnt_cast( source_type, target_type, function, description )
+CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+   SELECT ok( NOT _cast_exists( $1, $2, $3 ), $4 );
+$$ LANGUAGE SQL;
+
+-- hasnt_cast( source_type, target_type, function )
+CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+   SELECT ok(
+        NOT _cast_exists( $1, $2, $3 ),
+        'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2)
+        || ') WITH FUNCTION ' || quote_ident($3) || '() should not exist'
+    );
+$$ LANGUAGE SQL;
+
+-- hasnt_cast( source_type, target_type, description )
+CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _cast_exists( $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_cast( source_type, target_type )
+CREATE OR REPLACE FUNCTION hasnt_cast ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        NOT _cast_exists( $1, $2 ),
+        'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2)
+        || ') should not exist'
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _expand_context( char )
+RETURNS text AS $$
+   SELECT CASE $1
+          WHEN 'i' THEN 'implicit'
+          WHEN 'a' THEN 'assignment'
+          WHEN 'e' THEN 'explicit'
+          ELSE          'unknown' END
+$$ LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION _get_context( NAME, NAME )
+RETURNS "char" AS $$
+   SELECT c.castcontext
+     FROM pg_catalog.pg_cast c
+    WHERE _cmp_types(castsource, $1)
+      AND _cmp_types(casttarget, $2)
+$$ LANGUAGE SQL;
+
+-- cast_context_is( source_type, target_type, context, description )
+CREATE OR REPLACE FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    want char = substring(LOWER($3) FROM 1 FOR 1);
+    have char := _get_context($1, $2);
+BEGIN
+    IF have IS NOT NULL THEN
+        RETURN is( _expand_context(have), _expand_context(want), $4 );
+    END IF;
+
+    RETURN ok( false, $4 ) || E'\n' || diag(
+       '    Cast (' || quote_ident($1) || ' AS ' || quote_ident($2)
+      || ') does not exist'
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- cast_context_is( source_type, target_type, context )
+CREATE OR REPLACE FUNCTION cast_context_is( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT cast_context_is(
+        $1, $2, $3,
+        'Cast (' || quote_ident($1) || ' AS ' || quote_ident($2)
+        || ') context should be ' || _expand_context(substring(LOWER($3) FROM 1 FOR 1))
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+       SELECT TRUE
+         FROM pg_catalog.pg_operator o
+         JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid
+        WHERE n.nspname = $2
+          AND o.oprname = $3
+          AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL
+              ELSE _cmp_types(o.oprleft, $1) END
+          AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL
+              ELSE _cmp_types(o.oprright, $4) END
+          AND _cmp_types(o.oprresult, $5)
+   );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+       SELECT TRUE
+         FROM pg_catalog.pg_operator o
+        WHERE pg_catalog.pg_operator_is_visible(o.oid)
+          AND o.oprname = $2
+          AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL
+              ELSE _cmp_types(o.oprleft, $1) END
+          AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL
+              ELSE _cmp_types(o.oprright, $3) END
+          AND _cmp_types(o.oprresult, $4)
+   );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+       SELECT TRUE
+         FROM pg_catalog.pg_operator o
+        WHERE pg_catalog.pg_operator_is_visible(o.oid)
+          AND o.oprname = $2
+          AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL
+              ELSE _cmp_types(o.oprleft, $1) END
+          AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL
+              ELSE _cmp_types(o.oprright, $3) END
+   );
+$$ LANGUAGE SQL;
+
+-- has_operator( left_type, schema, name, right_type, return_type, description )
+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists($1, $2, $3, $4, $5 ), $6 );
+$$ LANGUAGE SQL;
+
+-- has_operator( left_type, schema, name, right_type, return_type )
+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists($1, $2, $3, $4, $5 ),
+        'Operator ' || quote_ident($2) || '.' || $3 || '(' || $1 || ',' || $4
+        || ') RETURNS ' || $5 || ' should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_operator( left_type, name, right_type, return_type, description )
+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists($1, $2, $3, $4 ), $5 );
+$$ LANGUAGE SQL;
+
+-- has_operator( left_type, name, right_type, return_type )
+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists($1, $2, $3, $4 ),
+        'Operator ' ||  $2 || '(' || $1 || ',' || $3
+        || ') RETURNS ' || $4 || ' should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_operator( left_type, name, right_type, description )
+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists($1, $2, $3 ), $4 );
+$$ LANGUAGE SQL;
+
+-- has_operator( left_type, name, right_type )
+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists($1, $2, $3 ),
+        'Operator ' ||  $2 || '(' || $1 || ',' || $3
+        || ') should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_leftop( schema, name, right_type, return_type, description )
+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists(NULL, $1, $2, $3, $4), $5 );
+$$ LANGUAGE SQL;
+
+-- has_leftop( schema, name, right_type, return_type )
+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists(NULL, $1, $2, $3, $4 ),
+        'Left operator ' || quote_ident($1) || '.' || $2 || '(NONE,'
+        || $3 || ') RETURNS ' || $4 || ' should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_leftop( name, right_type, return_type, description )
+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists(NULL, $1, $2, $3), $4 );
+$$ LANGUAGE SQL;
+
+-- has_leftop( name, right_type, return_type )
+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists(NULL, $1, $2, $3 ),
+        'Left operator ' || $1 || '(NONE,' || $2 || ') RETURNS ' || $3 || ' should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_leftop( name, right_type, description )
+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists(NULL, $1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- has_leftop( name, right_type )
+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists(NULL, $1, $2 ),
+        'Left operator ' || $1 || '(NONE,' || $2 || ') should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_rightop( left_type, schema, name, return_type, description )
+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists( $1, $2, $3, NULL, $4), $5 );
+$$ LANGUAGE SQL;
+
+-- has_rightop( left_type, schema, name, return_type )
+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists($1, $2, $3, NULL, $4 ),
+        'Right operator ' || quote_ident($2) || '.' || $3 || '('
+        || $1 || ',NONE) RETURNS ' || $4 || ' should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_rightop( left_type, name, return_type, description )
+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists( $1, $2, NULL, $3), $4 );
+$$ LANGUAGE SQL;
+
+-- has_rightop( left_type, name, return_type )
+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists($1, $2, NULL, $3 ),
+        'Right operator ' || $2 || '('
+        || $1 || ',NONE) RETURNS ' || $3 || ' should exist'
+    );
+$$ LANGUAGE SQL;
+
+-- has_rightop( left_type, name, description )
+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _op_exists( $1, $2, NULL), $3 );
+$$ LANGUAGE SQL;
+
+-- has_rightop( left_type, name )
+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+         _op_exists($1, $2, NULL ),
+        'Right operator ' || $2 || '(' || $1 || ',NONE) should exist'
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _are ( text, name[], name[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    what    ALIAS FOR $1;
+    extras  ALIAS FOR $2;
+    missing ALIAS FOR $3;
+    descr   ALIAS FOR $4;
+    msg     TEXT    := '';
+    res     BOOLEAN := TRUE;
+BEGIN
+    IF extras[1] IS NOT NULL THEN
+        res = FALSE;
+        msg := E'\n' || diag(
+            '    Extra ' || what || E':\n        '
+            ||  _ident_array_to_string( extras, E'\n        ' )
+        );
+    END IF;
+    IF missing[1] IS NOT NULL THEN
+        res = FALSE;
+        msg := msg || E'\n' || diag(
+            '    Missing ' || what || E':\n        '
+            ||  _ident_array_to_string( missing, E'\n        ' )
+        );
+    END IF;
+
+    RETURN ok(res, descr) || msg;
+END;
+$$ LANGUAGE plpgsql;
+
+-- tablespaces_are( tablespaces, description )
+CREATE OR REPLACE FUNCTION tablespaces_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'tablespaces',
+        ARRAY(
+            SELECT spcname
+              FROM pg_catalog.pg_tablespace
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+               FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT spcname
+              FROM pg_catalog.pg_tablespace
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- tablespaces_are( tablespaces )
+CREATE OR REPLACE FUNCTION tablespaces_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT tablespaces_are( $1, 'There should be the correct tablespaces' );
+$$ LANGUAGE SQL;
+
+-- schemas_are( schemas, description )
+CREATE OR REPLACE FUNCTION schemas_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'schemas',
+        ARRAY(
+            SELECT nspname
+              FROM pg_catalog.pg_namespace
+             WHERE nspname NOT LIKE 'pg_%'
+               AND nspname <> 'information_schema'
+             EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT nspname
+              FROM pg_catalog.pg_namespace
+             WHERE nspname NOT LIKE 'pg_%'
+               AND nspname <> 'information_schema'
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- schemas_are( schemas )
+CREATE OR REPLACE FUNCTION schemas_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT schemas_are( $1, 'There should be the correct schemas' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME, NAME[] )
+RETURNS NAME[] AS $$
+    SELECT ARRAY(
+        SELECT c.relname
+          FROM pg_catalog.pg_namespace n
+          JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+         WHERE c.relkind = $1
+           AND n.nspname = $2
+           AND c.relname NOT IN('pg_all_foreign_keys', 'tap_funky', '__tresults___numb_seq', '__tcache___id_seq')
+        EXCEPT
+        SELECT $3[i]
+          FROM generate_series(1, array_upper($3, 1)) s(i)
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME[] )
+RETURNS NAME[] AS $$
+    SELECT ARRAY(
+        SELECT c.relname
+          FROM pg_catalog.pg_namespace n
+          JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+         WHERE pg_catalog.pg_table_is_visible(c.oid)
+           AND n.nspname <> 'pg_catalog'
+           AND c.relkind = $1
+           AND c.relname NOT IN ('__tcache__', '__tresults__', 'pg_all_foreign_keys', 'tap_funky', '__tresults___numb_seq', '__tcache___id_seq')
+        EXCEPT
+        SELECT $2[i]
+          FROM generate_series(1, array_upper($2, 1)) s(i)
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME, NAME[] )
+RETURNS NAME[] AS $$
+    SELECT ARRAY(
+        SELECT $3[i]
+          FROM generate_series(1, array_upper($3, 1)) s(i)
+        EXCEPT
+        SELECT c.relname
+          FROM pg_catalog.pg_namespace n
+          JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+         WHERE c.relkind = $1
+           AND n.nspname = $2
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME[] )
+RETURNS NAME[] AS $$
+    SELECT ARRAY(
+        SELECT $2[i]
+          FROM generate_series(1, array_upper($2, 1)) s(i)
+        EXCEPT
+        SELECT c.relname
+          FROM pg_catalog.pg_namespace n
+          JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+         WHERE pg_catalog.pg_table_is_visible(c.oid)
+           AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+           AND c.relkind = $1
+    );
+$$ LANGUAGE SQL;
+
+-- tables_are( schema, tables, description )
+CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are( 'tables', _extras('r', $1, $2), _missing('r', $1, $2), $3);
+$$ LANGUAGE SQL;
+
+-- tables_are( tables, description )
+CREATE OR REPLACE FUNCTION tables_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are( 'tables', _extras('r', $1), _missing('r', $1), $2);
+$$ LANGUAGE SQL;
+
+-- tables_are( schema, tables )
+CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'tables', _extras('r', $1, $2), _missing('r', $1, $2),
+        'Schema ' || quote_ident($1) || ' should have the correct tables'
+    );
+$$ LANGUAGE SQL;
+
+-- tables_are( tables )
+CREATE OR REPLACE FUNCTION tables_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'tables', _extras('r', $1), _missing('r', $1),
+        'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables'
+    );
+$$ LANGUAGE SQL;
+
+-- views_are( schema, views, description )
+CREATE OR REPLACE FUNCTION views_are ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are( 'views', _extras('v', $1, $2), _missing('v', $1, $2), $3);
+$$ LANGUAGE SQL;
+
+-- views_are( views, description )
+CREATE OR REPLACE FUNCTION views_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are( 'views', _extras('v', $1), _missing('v', $1), $2);
+$$ LANGUAGE SQL;
+
+-- views_are( schema, views )
+CREATE OR REPLACE FUNCTION views_are ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'views', _extras('v', $1, $2), _missing('v', $1, $2),
+        'Schema ' || quote_ident($1) || ' should have the correct views'
+    );
+$$ LANGUAGE SQL;
+
+-- views_are( views )
+CREATE OR REPLACE FUNCTION views_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'views', _extras('v', $1), _missing('v', $1),
+        'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views'
+    );
+$$ LANGUAGE SQL;
+
+-- sequences_are( schema, sequences, description )
+CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are( 'sequences', _extras('S', $1, $2), _missing('S', $1, $2), $3);
+$$ LANGUAGE SQL;
+
+-- sequences_are( sequences, description )
+CREATE OR REPLACE FUNCTION sequences_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are( 'sequences', _extras('S', $1), _missing('S', $1), $2);
+$$ LANGUAGE SQL;
+
+-- sequences_are( schema, sequences )
+CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'sequences', _extras('S', $1, $2), _missing('S', $1, $2),
+        'Schema ' || quote_ident($1) || ' should have the correct sequences'
+    );
+$$ LANGUAGE SQL;
+
+-- sequences_are( sequences )
+CREATE OR REPLACE FUNCTION sequences_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'sequences', _extras('S', $1), _missing('S', $1),
+        'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences'
+    );
+$$ LANGUAGE SQL;
+
+-- functions_are( schema, functions[], description )
+CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'functions',
+        ARRAY(
+            SELECT name FROM tap_funky WHERE schema = $1
+            EXCEPT
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $2[i]
+               FROM generate_series(1, array_upper($2, 1)) s(i)
+            EXCEPT
+            SELECT name FROM tap_funky WHERE schema = $1
+        ),
+        $3
+    );
+$$ LANGUAGE SQL;
+
+-- functions_are( schema, functions[] )
+CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT functions_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct functions' );
+$$ LANGUAGE SQL;
+
+-- functions_are( functions[], description )
+CREATE OR REPLACE FUNCTION functions_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'functions',
+        ARRAY(
+            SELECT name FROM tap_funky WHERE is_visible
+            AND schema NOT IN ('pg_catalog', 'information_schema')
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+               FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT name FROM tap_funky WHERE is_visible
+            AND schema NOT IN ('pg_catalog', 'information_schema')
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- functions_are( functions[] )
+CREATE OR REPLACE FUNCTION functions_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT functions_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct functions' );
+$$ LANGUAGE SQL;
+
+-- indexes_are( schema, table, indexes[], description )
+CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'indexes',
+        ARRAY(
+            SELECT ci.relname
+              FROM pg_catalog.pg_index x
+              JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+              JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+              JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+             WHERE ct.relname = $2
+               AND n.nspname  = $1
+            EXCEPT
+            SELECT $3[i]
+              FROM generate_series(1, array_upper($3, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $3[i]
+              FROM generate_series(1, array_upper($3, 1)) s(i)
+            EXCEPT
+            SELECT ci.relname
+              FROM pg_catalog.pg_index x
+              JOIN pg_catalog.pg_class ct    ON ct.oid = x.indrelid
+              JOIN pg_catalog.pg_class ci    ON ci.oid = x.indexrelid
+              JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+             WHERE ct.relname = $2
+               AND n.nspname  = $1
+        ),
+        $4
+    );
+$$ LANGUAGE SQL;
+
+-- indexes_are( schema, table, indexes[] )
+CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT indexes_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct indexes' );
+$$ LANGUAGE SQL;
+
+-- indexes_are( table, indexes[], description )
+CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'indexes',
+        ARRAY(
+            SELECT ci.relname
+              FROM pg_catalog.pg_index x
+              JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+              JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+              JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+             WHERE ct.relname = $1
+               AND pg_catalog.pg_table_is_visible(ct.oid)
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+            EXCEPT
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+            EXCEPT
+            SELECT ci.relname
+              FROM pg_catalog.pg_index x
+              JOIN pg_catalog.pg_class ct ON ct.oid = x.indrelid
+              JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid
+              JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
+             WHERE ct.relname = $1
+               AND pg_catalog.pg_table_is_visible(ct.oid)
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+        ),
+        $3
+    );
+$$ LANGUAGE SQL;
+
+-- indexes_are( table, indexes[] )
+CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT indexes_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct indexes' );
+$$ LANGUAGE SQL;
+
+-- users_are( users[], description )
+CREATE OR REPLACE FUNCTION users_are( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'users',
+        ARRAY(
+            SELECT usename
+              FROM pg_catalog.pg_user
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT usename
+              FROM pg_catalog.pg_user
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- users_are( users[] )
+CREATE OR REPLACE FUNCTION users_are( NAME[] )
+RETURNS TEXT AS $$
+    SELECT users_are( $1, 'There should be the correct users' );
+$$ LANGUAGE SQL;
+
+-- groups_are( groups[], description )
+CREATE OR REPLACE FUNCTION groups_are( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'groups',
+        ARRAY(
+            SELECT groname
+              FROM pg_catalog.pg_group
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT groname
+              FROM pg_catalog.pg_group
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- groups_are( groups[] )
+CREATE OR REPLACE FUNCTION groups_are( NAME[] )
+RETURNS TEXT AS $$
+    SELECT groups_are( $1, 'There should be the correct groups' );
+$$ LANGUAGE SQL;
+
+-- languages_are( languages[], description )
+CREATE OR REPLACE FUNCTION languages_are( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'languages',
+        ARRAY(
+            SELECT lanname
+              FROM pg_catalog.pg_language
+             WHERE lanispl
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT lanname
+              FROM pg_catalog.pg_language
+             WHERE lanispl
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- languages_are( languages[] )
+CREATE OR REPLACE FUNCTION languages_are( NAME[] )
+RETURNS TEXT AS $$
+    SELECT languages_are( $1, 'There should be the correct procedural languages' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _is_trusted( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT lanpltrusted FROM pg_catalog.pg_language WHERE lanname = $1;
+$$ LANGUAGE SQL;
+
+-- has_language( language, description)
+CREATE OR REPLACE FUNCTION has_language( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _is_trusted($1) IS NOT NULL, $2 );
+$$ LANGUAGE SQL;
+
+-- has_language( language )
+CREATE OR REPLACE FUNCTION has_language( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _is_trusted($1) IS NOT NULL, 'Procedural language ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- hasnt_language( language, description)
+CREATE OR REPLACE FUNCTION hasnt_language( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _is_trusted($1) IS NULL, $2 );
+$$ LANGUAGE SQL;
+
+-- hasnt_language( language )
+CREATE OR REPLACE FUNCTION hasnt_language( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _is_trusted($1) IS NULL, 'Procedural language ' || quote_ident($1) || ' should not exist' );
+$$ LANGUAGE SQL;
+
+-- language_is_trusted( language, description )
+CREATE OR REPLACE FUNCTION language_is_trusted( NAME, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    is_trusted boolean := _is_trusted($1);
+BEGIN
+    IF is_trusted IS NULL THEN
+        RETURN fail( $2 ) || E'\n' || diag( '    Procedural language ' || quote_ident($1) || ' does not exist') ;
+    END IF;
+    RETURN ok( is_trusted, $2 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- language_is_trusted( language )
+CREATE OR REPLACE FUNCTION language_is_trusted( NAME )
+RETURNS TEXT AS $$
+    SELECT language_is_trusted($1, 'Procedural language ' || quote_ident($1) || ' should be trusted' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _opc_exists( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT EXISTS (
+        SELECT TRUE
+          FROM pg_catalog.pg_opclass oc
+          JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid
+         WHERE n.nspname  = COALESCE($1, n.nspname)
+           AND oc.opcname = $2
+    );
+$$ LANGUAGE SQL;
+
+-- has_opclass( schema, name, description )
+CREATE OR REPLACE FUNCTION has_opclass( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _opc_exists( $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- has_opclass( schema, name )
+CREATE OR REPLACE FUNCTION has_opclass( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- has_opclass( name, description )
+CREATE OR REPLACE FUNCTION has_opclass( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _opc_exists( NULL, $1 ), $2)
+$$ LANGUAGE SQL;
+
+-- has_opclass( name )
+CREATE OR REPLACE FUNCTION has_opclass( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- hasnt_opclass( schema, name, description )
+CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _opc_exists( $1, $2 ), $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_opclass( schema, name )
+CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- hasnt_opclass( name, description )
+CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _opc_exists( NULL, $1 ), $2)
+$$ LANGUAGE SQL;
+
+-- hasnt_opclass( name )
+CREATE OR REPLACE FUNCTION hasnt_opclass( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should exist' );
+$$ LANGUAGE SQL;
+
+-- opclasses_are( schema, opclasses[], description )
+CREATE OR REPLACE FUNCTION opclasses_are ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'operator classes',
+        ARRAY(
+            SELECT oc.opcname
+              FROM pg_catalog.pg_opclass oc
+              JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid
+             WHERE n.nspname  = $1
+            EXCEPT
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $2[i]
+               FROM generate_series(1, array_upper($2, 1)) s(i)
+            EXCEPT
+            SELECT oc.opcname
+              FROM pg_catalog.pg_opclass oc
+              JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid
+             WHERE n.nspname  = $1
+        ),
+        $3
+    );
+$$ LANGUAGE SQL;
+
+-- opclasses_are( schema, opclasses[] )
+CREATE OR REPLACE FUNCTION opclasses_are ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT opclasses_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct operator classes' );
+$$ LANGUAGE SQL;
+
+-- opclasses_are( opclasses[], description )
+CREATE OR REPLACE FUNCTION opclasses_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'operator classes',
+        ARRAY(
+            SELECT oc.opcname
+              FROM pg_catalog.pg_opclass oc
+              JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+               AND pg_catalog.pg_opclass_is_visible(oc.oid)
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+               FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT oc.opcname
+              FROM pg_catalog.pg_opclass oc
+              JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+               AND pg_catalog.pg_opclass_is_visible(oc.oid)
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- opclasses_are( opclasses[] )
+CREATE OR REPLACE FUNCTION opclasses_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT opclasses_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct operator classes' );
+$$ LANGUAGE SQL;
+
+-- rules_are( schema, table, rules[], description )
+CREATE OR REPLACE FUNCTION rules_are( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'rules',
+        ARRAY(
+            SELECT r.rulename
+              FROM pg_catalog.pg_rewrite r
+              JOIN pg_catalog.pg_class c     ON c.oid = r.ev_class
+              JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+             WHERE c.relname = $2
+               AND n.nspname = $1
+            EXCEPT
+            SELECT $3[i]
+              FROM generate_series(1, array_upper($3, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $3[i]
+              FROM generate_series(1, array_upper($3, 1)) s(i)
+            EXCEPT
+            SELECT r.rulename
+              FROM pg_catalog.pg_rewrite r
+              JOIN pg_catalog.pg_class c     ON c.oid = r.ev_class
+              JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+             WHERE c.relname = $2
+               AND n.nspname = $1
+        ),
+        $4
+    );
+$$ LANGUAGE SQL;
+
+-- rules_are( schema, table, rules[] )
+CREATE OR REPLACE FUNCTION rules_are( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT rules_are( $1, $2, $3, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct rules' );
+$$ LANGUAGE SQL;
+
+-- rules_are( table, rules[], description )
+CREATE OR REPLACE FUNCTION rules_are( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'rules',
+        ARRAY(
+            SELECT r.rulename
+              FROM pg_catalog.pg_rewrite r
+              JOIN pg_catalog.pg_class c     ON c.oid = r.ev_class
+              JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+             WHERE c.relname = $1
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+               AND pg_catalog.pg_table_is_visible(c.oid)
+            EXCEPT
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+            EXCEPT
+            SELECT r.rulename
+              FROM pg_catalog.pg_rewrite r
+              JOIN pg_catalog.pg_class c     ON c.oid = r.ev_class
+              JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+               AND c.relname = $1
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+               AND pg_catalog.pg_table_is_visible(c.oid)
+        ),
+        $3
+    );
+$$ LANGUAGE SQL;
+
+-- rules_are( table, rules[] )
+CREATE OR REPLACE FUNCTION rules_are( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT rules_are( $1, $2, 'Relation ' || quote_ident($1) || ' should have the correct rules' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _is_instead( NAME, NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT r.is_instead
+      FROM pg_catalog.pg_rewrite r
+      JOIN pg_catalog.pg_class c     ON c.oid = r.ev_class
+      JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+     WHERE r.rulename = $3
+       AND c.relname  = $2
+       AND n.nspname  = $1
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _is_instead( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT r.is_instead
+      FROM pg_catalog.pg_rewrite r
+      JOIN pg_catalog.pg_class c     ON c.oid = r.ev_class
+     WHERE r.rulename = $2
+       AND c.relname  = $1
+       AND pg_catalog.pg_table_is_visible(c.oid)
+$$ LANGUAGE SQL;
+
+-- has_rule( schema, table, rule, description )
+CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _is_instead($1, $2, $3) IS NOT NULL, $4 );
+$$ LANGUAGE SQL;
+
+-- has_rule( schema, table, rule )
+CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _is_instead($1, $2, $3) IS NOT NULL, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should have rule ' || quote_ident($3) );
+$$ LANGUAGE SQL;
+
+-- has_rule( table, rule, description )
+CREATE OR REPLACE FUNCTION has_rule( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _is_instead($1, $2) IS NOT NULL, $3 );
+$$ LANGUAGE SQL;
+
+-- has_rule( table, rule )
+CREATE OR REPLACE FUNCTION has_rule( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _is_instead($1, $2) IS NOT NULL, 'Relation ' || quote_ident($1) || ' should have rule ' || quote_ident($2) );
+$$ LANGUAGE SQL;
+
+-- hasnt_rule( schema, table, rule, description )
+CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _is_instead($1, $2, $3) IS NULL, $4 );
+$$ LANGUAGE SQL;
+
+-- hasnt_rule( schema, table, rule )
+CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _is_instead($1, $2, $3) IS NULL, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should not have rule ' || quote_ident($3) );
+$$ LANGUAGE SQL;
+
+-- hasnt_rule( table, rule, description )
+CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( _is_instead($1, $2) IS NULL, $3 );
+$$ LANGUAGE SQL;
+
+-- hasnt_rule( table, rule )
+CREATE OR REPLACE FUNCTION hasnt_rule( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _is_instead($1, $2) IS NULL, 'Relation ' || quote_ident($1) || ' should not have rule ' || quote_ident($2) );
+$$ LANGUAGE SQL;
+
+-- rule_is_instead( schema, table, rule, description )
+CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    is_it boolean := _is_instead($1, $2, $3);
+BEGIN
+    IF is_it IS NOT NULL THEN RETURN ok( is_it, $4 ); END IF;
+    RETURN ok( FALSE, $4 ) || E'\n' || diag(
+        '    Rule ' || quote_ident($3) || ' does not exist'
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- rule_is_instead( schema, table, rule )
+CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT rule_is_instead( $1, $2, $3, 'Rule ' || quote_ident($3) || ' on relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be an INSTEAD rule' );
+$$ LANGUAGE SQL;
+
+-- rule_is_instead( table, rule, description )
+CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    is_it boolean := _is_instead($1, $2);
+BEGIN
+    IF is_it IS NOT NULL THEN RETURN ok( is_it, $3 ); END IF;
+    RETURN ok( FALSE, $3 ) || E'\n' || diag(
+        '    Rule ' || quote_ident($2) || ' does not exist'
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- rule_is_instead( table, rule )
+CREATE OR REPLACE FUNCTION rule_is_instead( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT rule_is_instead($1, $2, 'Rule ' || quote_ident($2) || ' on relation ' || quote_ident($1) || ' should be an INSTEAD rule' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _expand_on( char )
+RETURNS text AS $$
+   SELECT CASE $1
+          WHEN '1' THEN 'SELECT'
+          WHEN '2' THEN 'UPDATE'
+          WHEN '3' THEN 'INSERT'
+          WHEN '4' THEN 'DELETE'
+          ELSE          'UNKNOWN' END
+$$ LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION _contract_on( TEXT )
+RETURNS "char" AS $$
+   SELECT CASE substring(LOWER($1) FROM 1 FOR 1)
+          WHEN 's' THEN '1'::"char"
+          WHEN 'u' THEN '2'::"char"
+          WHEN 'i' THEN '3'::"char"
+          WHEN 'd' THEN '4'::"char"
+          ELSE          '0'::"char" END
+$$ LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION _rule_on( NAME, NAME, NAME )
+RETURNS "char" AS $$
+    SELECT r.ev_type
+      FROM pg_catalog.pg_rewrite r
+      JOIN pg_catalog.pg_class c     ON c.oid = r.ev_class
+      JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+     WHERE r.rulename = $3
+       AND c.relname  = $2
+       AND n.nspname  = $1
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _rule_on( NAME, NAME )
+RETURNS "char" AS $$
+    SELECT r.ev_type
+      FROM pg_catalog.pg_rewrite r
+      JOIN pg_catalog.pg_class c     ON c.oid = r.ev_class
+     WHERE r.rulename = $2
+       AND c.relname  = $1
+$$ LANGUAGE SQL;
+
+-- rule_is_on( schema, table, rule, event, description )
+CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    want char := _contract_on($4);
+    have char := _rule_on($1, $2, $3);
+BEGIN
+    IF have IS NOT NULL THEN
+        RETURN is( _expand_on(have), _expand_on(want), $5 );
+    END IF;
+
+    RETURN ok( false, $5 ) || E'\n' || diag(
+        '    Rule ' || quote_ident($3) || ' does not exist on '
+        || quote_ident($1) || '.' || quote_ident($2)
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- rule_is_on( schema, table, rule, event )
+CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT rule_is_on(
+        $1, $2, $3, $4,
+        'Rule ' || quote_ident($3) || ' should be on ' || _expand_on(_contract_on($4)::char)
+        || ' to ' || quote_ident($1) || '.' || quote_ident($2)
+    );
+$$ LANGUAGE SQL;
+
+-- rule_is_on( table, rule, event, description )
+CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    want char := _contract_on($3);
+    have char := _rule_on($1, $2);
+BEGIN
+    IF have IS NOT NULL THEN
+        RETURN is( _expand_on(have), _expand_on(want), $4 );
+    END IF;
+
+    RETURN ok( false, $4 ) || E'\n' || diag(
+        '    Rule ' || quote_ident($2) || ' does not exist on '
+        || quote_ident($1)
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- rule_is_on( table, rule, event )
+CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT rule_is_on(
+        $1, $2, $3,
+        'Rule ' || quote_ident($2) || ' should be on '
+        || _expand_on(_contract_on($3)::char) || ' to ' || quote_ident($1)
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _nosuch( NAME, NAME, NAME[])
+RETURNS TEXT AS $$
+    SELECT E'\n' || diag(
+        '    Function '
+          || CASE WHEN $1 IS NOT NULL THEN quote_ident($1) || '.' ELSE '' END
+          || quote_ident($2) || '('
+          || array_to_string($3, ', ') || ') does not exist'
+    );
+$$ LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT)
+RETURNS TEXT AS $$
+    SELECT CASE WHEN $4 IS NULL
+      THEN ok( FALSE, $6 ) || _nosuch($1, $2, $3)
+      ELSE is( $4, $5, $6 )
+      END;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], boolean, TEXT)
+RETURNS TEXT AS $$
+    SELECT CASE WHEN $4 IS NULL
+      THEN ok( FALSE, $5 ) || _nosuch($1, $2, $3)
+      ELSE ok( $4, $5 )
+      END;
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT)
+RETURNS TEXT AS $$
+    SELECT CASE WHEN $3 IS NULL
+      THEN ok( FALSE, $5 ) || _nosuch($1, $2, '{}')
+      ELSE is( $3, $4, $5 )
+      END;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, boolean, TEXT)
+RETURNS TEXT AS $$
+    SELECT CASE WHEN $3 IS NULL
+      THEN ok( FALSE, $4 ) || _nosuch($1, $2, '{}')
+      ELSE ok( $3, $4 )
+      END;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _lang ( NAME, NAME, NAME[] )
+RETURNS NAME AS $$
+    SELECT l.lanname
+      FROM tap_funky f
+      JOIN pg_catalog.pg_language l ON f.langoid = l.oid
+     WHERE f.schema = $1
+       and f.name   = $2
+       AND f.args   = array_to_string($3, ',')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _lang ( NAME, NAME )
+RETURNS NAME AS $$
+    SELECT l.lanname
+      FROM tap_funky f
+      JOIN pg_catalog.pg_language l ON f.langoid = l.oid
+     WHERE f.schema = $1
+       and f.name   = $2
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _lang ( NAME, NAME[] )
+RETURNS NAME AS $$
+    SELECT l.lanname
+      FROM tap_funky f
+      JOIN pg_catalog.pg_language l ON f.langoid = l.oid
+     WHERE f.name = $1
+       AND f.args = array_to_string($2, ',')
+       AND f.is_visible;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _lang ( NAME )
+RETURNS NAME AS $$
+    SELECT l.lanname
+      FROM tap_funky f
+      JOIN pg_catalog.pg_language l ON f.langoid = l.oid
+     WHERE f.name = $1
+       AND f.is_visible;
+$$ LANGUAGE SQL;
+
+-- function_lang_is( schema, function, args[], language, description )
+CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME[], NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, $3, _lang($1, $2, $3), $4, $5 );
+$$ LANGUAGE SQL;
+
+-- function_lang_is( schema, function, args[], language )
+CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME[], NAME )
+RETURNS TEXT AS $$
+    SELECT function_lang_is(
+        $1, $2, $3, $4,
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' ||
+        array_to_string($3, ', ') || ') should be written in ' || quote_ident($4)
+    );
+$$ LANGUAGE SQL;
+
+-- function_lang_is( schema, function, language, description )
+CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, _lang($1, $2), $3, $4 );
+$$ LANGUAGE SQL;
+
+-- function_lang_is( schema, function, language )
+CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT function_lang_is(
+        $1, $2, $3,
+        'Function ' || quote_ident($1) || '.' || quote_ident($2)
+        || '() should be written in ' || quote_ident($3)
+    );
+$$ LANGUAGE SQL;
+
+-- function_lang_is( function, args[], language, description )
+CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME[], NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, $2, _lang($1, $2), $3, $4 );
+$$ LANGUAGE SQL;
+
+-- function_lang_is( function, args[], language )
+CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME[], NAME )
+RETURNS TEXT AS $$
+    SELECT function_lang_is(
+        $1, $2, $3,
+        'Function ' || quote_ident($1) || '(' ||
+        array_to_string($2, ', ') || ') should be written in ' || quote_ident($3)
+    );
+$$ LANGUAGE SQL;
+
+-- function_lang_is( function, language, description )
+CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, _lang($1), $2, $3 );
+$$ LANGUAGE SQL;
+
+-- function_lang_is( function, language )
+CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT function_lang_is(
+        $1, $2,
+        'Function ' || quote_ident($1)
+        || '() should be written in ' || quote_ident($2)
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _returns ( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT returns
+      FROM tap_funky
+     WHERE schema = $1
+       AND name   = $2
+       AND args   = array_to_string($3, ',')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _returns ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT returns FROM tap_funky WHERE schema = $1 AND name = $2
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _returns ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT returns
+      FROM tap_funky
+     WHERE name = $1
+       AND args = array_to_string($2, ',')
+       AND is_visible;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _returns ( NAME )
+RETURNS TEXT AS $$
+    SELECT returns FROM tap_funky WHERE name = $1 AND is_visible;
+$$ LANGUAGE SQL;
+
+-- function_returns( schema, function, args[], type, description )
+CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, NAME[], TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, $3, _returns($1, $2, $3), $4, $5 );
+$$ LANGUAGE SQL;
+
+-- function_returns( schema, function, args[], type )
+CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT function_returns(
+        $1, $2, $3, $4,
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' ||
+        array_to_string($3, ', ') || ') should return ' || $4
+    );
+$$ LANGUAGE SQL;
+
+-- function_returns( schema, function, type, description )
+CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, _returns($1, $2), $3, $4 );
+$$ LANGUAGE SQL;
+
+-- function_returns( schema, function, type )
+CREATE OR REPLACE FUNCTION function_returns( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT function_returns(
+        $1, $2, $3,
+        'Function ' || quote_ident($1) || '.' || quote_ident($2)
+        || '() should return ' || $3
+    );
+$$ LANGUAGE SQL;
+
+-- function_returns( function, args[], type, description )
+CREATE OR REPLACE FUNCTION function_returns( NAME, NAME[], TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, $2, _returns($1, $2), $3, $4 );
+$$ LANGUAGE SQL;
+
+-- function_returns( function, args[], type )
+CREATE OR REPLACE FUNCTION function_returns( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT function_returns(
+        $1, $2, $3,
+        'Function ' || quote_ident($1) || '(' ||
+        array_to_string($2, ', ') || ') should return ' || $3
+    );
+$$ LANGUAGE SQL;
+
+-- function_returns( function, type, description )
+CREATE OR REPLACE FUNCTION function_returns( NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, _returns($1), $2, $3 );
+$$ LANGUAGE SQL;
+
+-- function_returns( function, type )
+CREATE OR REPLACE FUNCTION function_returns( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT function_returns(
+        $1, $2,
+        'Function ' || quote_ident($1) || '() should return ' || $2
+    );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _definer ( NAME, NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT is_definer
+      FROM tap_funky
+     WHERE schema = $1
+       AND name   = $2
+       AND args   = array_to_string($3, ',')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _definer ( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT is_definer FROM tap_funky WHERE schema = $1 AND name = $2
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _definer ( NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT is_definer
+      FROM tap_funky
+     WHERE name = $1
+       AND args = array_to_string($2, ',')
+       AND is_visible;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _definer ( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT is_definer FROM tap_funky WHERE name = $1 AND is_visible;
+$$ LANGUAGE SQL;
+
+-- is_definer( schema, function, args[], description )
+CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, $3, _definer($1, $2, $3), $4 );
+$$ LANGUAGE SQL;
+
+-- is_definer( schema, function, args[] )
+CREATE OR REPLACE FUNCTION is_definer( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _definer($1, $2, $3),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' ||
+        array_to_string($3, ', ') || ') should be security definer'
+    );
+$$ LANGUAGE sql;
+
+-- is_definer( schema, function, description )
+CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, _definer($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- is_definer( schema, function )
+CREATE OR REPLACE FUNCTION is_definer( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _definer($1, $2),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be security definer'
+    );
+$$ LANGUAGE sql;
+
+-- is_definer( function, args[], description )
+CREATE OR REPLACE FUNCTION is_definer ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, $2, _definer($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- is_definer( function, args[] )
+CREATE OR REPLACE FUNCTION is_definer( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _definer($1, $2),
+        'Function ' || quote_ident($1) || '(' ||
+        array_to_string($2, ', ') || ') should be security definer'
+    );
+$$ LANGUAGE sql;
+
+-- is_definer( function, description )
+CREATE OR REPLACE FUNCTION is_definer( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, _definer($1), $2 );
+$$ LANGUAGE sql;
+
+-- is_definer( function )
+CREATE OR REPLACE FUNCTION is_definer( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _definer($1), 'Function ' || quote_ident($1) || '() should be security definer' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT is_agg
+      FROM tap_funky
+     WHERE schema = $1
+       AND name   = $2
+       AND args   = array_to_string($3, ',')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _agg ( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT is_agg
+      FROM tap_funky
+     WHERE name = $1
+       AND args = array_to_string($2, ',')
+       AND is_visible;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _agg ( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT is_agg FROM tap_funky WHERE name = $1 AND is_visible;
+$$ LANGUAGE SQL;
+
+-- is_aggregate( schema, function, args[], description )
+CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, $3, _agg($1, $2, $3), $4 );
+$$ LANGUAGE SQL;
+
+-- is_aggregate( schema, function, args[] )
+CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _agg($1, $2, $3),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' ||
+        array_to_string($3, ', ') || ') should be an aggregate function'
+    );
+$$ LANGUAGE sql;
+
+-- is_aggregate( schema, function, description )
+CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, _agg($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- is_aggregate( schema, function )
+CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _agg($1, $2),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be an aggregate function'
+    );
+$$ LANGUAGE sql;
+
+-- is_aggregate( function, args[], description )
+CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, $2, _agg($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- is_aggregate( function, args[] )
+CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _agg($1, $2),
+        'Function ' || quote_ident($1) || '(' ||
+        array_to_string($2, ', ') || ') should be an aggregate function'
+    );
+$$ LANGUAGE sql;
+
+-- is_aggregate( function, description )
+CREATE OR REPLACE FUNCTION is_aggregate( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, _agg($1), $2 );
+$$ LANGUAGE sql;
+
+-- is_aggregate( function )
+CREATE OR REPLACE FUNCTION is_aggregate( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _agg($1), 'Function ' || quote_ident($1) || '() should be an aggregate function' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _strict ( NAME, NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT is_strict
+      FROM tap_funky
+     WHERE schema = $1
+       AND name   = $2
+       AND args   = array_to_string($3, ',')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _strict ( NAME, NAME )
+RETURNS BOOLEAN AS $$
+    SELECT is_strict FROM tap_funky WHERE schema = $1 AND name = $2
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _strict ( NAME, NAME[] )
+RETURNS BOOLEAN AS $$
+    SELECT is_strict
+      FROM tap_funky
+     WHERE name = $1
+       AND args = array_to_string($2, ',')
+       AND is_visible;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _strict ( NAME )
+RETURNS BOOLEAN AS $$
+    SELECT is_strict FROM tap_funky WHERE name = $1 AND is_visible;
+$$ LANGUAGE SQL;
+
+-- is_strict( schema, function, args[], description )
+CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, $3, _strict($1, $2, $3), $4 );
+$$ LANGUAGE SQL;
+
+-- is_strict( schema, function, args[] )
+CREATE OR REPLACE FUNCTION is_strict( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _strict($1, $2, $3),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' ||
+        array_to_string($3, ', ') || ') should be strict'
+    );
+$$ LANGUAGE sql;
+
+-- is_strict( schema, function, description )
+CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, _strict($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- is_strict( schema, function )
+CREATE OR REPLACE FUNCTION is_strict( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _strict($1, $2),
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be strict'
+    );
+$$ LANGUAGE sql;
+
+-- is_strict( function, args[], description )
+CREATE OR REPLACE FUNCTION is_strict ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, $2, _strict($1, $2), $3 );
+$$ LANGUAGE SQL;
+
+-- is_strict( function, args[] )
+CREATE OR REPLACE FUNCTION is_strict( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT ok(
+        _strict($1, $2),
+        'Function ' || quote_ident($1) || '(' ||
+        array_to_string($2, ', ') || ') should be strict'
+    );
+$$ LANGUAGE sql;
+
+-- is_strict( function, description )
+CREATE OR REPLACE FUNCTION is_strict( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, _strict($1), $2 );
+$$ LANGUAGE sql;
+
+-- is_strict( function )
+CREATE OR REPLACE FUNCTION is_strict( NAME )
+RETURNS TEXT AS $$
+    SELECT ok( _strict($1), 'Function ' || quote_ident($1) || '() should be strict' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _expand_vol( char )
+RETURNS TEXT AS $$
+   SELECT CASE $1
+          WHEN 'i' THEN 'IMMUTABLE'
+          WHEN 's' THEN 'STABLE'
+          WHEN 'v' THEN 'VOLATILE'
+          ELSE          'UNKNOWN' END
+$$ LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION _refine_vol( text )
+RETURNS text AS $$
+    SELECT _expand_vol(substring(LOWER($1) FROM 1 FOR 1)::char);
+$$ LANGUAGE SQL IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION _vol ( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT _expand_vol(volatility)
+      FROM tap_funky f
+     WHERE f.schema = $1
+       and f.name   = $2
+       AND f.args   = array_to_string($3, ',')
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _vol ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT _expand_vol(volatility) FROM tap_funky f
+     WHERE f.schema = $1 and f.name = $2
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _vol ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT _expand_vol(volatility)
+      FROM tap_funky f
+     WHERE f.name = $1
+       AND f.args = array_to_string($2, ',')
+       AND f.is_visible;
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _vol ( NAME )
+RETURNS TEXT AS $$
+    SELECT _expand_vol(volatility) FROM tap_funky f
+     WHERE f.name = $1 AND f.is_visible;
+$$ LANGUAGE SQL;
+
+-- volatility_is( schema, function, args[], volatility, description )
+CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, NAME[], TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, $3, _vol($1, $2, $3), _refine_vol($4), $5 );
+$$ LANGUAGE SQL;
+
+-- volatility_is( schema, function, args[], volatility )
+CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT volatility_is(
+        $1, $2, $3, $4,
+        'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' ||
+        array_to_string($3, ', ') || ') should be ' || _refine_vol($4)
+    );
+$$ LANGUAGE SQL;
+
+-- volatility_is( schema, function, volatility, description )
+CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare($1, $2, _vol($1, $2), _refine_vol($3), $4 );
+$$ LANGUAGE SQL;
+
+-- volatility_is( schema, function, volatility )
+CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT volatility_is(
+        $1, $2, $3,
+        'Function ' || quote_ident($1) || '.' || quote_ident($2)
+        || '() should be ' || _refine_vol($3)
+    );
+$$ LANGUAGE SQL;
+
+-- volatility_is( function, args[], volatility, description )
+CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME[], TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, $2, _vol($1, $2), _refine_vol($3), $4 );
+$$ LANGUAGE SQL;
+
+-- volatility_is( function, args[], volatility )
+CREATE OR REPLACE FUNCTION volatility_is( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT volatility_is(
+        $1, $2, $3,
+        'Function ' || quote_ident($1) || '(' ||
+        array_to_string($2, ', ') || ') should be ' || _refine_vol($3)
+    );
+$$ LANGUAGE SQL;
+
+-- volatility_is( function, volatility, description )
+CREATE OR REPLACE FUNCTION volatility_is( NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _func_compare(NULL, $1, _vol($1), _refine_vol($2), $3 );
+$$ LANGUAGE SQL;
+
+-- volatility_is( function, volatility )
+CREATE OR REPLACE FUNCTION volatility_is( NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT volatility_is(
+        $1, $2,
+        'Function ' || quote_ident($1) || '() should be ' || _refine_vol($2)
+    );
+$$ LANGUAGE SQL;
+
+-- check_test( test_output, pass, name, description, diag, match_diag )
+CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN )
+RETURNS SETOF TEXT AS $$
+DECLARE
+    tnumb   INTEGER;
+    aok     BOOLEAN;
+    adescr  TEXT;
+    res     BOOLEAN;
+    descr   TEXT;
+    adiag   TEXT;
+    have    ALIAS FOR $1;
+    eok     ALIAS FOR $2;
+    name    ALIAS FOR $3;
+    edescr  ALIAS FOR $4;
+    ediag   ALIAS FOR $5;
+    matchit ALIAS FOR $6;
+BEGIN
+    -- What test was it that just ran?
+    tnumb := currval('__tresults___numb_seq');
+
+    -- Fetch the results.
+    EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb
+       INTO aok, adescr;
+
+    -- Now delete those results.
+    EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb;
+    EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb;
+
+    -- Set up the description.
+    descr := coalesce( name || ' ', 'Test ' ) || 'should ';
+
+    -- So, did the test pass?
+    RETURN NEXT is(
+        aok,
+        eok,
+        descr || CASE eok WHEN true then 'pass' ELSE 'fail' END
+    );
+
+    -- Was the description as expected?
+    IF edescr IS NOT NULL THEN
+        RETURN NEXT is(
+            adescr,
+            edescr,
+            descr || 'have the proper description'
+        );
+    END IF;
+
+    -- Were the diagnostics as expected?
+    IF ediag IS NOT NULL THEN
+        -- Remove ok and the test number.
+        adiag := substring(
+            have
+            FROM CASE WHEN aok THEN 4 ELSE 9 END + char_length(tnumb::text)
+        );
+
+        -- Remove the description, if there is one.
+        IF adescr <> '' THEN
+            adiag := substring( adiag FROM 3 + char_length( diag( adescr ) ) );
+        END IF;
+
+        -- Remove failure message from ok().
+        IF NOT aok THEN
+           adiag := substring(
+               adiag
+               FROM 14 + char_length(tnumb::text)
+                       + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END
+           );
+        END IF;
+
+        -- Remove the #s.
+        adiag := replace( substring(adiag from 3), E'\n# ', E'\n' );
+
+        -- Now compare the diagnostics.
+        IF matchit THEN
+            RETURN NEXT matches(
+                adiag,
+                ediag,
+                descr || 'have the proper diagnostics'
+            );
+        ELSE
+            RETURN NEXT is(
+                adiag,
+                ediag,
+                descr || 'have the proper diagnostics'
+            );
+        END IF;
+    END IF;
+
+    -- And we're done
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+-- check_test( test_output, pass, name, description, diag )
+CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM check_test( $1, $2, $3, $4, $5, FALSE );
+$$ LANGUAGE sql;
+
+-- check_test( test_output, pass, name, description )
+CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM check_test( $1, $2, $3, $4, NULL, FALSE );
+$$ LANGUAGE sql;
+
+-- check_test( test_output, pass, name )
+CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM check_test( $1, $2, $3, NULL, NULL, FALSE );
+$$ LANGUAGE sql;
+
+-- check_test( test_output, pass )
+CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM check_test( $1, $2, NULL, NULL, NULL, FALSE );
+$$ LANGUAGE sql;
+
+
+CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT )
+RETURNS TEXT[] AS $$
+    SELECT ARRAY(
+        SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) AS pname
+          FROM pg_catalog.pg_proc p
+          JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid
+         WHERE n.nspname = $1
+           AND p.proname ~ $2
+         ORDER BY pname
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION findfuncs( TEXT )
+RETURNS TEXT[] AS $$
+    SELECT ARRAY(
+        SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) AS pname
+          FROM pg_catalog.pg_proc p
+          JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid
+         WHERE pg_catalog.pg_function_is_visible(p.oid)
+           AND p.proname ~ $1
+         ORDER BY pname
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _runem( text[], boolean )
+RETURNS SETOF TEXT AS $$
+DECLARE
+    tap    text;
+    lbound int := array_lower($1, 1);
+BEGIN
+    IF lbound IS NULL THEN RETURN; END IF;
+    FOR i IN lbound..array_upper($1, 1) LOOP
+        -- Send the name of the function to diag if warranted.
+        IF $2 THEN RETURN NEXT diag( $1[i] || '()' ); END IF;
+        -- Execute the tap function and return its results.
+        FOR tap IN EXECUTE 'SELECT * FROM ' || $1[i] || '()' LOOP
+            RETURN NEXT tap;
+        END LOOP;
+    END LOOP;
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _is_verbose()
+RETURNS BOOLEAN AS $$
+    SELECT current_setting('client_min_messages') NOT IN (
+        'warning', 'error', 'fatal', 'panic'
+    );
+$$ LANGUAGE sql STABLE;
+
+-- do_tap( schema, pattern )
+CREATE OR REPLACE FUNCTION do_tap( name, text )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM _runem( findfuncs($1, $2), _is_verbose() );
+$$ LANGUAGE sql;
+
+-- do_tap( schema )
+CREATE OR REPLACE FUNCTION do_tap( name )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM _runem( findfuncs($1, '^test'), _is_verbose() );
+$$ LANGUAGE sql;
+
+-- do_tap( pattern )
+CREATE OR REPLACE FUNCTION do_tap( text )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM _runem( findfuncs($1), _is_verbose() );
+$$ LANGUAGE sql;
+
+-- do_tap()
+CREATE OR REPLACE FUNCTION do_tap( )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM _runem( findfuncs('^test'), _is_verbose());
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _currtest()
+RETURNS INTEGER AS $$
+BEGIN
+    RETURN currval('__tresults___numb_seq');
+EXCEPTION
+    WHEN object_not_in_prerequisite_state THEN RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _cleanup()
+RETURNS boolean AS $$
+    DROP TABLE __tresults__;
+    DROP SEQUENCE __tresults___numb_seq;
+    DROP TABLE __tcache__;
+    DROP SEQUENCE __tcache___id_seq;
+    SELECT TRUE;
+$$ LANGUAGE sql;
+
+-- diag_test_name ( test_name )
+CREATE OR REPLACE FUNCTION diag_test_name(TEXT)
+RETURNS TEXT AS $$
+    SELECT diag($1 || '()');
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _runner( text[], text[], text[], text[], text[] )
+RETURNS SETOF TEXT AS $$
+DECLARE
+    startup  ALIAS FOR $1;
+    shutdown ALIAS FOR $2;
+    setup    ALIAS FOR $3;
+    teardown ALIAS FOR $4;
+    tests    ALIAS FOR $5;
+    tap      text;
+    verbos   boolean := _is_verbose(); -- verbose is a reserved word in 8.5.
+    num_faild INTEGER := 0;
+BEGIN
+    BEGIN
+        -- No plan support.
+        PERFORM * FROM no_plan();
+        FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP;
+    EXCEPTION
+        -- Catch all exceptions and simply rethrow custom exceptions. This
+        -- will roll back everything in the above block.
+        WHEN raise_exception THEN
+            RAISE EXCEPTION '%', SQLERRM;
+    END;
+
+    BEGIN
+        FOR i IN 1..array_upper(tests, 1) LOOP
+            BEGIN
+                -- What test are we running?
+                IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF;
+
+                -- Run the setup functions.
+                FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP;
+
+                -- Run the actual test function.
+                FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP
+                    RETURN NEXT tap;
+                END LOOP;
+
+                -- Run the teardown functions.
+                FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP;
+
+                -- Remember how many failed and then roll back.
+                num_faild := num_faild + num_failed();
+                RAISE EXCEPTION '__TAP_ROLLBACK__';
+
+            EXCEPTION WHEN raise_exception THEN
+                IF SQLERRM <> '__TAP_ROLLBACK__' THEN
+                    -- We didn't raise it, so propagate it.
+                    RAISE EXCEPTION '%', SQLERRM;
+                END IF;
+            END;
+        END LOOP;
+
+        -- Run the shutdown functions.
+        FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP;
+
+        -- Raise an exception to rollback any changes.
+        RAISE EXCEPTION '__TAP_ROLLBACK__';
+    EXCEPTION WHEN raise_exception THEN
+        IF SQLERRM <> '__TAP_ROLLBACK__' THEN
+            -- We didn't raise it, so propagate it.
+            RAISE EXCEPTION '%', SQLERRM;
+        END IF;
+    END;
+    -- Finish up.
+    FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) LOOP
+        RETURN NEXT tap;
+    END LOOP;
+
+    -- Clean up and return.
+    PERFORM _cleanup();
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+-- runtests( schema, match )
+CREATE OR REPLACE FUNCTION runtests( NAME, TEXT )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM _runner(
+        findfuncs( $1, '^startup' ),
+        findfuncs( $1, '^shutdown' ),
+        findfuncs( $1, '^setup' ),
+        findfuncs( $1, '^teardown' ),
+        findfuncs( $1, $2 )
+    );
+$$ LANGUAGE sql;
+
+-- runtests( schema )
+CREATE OR REPLACE FUNCTION runtests( NAME )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM runtests( $1, '^test' );
+$$ LANGUAGE sql;
+
+-- runtests( match )
+CREATE OR REPLACE FUNCTION runtests( TEXT )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM _runner(
+        findfuncs( '^startup' ),
+        findfuncs( '^shutdown' ),
+        findfuncs( '^setup' ),
+        findfuncs( '^teardown' ),
+        findfuncs( $1 )
+    );
+$$ LANGUAGE sql;
+
+-- runtests( )
+CREATE OR REPLACE FUNCTION runtests( )
+RETURNS SETOF TEXT AS $$
+    SELECT * FROM runtests( '^test' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    EXECUTE 'CREATE TEMP TABLE ' || $2 || ' AS ' || _query($1);
+    return $2;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _temptable ( anyarray, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    CREATE TEMP TABLE _____coltmp___ AS
+    SELECT $1[i]
+    FROM generate_series(array_lower($1, 1), array_upper($1, 1)) s(i);
+    EXECUTE 'ALTER TABLE _____coltmp___ RENAME TO ' || $2;
+    return $2;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _temptypes( TEXT )
+RETURNS TEXT AS $$
+    SELECT array_to_string(ARRAY(
+        SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
+          FROM pg_catalog.pg_attribute a
+          JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
+         WHERE c.oid = ('pg_temp.' || $1)::pg_catalog.regclass
+           AND attnum > 0
+           AND NOT attisdropped
+         ORDER BY attnum
+    ), ',');
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _docomp( TEXT, TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have    ALIAS FOR $1;
+    want    ALIAS FOR $2;
+    extras  TEXT[]  := '{}';
+    missing TEXT[]  := '{}';
+    res     BOOLEAN := TRUE;
+    msg     TEXT    := '';
+    rec     RECORD;
+BEGIN
+    BEGIN
+        -- Find extra records.
+        FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4
+                        || 'SELECT * FROM ' || want LOOP
+            extras := extras || rec::text;
+        END LOOP;
+
+        -- Find missing records.
+        FOR rec in EXECUTE 'SELECT * FROM ' || want || ' EXCEPT ' || $4
+                        || 'SELECT * FROM ' || have LOOP
+            missing := missing || rec::text;
+        END LOOP;
+
+        -- Drop the temporary tables.
+        EXECUTE 'DROP TABLE ' || have;
+        EXECUTE 'DROP TABLE ' || want;
+    EXCEPTION WHEN syntax_error OR datatype_mismatch THEN
+        msg := E'\n' || diag(
+            E'    Columns differ between queries:\n'
+            || '        have: (' || _temptypes(have) || E')\n'
+            || '        want: (' || _temptypes(want) || ')'
+        );
+        EXECUTE 'DROP TABLE ' || have;
+        EXECUTE 'DROP TABLE ' || want;
+        RETURN ok(FALSE, $3) || msg;
+    END;
+
+    -- What extra records do we have?
+    IF extras[1] IS NOT NULL THEN
+        res := FALSE;
+        msg := E'\n' || diag(
+            E'    Extra records:\n        '
+            ||  array_to_string( extras, E'\n        ' )
+        );
+    END IF;
+
+    -- What missing records do we have?
+    IF missing[1] IS NOT NULL THEN
+        res := FALSE;
+        msg := msg || E'\n' || diag(
+            E'    Missing records:\n        '
+            ||  array_to_string( missing, E'\n        ' )
+        );
+    END IF;
+
+    RETURN ok(res, $3) || msg;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _docomp(
+        _temptable( $1, '__taphave__' ),
+        _temptable( $2, '__tapwant__' ),
+        $3, $4
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _relcomp( TEXT, anyarray, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _docomp(
+        _temptable( $1, '__taphave__' ),
+        _temptable( $2, '__tapwant__' ),
+        $3, $4
+    );
+$$ LANGUAGE sql;
+
+-- set_eq( sql, sql, description )
+CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, $3, '' );
+$$ LANGUAGE sql;
+
+-- set_eq( sql, sql )
+CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, NULL::text, '' );
+$$ LANGUAGE sql;
+
+-- set_eq( sql, array, description )
+CREATE OR REPLACE FUNCTION set_eq( TEXT, anyarray, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, $3, '' );
+$$ LANGUAGE sql;
+
+-- set_eq( sql, array )
+CREATE OR REPLACE FUNCTION set_eq( TEXT, anyarray )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, NULL::text, '' );
+$$ LANGUAGE sql;
+
+-- bag_eq( sql, sql, description )
+CREATE OR REPLACE FUNCTION bag_eq( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, $3, 'ALL ' );
+$$ LANGUAGE sql;
+
+-- bag_eq( sql, sql )
+CREATE OR REPLACE FUNCTION bag_eq( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, NULL::text, 'ALL ' );
+$$ LANGUAGE sql;
+
+-- bag_eq( sql, array, description )
+CREATE OR REPLACE FUNCTION bag_eq( TEXT, anyarray, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, $3, 'ALL ' );
+$$ LANGUAGE sql;
+
+-- bag_eq( sql, array )
+CREATE OR REPLACE FUNCTION bag_eq( TEXT, anyarray )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, NULL::text, 'ALL ' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _do_ne( TEXT, TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have    ALIAS FOR $1;
+    want    ALIAS FOR $2;
+    extras  TEXT[]  := '{}';
+    missing TEXT[]  := '{}';
+    res     BOOLEAN := TRUE;
+    msg     TEXT    := '';
+BEGIN
+    BEGIN
+        -- Find extra records.
+        EXECUTE 'SELECT EXISTS ( '
+             || '( SELECT * FROM ' || have || ' EXCEPT ' || $4
+             || '  SELECT * FROM ' || want
+             || ' ) UNION ( '
+             || '  SELECT * FROM ' || want || ' EXCEPT ' || $4
+             || '  SELECT * FROM ' || have
+             || ' ) LIMIT 1 )' INTO res;
+
+        -- Drop the temporary tables.
+        EXECUTE 'DROP TABLE ' || have;
+        EXECUTE 'DROP TABLE ' || want;
+    EXCEPTION WHEN syntax_error OR datatype_mismatch THEN
+        msg := E'\n' || diag(
+            E'    Columns differ between queries:\n'
+            || '        have: (' || _temptypes(have) || E')\n'
+            || '        want: (' || _temptypes(want) || ')'
+        );
+        EXECUTE 'DROP TABLE ' || have;
+        EXECUTE 'DROP TABLE ' || want;
+        RETURN ok(FALSE, $3) || msg;
+    END;
+
+    -- Return the value from the query.
+    RETURN ok(res, $3);
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION _relne( TEXT, TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _do_ne(
+        _temptable( $1, '__taphave__' ),
+        _temptable( $2, '__tapwant__' ),
+        $3, $4
+    );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _relne( TEXT, anyarray, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _do_ne(
+        _temptable( $1, '__taphave__' ),
+        _temptable( $2, '__tapwant__' ),
+        $3, $4
+    );
+$$ LANGUAGE sql;
+
+-- set_ne( sql, sql, description )
+CREATE OR REPLACE FUNCTION set_ne( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relne( $1, $2, $3, '' );
+$$ LANGUAGE sql;
+
+-- set_ne( sql, sql )
+CREATE OR REPLACE FUNCTION set_ne( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relne( $1, $2, NULL::text, '' );
+$$ LANGUAGE sql;
+
+-- set_ne( sql, array, description )
+CREATE OR REPLACE FUNCTION set_ne( TEXT, anyarray, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relne( $1, $2, $3, '' );
+$$ LANGUAGE sql;
+
+-- set_ne( sql, array )
+CREATE OR REPLACE FUNCTION set_ne( TEXT, anyarray )
+RETURNS TEXT AS $$
+    SELECT _relne( $1, $2, NULL::text, '' );
+$$ LANGUAGE sql;
+
+-- bag_ne( sql, sql, description )
+CREATE OR REPLACE FUNCTION bag_ne( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relne( $1, $2, $3, 'ALL ' );
+$$ LANGUAGE sql;
+
+-- bag_ne( sql, sql )
+CREATE OR REPLACE FUNCTION bag_ne( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relne( $1, $2, NULL::text, 'ALL ' );
+$$ LANGUAGE sql;
+
+-- bag_ne( sql, array, description )
+CREATE OR REPLACE FUNCTION bag_ne( TEXT, anyarray, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relne( $1, $2, $3, 'ALL ' );
+$$ LANGUAGE sql;
+
+-- bag_ne( sql, array )
+CREATE OR REPLACE FUNCTION bag_ne( TEXT, anyarray )
+RETURNS TEXT AS $$
+    SELECT _relne( $1, $2, NULL::text, 'ALL ' );
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have    TEXT    := _temptable( $1, '__taphave__' );
+    want    TEXT    := _temptable( $2, '__tapwant__' );
+    results TEXT[]  := '{}';
+    res     BOOLEAN := TRUE;
+    msg     TEXT    := '';
+    rec     RECORD;
+BEGIN
+    BEGIN
+        -- Find relevant records.
+        FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4
+                       || ' SELECT * FROM ' || have LOOP
+            results := results || rec::text;
+        END LOOP;
+
+        -- Drop the temporary tables.
+        EXECUTE 'DROP TABLE ' || have;
+        EXECUTE 'DROP TABLE ' || want;
+    EXCEPTION WHEN syntax_error OR datatype_mismatch THEN
+        msg := E'\n' || diag(
+            E'    Columns differ between queries:\n'
+            || '        have: (' || _temptypes(have) || E')\n'
+            || '        want: (' || _temptypes(want) || ')'
+        );
+        EXECUTE 'DROP TABLE ' || have;
+        EXECUTE 'DROP TABLE ' || want;
+        RETURN ok(FALSE, $3) || msg;
+    END;
+
+    -- What records do we have?
+    IF results[1] IS NOT NULL THEN
+        res := FALSE;
+        msg := msg || E'\n' || diag(
+            '    ' || $5 || E' records:\n        '
+            ||  array_to_string( results, E'\n        ' )
+        );
+    END IF;
+
+    RETURN ok(res, $3) || msg;
+END;
+$$ LANGUAGE plpgsql;
+
+-- set_has( sql, sql, description )
+CREATE OR REPLACE FUNCTION set_has( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, $3, 'EXCEPT', 'Missing' );
+$$ LANGUAGE sql;
+
+-- set_has( sql, sql )
+CREATE OR REPLACE FUNCTION set_has( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, NULL::TEXT, 'EXCEPT', 'Missing' );
+$$ LANGUAGE sql;
+
+-- bag_has( sql, sql, description )
+CREATE OR REPLACE FUNCTION bag_has( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, $3, 'EXCEPT ALL', 'Missing' );
+$$ LANGUAGE sql;
+
+-- bag_has( sql, sql )
+CREATE OR REPLACE FUNCTION bag_has( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, NULL::TEXT, 'EXCEPT ALL', 'Missing' );
+$$ LANGUAGE sql;
+
+-- set_hasnt( sql, sql, description )
+CREATE OR REPLACE FUNCTION set_hasnt( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, $3, 'INTERSECT', 'Extra' );
+$$ LANGUAGE sql;
+
+-- set_hasnt( sql, sql )
+CREATE OR REPLACE FUNCTION set_hasnt( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, NULL::TEXT, 'INTERSECT', 'Extra' );
+$$ LANGUAGE sql;
+
+-- bag_hasnt( sql, sql, description )
+CREATE OR REPLACE FUNCTION bag_hasnt( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, $3, 'INTERSECT ALL', 'Extra' );
+$$ LANGUAGE sql;
+
+-- bag_hasnt( sql, sql )
+CREATE OR REPLACE FUNCTION bag_hasnt( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT _relcomp( $1, $2, NULL::TEXT, 'INTERSECT ALL', 'Extra' );
+$$ LANGUAGE sql;
+
+-- results_eq( cursor, cursor, description )
+CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor, text )
+RETURNS TEXT AS $$
+DECLARE
+    have       ALIAS FOR $1;
+    want       ALIAS FOR $2;
+    have_rec   RECORD;
+    want_rec   RECORD;
+    have_found BOOLEAN;
+    want_found BOOLEAN;
+    rownum     INTEGER := 1;
+BEGIN
+    FETCH have INTO have_rec;
+    have_found := FOUND;
+    FETCH want INTO want_rec;
+    want_found := FOUND;
+    WHILE have_found OR want_found LOOP
+        IF have_rec IS DISTINCT FROM want_rec OR have_found <> want_found THEN
+            RETURN ok( false, $3 ) || E'\n' || diag(
+                '    Results differ beginning at row ' || rownum || E':\n' ||
+                '        have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' ||
+                '        want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END
+            );
+        END IF;
+        rownum = rownum + 1;
+        FETCH have INTO have_rec;
+        have_found := FOUND;
+        FETCH want INTO want_rec;
+        want_found := FOUND;
+    END LOOP;
+
+    RETURN ok( true, $3 );
+EXCEPTION
+    WHEN datatype_mismatch THEN
+        RETURN ok( false, $3 ) || E'\n' || diag(
+            E'    Columns differ between queries:\n' ||
+            '        have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' ||
+            '        want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END
+        );
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_eq( cursor, cursor )
+CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor )
+RETURNS TEXT AS $$
+    SELECT results_eq( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_eq( sql, sql, description )
+CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have REFCURSOR;
+    want REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN have FOR EXECUTE _query($1);
+    OPEN want FOR EXECUTE _query($2);
+    res := results_eq(have, want, $3);
+    CLOSE have;
+    CLOSE want;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_eq( sql, sql )
+CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT results_eq( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_eq( sql, array, description )
+CREATE OR REPLACE FUNCTION results_eq( TEXT, anyarray, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have REFCURSOR;
+    want REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN have FOR EXECUTE _query($1);
+    OPEN want FOR SELECT $2[i]
+    FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i);
+    res := results_eq(have, want, $3);
+    CLOSE have;
+    CLOSE want;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_eq( sql, array )
+CREATE OR REPLACE FUNCTION results_eq( TEXT, anyarray )
+RETURNS TEXT AS $$
+    SELECT results_eq( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_eq( sql, cursor, description )
+CREATE OR REPLACE FUNCTION results_eq( TEXT, refcursor, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN have FOR EXECUTE _query($1);
+    res := results_eq(have, $2, $3);
+    CLOSE have;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_eq( sql, cursor )
+CREATE OR REPLACE FUNCTION results_eq( TEXT, refcursor )
+RETURNS TEXT AS $$
+    SELECT results_eq( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_eq( cursor, sql, description )
+CREATE OR REPLACE FUNCTION results_eq( refcursor, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    want REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN want FOR EXECUTE _query($2);
+    res := results_eq($1, want, $3);
+    CLOSE want;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_eq( cursor, sql )
+CREATE OR REPLACE FUNCTION results_eq( refcursor, TEXT )
+RETURNS TEXT AS $$
+    SELECT results_eq( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_eq( cursor, array, description )
+CREATE OR REPLACE FUNCTION results_eq( refcursor, anyarray, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    want REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN want FOR SELECT $2[i]
+    FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i);
+    res := results_eq($1, want, $3);
+    CLOSE want;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_eq( cursor, array )
+CREATE OR REPLACE FUNCTION results_eq( refcursor, anyarray )
+RETURNS TEXT AS $$
+    SELECT results_eq( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_ne( cursor, cursor, description )
+CREATE OR REPLACE FUNCTION results_ne( refcursor, refcursor, text )
+RETURNS TEXT AS $$
+DECLARE
+    have       ALIAS FOR $1;
+    want       ALIAS FOR $2;
+    have_rec   RECORD;
+    want_rec   RECORD;
+    have_found BOOLEAN;
+    want_found BOOLEAN;
+BEGIN
+    FETCH have INTO have_rec;
+    have_found := FOUND;
+    FETCH want INTO want_rec;
+    want_found := FOUND;
+    WHILE have_found OR want_found LOOP
+        IF have_rec IS DISTINCT FROM want_rec OR have_found <> want_found THEN
+            RETURN ok( true, $3 );
+        ELSE
+            FETCH have INTO have_rec;
+            have_found := FOUND;
+            FETCH want INTO want_rec;
+            want_found := FOUND;
+        END IF;
+    END LOOP;
+    RETURN ok( false, $3 );
+EXCEPTION
+    WHEN datatype_mismatch THEN
+        RETURN ok( false, $3 ) || E'\n' || diag(
+            E'    Columns differ between queries:\n' ||
+            '        have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' ||
+            '        want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END
+        );
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_ne( cursor, cursor )
+CREATE OR REPLACE FUNCTION results_ne( refcursor, refcursor )
+RETURNS TEXT AS $$
+    SELECT results_ne( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_ne( sql, sql, description )
+CREATE OR REPLACE FUNCTION results_ne( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have REFCURSOR;
+    want REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN have FOR EXECUTE _query($1);
+    OPEN want FOR EXECUTE _query($2);
+    res := results_ne(have, want, $3);
+    CLOSE have;
+    CLOSE want;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_ne( sql, sql )
+CREATE OR REPLACE FUNCTION results_ne( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT results_ne( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_ne( sql, array, description )
+CREATE OR REPLACE FUNCTION results_ne( TEXT, anyarray, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have REFCURSOR;
+    want REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN have FOR EXECUTE _query($1);
+    OPEN want FOR SELECT $2[i]
+    FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i);
+    res := results_ne(have, want, $3);
+    CLOSE have;
+    CLOSE want;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_ne( sql, array )
+CREATE OR REPLACE FUNCTION results_ne( TEXT, anyarray )
+RETURNS TEXT AS $$
+    SELECT results_ne( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_ne( sql, cursor, description )
+CREATE OR REPLACE FUNCTION results_ne( TEXT, refcursor, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    have REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN have FOR EXECUTE _query($1);
+    res := results_ne(have, $2, $3);
+    CLOSE have;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_ne( sql, cursor )
+CREATE OR REPLACE FUNCTION results_ne( TEXT, refcursor )
+RETURNS TEXT AS $$
+    SELECT results_ne( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_ne( cursor, sql, description )
+CREATE OR REPLACE FUNCTION results_ne( refcursor, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    want REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN want FOR EXECUTE _query($2);
+    res := results_ne($1, want, $3);
+    CLOSE want;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_ne( cursor, sql )
+CREATE OR REPLACE FUNCTION results_ne( refcursor, TEXT )
+RETURNS TEXT AS $$
+    SELECT results_ne( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- results_ne( cursor, array, description )
+CREATE OR REPLACE FUNCTION results_ne( refcursor, anyarray, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    want REFCURSOR;
+    res  TEXT;
+BEGIN
+    OPEN want FOR SELECT $2[i]
+    FROM generate_series(array_lower($2, 1), array_upper($2, 1)) s(i);
+    res := results_ne($1, want, $3);
+    CLOSE want;
+    RETURN res;
+END;
+$$ LANGUAGE plpgsql;
+
+-- results_ne( cursor, array )
+CREATE OR REPLACE FUNCTION results_ne( refcursor, anyarray )
+RETURNS TEXT AS $$
+    SELECT results_ne( $1, $2, NULL::text );
+$$ LANGUAGE sql;
+
+-- isa_ok( value, regtype, description )
+CREATE OR REPLACE FUNCTION isa_ok( anyelement, regtype, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    typeof regtype := pg_typeof($1);
+BEGIN
+    IF typeof = $2 THEN RETURN ok(true, $3 || ' isa ' || $2 ); END IF;
+    RETURN ok(false, $3 || ' isa ' || $2 ) || E'\n' ||
+        diag('    ' || $3 || ' isn''t a "' || $2 || '" it''s a "' || typeof || '"');
+END;
+$$ LANGUAGE plpgsql;
+
+-- isa_ok( value, regtype )
+CREATE OR REPLACE FUNCTION isa_ok( anyelement, regtype )
+RETURNS TEXT AS $$
+    SELECT isa_ok($1, $2, 'the value');
+$$ LANGUAGE sql;
+
+-- is_empty( sql, description )
+CREATE OR REPLACE FUNCTION is_empty( TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    extras  TEXT[]  := '{}';
+    res     BOOLEAN := TRUE;
+    msg     TEXT    := '';
+    rec     RECORD;
+BEGIN
+    -- Find extra records.
+    FOR rec in EXECUTE _query($1) LOOP
+        extras := extras || rec::text;
+    END LOOP;
+
+    -- What extra records do we have?
+    IF extras[1] IS NOT NULL THEN
+        res := FALSE;
+        msg := E'\n' || diag(
+            E'    Unexpected records:\n        '
+            ||  array_to_string( extras, E'\n        ' )
+        );
+    END IF;
+
+    RETURN ok(res, $2) || msg;
+END;
+$$ LANGUAGE plpgsql;
+
+-- is_empty( sql )
+CREATE OR REPLACE FUNCTION is_empty( TEXT )
+RETURNS TEXT AS $$
+    SELECT is_empty( $1, NULL );
+$$ LANGUAGE sql;
+
+-- collect_tap( tap, tap, tap )
+CREATE OR REPLACE FUNCTION collect_tap( VARIADIC text[] )
+RETURNS TEXT AS $$
+    SELECT array_to_string($1, E'\n');
+$$ LANGUAGE sql;
+
+-- collect_tap( tap[] )
+CREATE OR REPLACE FUNCTION collect_tap( VARCHAR[] )
+RETURNS TEXT AS $$
+    SELECT array_to_string($1, E'\n');
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT ok( $1, $4 ) || CASE WHEN $1 THEN '' ELSE E'\n' || diag(
+           '   error message: ' || COALESCE( quote_literal($2), 'NULL' ) ||
+       E'\n   doesn''t match: ' || COALESCE( quote_literal($3), 'NULL' )
+    ) END;
+$$ LANGUAGE sql;
+
+-- throws_like ( sql, pattern, description )
+CREATE OR REPLACE FUNCTION throws_like ( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    EXECUTE _query($1);
+    RETURN ok( FALSE, $3 ) || E'\n' || diag( '    no exception thrown' );
+EXCEPTION WHEN OTHERS THEN
+    return _tlike( SQLERRM ~~ $2, SQLERRM, $2, $3 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- throws_like ( sql, pattern )
+CREATE OR REPLACE FUNCTION throws_like ( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT throws_like($1, $2, 'Should throw exception like ' || quote_literal($2) );
+$$ LANGUAGE sql;
+
+-- throws_ilike ( sql, pattern, description )
+CREATE OR REPLACE FUNCTION throws_ilike ( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    EXECUTE _query($1);
+    RETURN ok( FALSE, $3 ) || E'\n' || diag( '    no exception thrown' );
+EXCEPTION WHEN OTHERS THEN
+    return _tlike( SQLERRM ~~* $2, SQLERRM, $2, $3 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- throws_ilike ( sql, pattern )
+CREATE OR REPLACE FUNCTION throws_ilike ( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT throws_ilike($1, $2, 'Should throw exception like ' || quote_literal($2) );
+$$ LANGUAGE sql;
+
+-- throws_matching ( sql, pattern, description )
+CREATE OR REPLACE FUNCTION throws_matching ( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    EXECUTE _query($1);
+    RETURN ok( FALSE, $3 ) || E'\n' || diag( '    no exception thrown' );
+EXCEPTION WHEN OTHERS THEN
+    return _tlike( SQLERRM ~ $2, SQLERRM, $2, $3 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- throws_matching ( sql, pattern )
+CREATE OR REPLACE FUNCTION throws_matching ( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT throws_matching($1, $2, 'Should throw exception matching ' || quote_literal($2) );
+$$ LANGUAGE sql;
+
+-- throws_imatching ( sql, pattern, description )
+CREATE OR REPLACE FUNCTION throws_imatching ( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+BEGIN
+    EXECUTE _query($1);
+    RETURN ok( FALSE, $3 ) || E'\n' || diag( '    no exception thrown' );
+EXCEPTION WHEN OTHERS THEN
+    return _tlike( SQLERRM ~* $2, SQLERRM, $2, $3 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- throws_imatching ( sql, pattern )
+CREATE OR REPLACE FUNCTION throws_imatching ( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT throws_imatching($1, $2, 'Should throw exception matching ' || quote_literal($2) );
+$$ LANGUAGE sql;
+
+-- roles_are( roles[], description )
+CREATE OR REPLACE FUNCTION roles_are( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'roles',
+        ARRAY(
+            SELECT rolname
+              FROM pg_catalog.pg_roles
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT rolname
+              FROM pg_catalog.pg_roles
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- roles_are( roles[] )
+CREATE OR REPLACE FUNCTION roles_are( NAME[] )
+RETURNS TEXT AS $$
+    SELECT roles_are( $1, 'There should be the correct roles' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'types',
+        ARRAY(
+            SELECT t.typname
+              FROM pg_catalog.pg_type t
+              LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
+             WHERE (
+                     t.typrelid = 0
+                 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)
+             )
+               AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)
+               AND n.nspname = $1
+               AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) )
+            EXCEPT
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $2[i]
+               FROM generate_series(1, array_upper($2, 1)) s(i)
+            EXCEPT
+            SELECT t.typname
+              FROM pg_catalog.pg_type t
+              LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
+             WHERE (
+                     t.typrelid = 0
+                 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)
+             )
+               AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)
+               AND n.nspname = $1
+               AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) )
+        ),
+        $3
+    );
+$$ LANGUAGE SQL;
+
+-- types_are( schema, types[], description )
+CREATE OR REPLACE FUNCTION types_are ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, $3, NULL );
+$$ LANGUAGE SQL;
+
+-- types_are( schema, types[] )
+CREATE OR REPLACE FUNCTION types_are ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct types', NULL );
+$$ LANGUAGE SQL;
+
+-- types_are( types[], description )
+CREATE OR REPLACE FUNCTION _types_are ( NAME[], TEXT, CHAR[] )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'types',
+        ARRAY(
+            SELECT t.typname
+              FROM pg_catalog.pg_type t
+              LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
+             WHERE (
+                     t.typrelid = 0
+                 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)
+             )
+               AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+               AND pg_catalog.pg_type_is_visible(t.oid)
+               AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) )
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+               FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT t.typname
+              FROM pg_catalog.pg_type t
+              LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
+             WHERE (
+                     t.typrelid = 0
+                 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)
+             )
+               AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+               AND pg_catalog.pg_type_is_visible(t.oid)
+               AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) )
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+
+-- types_are( types[], description )
+CREATE OR REPLACE FUNCTION types_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, NULL );
+$$ LANGUAGE SQL;
+
+-- types_are( types[] )
+CREATE OR REPLACE FUNCTION types_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct types', NULL );
+$$ LANGUAGE SQL;
+
+-- domains_are( schema, domains[], description )
+CREATE OR REPLACE FUNCTION domains_are ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, $3, ARRAY['d'] );
+$$ LANGUAGE SQL;
+
+-- domains_are( schema, domains[] )
+CREATE OR REPLACE FUNCTION domains_are ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct domains', ARRAY['d'] );
+$$ LANGUAGE SQL;
+
+-- domains_are( domains[], description )
+CREATE OR REPLACE FUNCTION domains_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, ARRAY['d'] );
+$$ LANGUAGE SQL;
+
+-- domains_are( domains[] )
+CREATE OR REPLACE FUNCTION domains_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct domains', ARRAY['d'] );
+$$ LANGUAGE SQL;
+
+-- enums_are( schema, enums[], description )
+CREATE OR REPLACE FUNCTION enums_are ( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, $3, ARRAY['e'] );
+$$ LANGUAGE SQL;
+
+-- enums_are( schema, enums[] )
+CREATE OR REPLACE FUNCTION enums_are ( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct enums', ARRAY['e'] );
+$$ LANGUAGE SQL;
+
+-- enums_are( enums[], description )
+CREATE OR REPLACE FUNCTION enums_are ( NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, $2, ARRAY['e'] );
+$$ LANGUAGE SQL;
+
+-- enums_are( enums[] )
+CREATE OR REPLACE FUNCTION enums_are ( NAME[] )
+RETURNS TEXT AS $$
+    SELECT _types_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct enums', ARRAY['e'] );
+$$ LANGUAGE SQL;
+
+-- _dexists( schema, domain )
+CREATE OR REPLACE FUNCTION _dexists ( NAME, NAME )
+RETURNS BOOLEAN AS $$
+   SELECT EXISTS(
+       SELECT true
+         FROM pg_catalog.pg_namespace n
+         JOIN pg_catalog.pg_type t on n.oid = t.typnamespace
+        WHERE n.nspname = $1
+          AND t.typname = $2
+   );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _dexists ( NAME )
+RETURNS BOOLEAN AS $$
+   SELECT EXISTS(
+       SELECT true
+         FROM pg_catalog.pg_type t
+        WHERE t.typname = $1
+          AND pg_catalog.pg_type_is_visible(t.oid)
+   );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _get_dtype( NAME, TEXT, BOOLEAN )
+RETURNS TEXT AS $$
+    SELECT display_type(CASE WHEN $3 THEN tn.nspname ELSE NULL END, t.oid, t.typtypmod)
+      FROM pg_catalog.pg_type d
+      JOIN pg_catalog.pg_namespace dn ON d.typnamespace = dn.oid
+      JOIN pg_catalog.pg_type t       ON d.typbasetype  = t.oid
+      JOIN pg_catalog.pg_namespace tn ON d.typnamespace = tn.oid
+     WHERE d.typisdefined
+       AND dn.nspname = $1
+       AND d.typname  = LOWER($2)
+       AND d.typtype  = 'd'
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION _get_dtype( NAME )
+RETURNS TEXT AS $$
+    SELECT display_type(t.oid, t.typtypmod)
+      FROM pg_catalog.pg_type d
+      JOIN pg_catalog.pg_type t  ON d.typbasetype  = t.oid
+     WHERE d.typisdefined
+       AND d.typname = LOWER($1)
+       AND d.typtype = 'd'
+$$ LANGUAGE sql;
+
+-- domain_type_is( schema, domain, schema, type, description )
+CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    actual_type TEXT := _get_dtype($1, $2, true);
+BEGIN
+    IF actual_type IS NULL THEN
+        RETURN fail( $5 ) || E'\n' || diag (
+            '   Domain ' || quote_ident($1) || '.' || $2
+            || ' does not exist'
+        );
+    END IF;
+
+    RETURN is( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $5 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- domain_type_is( schema, domain, schema, type )
+CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT domain_type_is(
+        $1, $2, $3, $4,
+        'Domain ' || quote_ident($1) || '.' || $2
+        || ' should extend type ' || quote_ident($3) || '.' || $4
+    );
+$$ LANGUAGE SQL;
+
+-- domain_type_is( schema, domain, type, description )
+CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    actual_type TEXT := _get_dtype($1, $2, false);
+BEGIN
+    IF actual_type IS NULL THEN
+        RETURN fail( $4 ) || E'\n' || diag (
+            '   Domain ' || quote_ident($1) || '.' || $2
+            || ' does not exist'
+        );
+    END IF;
+
+    RETURN is( actual_type, _quote_ident_like($3, actual_type), $4 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- domain_type_is( schema, domain, type )
+CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT domain_type_is(
+        $1, $2, $3,
+        'Domain ' || quote_ident($1) || '.' || $2
+        || ' should extend type ' || $3
+    );
+$$ LANGUAGE SQL;
+
+-- domain_type_is( domain, type, description )
+CREATE OR REPLACE FUNCTION domain_type_is( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    actual_type TEXT := _get_dtype($1);
+BEGIN
+    IF actual_type IS NULL THEN
+        RETURN fail( $3 ) || E'\n' || diag (
+            '   Domain ' ||  $1 || ' does not exist'
+        );
+    END IF;
+
+    RETURN is( actual_type, _quote_ident_like($2, actual_type), $3 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- domain_type_is( domain, type )
+CREATE OR REPLACE FUNCTION domain_type_is( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT domain_type_is(
+        $1, $2,
+        'Domain ' || $1 || ' should extend type ' || $2
+    );
+$$ LANGUAGE SQL;
+
+-- domain_type_isnt( schema, domain, schema, type, description )
+CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    actual_type TEXT := _get_dtype($1, $2, true);
+BEGIN
+    IF actual_type IS NULL THEN
+        RETURN fail( $5 ) || E'\n' || diag (
+            '   Domain ' || quote_ident($1) || '.' || $2
+            || ' does not exist'
+        );
+    END IF;
+
+    RETURN isnt( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $5 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- domain_type_isnt( schema, domain, schema, type )
+CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT )
+RETURNS TEXT AS $$
+    SELECT domain_type_isnt(
+        $1, $2, $3, $4,
+        'Domain ' || quote_ident($1) || '.' || $2
+        || ' should not extend type ' || quote_ident($3) || '.' || $4
+    );
+$$ LANGUAGE SQL;
+
+-- domain_type_isnt( schema, domain, type, description )
+CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    actual_type TEXT := _get_dtype($1, $2, false);
+BEGIN
+    IF actual_type IS NULL THEN
+        RETURN fail( $4 ) || E'\n' || diag (
+            '   Domain ' || quote_ident($1) || '.' || $2
+            || ' does not exist'
+        );
+    END IF;
+
+    RETURN isnt( actual_type, _quote_ident_like($3, actual_type), $4 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- domain_type_isnt( schema, domain, type )
+CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT domain_type_isnt(
+        $1, $2, $3,
+        'Domain ' || quote_ident($1) || '.' || $2
+        || ' should not extend type ' || $3
+    );
+$$ LANGUAGE SQL;
+
+-- domain_type_isnt( domain, type, description )
+CREATE OR REPLACE FUNCTION domain_type_isnt( TEXT, TEXT, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    actual_type TEXT := _get_dtype($1);
+BEGIN
+    IF actual_type IS NULL THEN
+        RETURN fail( $3 ) || E'\n' || diag (
+            '   Domain ' ||  $1 || ' does not exist'
+        );
+    END IF;
+
+    RETURN isnt( actual_type, _quote_ident_like($2, actual_type), $3 );
+END;
+$$ LANGUAGE plpgsql;
+
+-- domain_type_isnt( domain, type )
+CREATE OR REPLACE FUNCTION domain_type_isnt( TEXT, TEXT )
+RETURNS TEXT AS $$
+    SELECT domain_type_isnt(
+        $1, $2,
+        'Domain ' || $1 || ' should not extend type ' || $2
+    );
+$$ LANGUAGE SQL;
+
+-- row_eq( sql, record, description )
+CREATE OR REPLACE FUNCTION row_eq( TEXT, anyelement, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    rec    RECORD;
+BEGIN
+    EXECUTE _query($1) INTO rec;
+    IF NOT rec IS DISTINCT FROM $2 THEN RETURN ok(true, $3); END IF;
+    RETURN ok(false, $3 ) || E'\n' || diag(
+           '        have: ' || CASE WHEN rec IS NULL THEN 'NULL' ELSE rec::text END ||
+        E'\n        want: ' || CASE WHEN $2  IS NULL THEN 'NULL' ELSE $2::text  END
+    );
+END;
+$$ LANGUAGE plpgsql;
+
+-- row_eq( sql, record )
+CREATE OR REPLACE FUNCTION row_eq( TEXT, anyelement )
+RETURNS TEXT AS $$
+    SELECT row_eq($1, $2, NULL );
+$$ LANGUAGE sql;
+
+-- triggers_are( schema, table, triggers[], description )
+CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'triggers',
+        ARRAY(
+            SELECT t.tgname
+              FROM pg_catalog.pg_trigger t
+              JOIN pg_catalog.pg_class c     ON c.oid = t.tgrelid
+              JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+             WHERE n.nspname = $1
+               AND c.relname = $2
+            EXCEPT
+            SELECT $3[i]
+              FROM generate_series(1, array_upper($3, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $3[i]
+              FROM generate_series(1, array_upper($3, 1)) s(i)
+            EXCEPT
+            SELECT t.tgname
+              FROM pg_catalog.pg_trigger t
+              JOIN pg_catalog.pg_class c     ON c.oid = t.tgrelid
+              JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+             WHERE n.nspname = $1
+               AND c.relname = $2
+        ),
+        $4
+    );
+$$ LANGUAGE SQL;
+
+-- triggers_are( schema, table, triggers[] )
+CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT triggers_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct triggers' );
+$$ LANGUAGE SQL;
+
+-- triggers_are( table, triggers[], description )
+CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'triggers',
+        ARRAY(
+            SELECT t.tgname
+              FROM pg_catalog.pg_trigger t
+              JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid
+              JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+             WHERE c.relname = $1
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+            EXCEPT
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+            EXCEPT
+            SELECT t.tgname
+              FROM pg_catalog.pg_trigger t
+              JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid
+              JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+        ),
+        $3
+    );
+$$ LANGUAGE SQL;
+
+-- triggers_are( table, triggers[] )
+CREATE OR REPLACE FUNCTION triggers_are( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT triggers_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct triggers' );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION _areni ( text, text[], text[], TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    what    ALIAS FOR $1;
+    extras  ALIAS FOR $2;
+    missing ALIAS FOR $3;
+    descr   ALIAS FOR $4;
+    msg     TEXT    := '';
+    res     BOOLEAN := TRUE;
+BEGIN
+    IF extras[1] IS NOT NULL THEN
+        res = FALSE;
+        msg := E'\n' || diag(
+            '    Extra ' || what || E':\n        '
+            ||  array_to_string( extras, E'\n        ' )
+        );
+    END IF;
+    IF missing[1] IS NOT NULL THEN
+        res = FALSE;
+        msg := msg || E'\n' || diag(
+            '    Missing ' || what || E':\n        '
+            ||  array_to_string( missing, E'\n        ' )
+        );
+    END IF;
+
+    RETURN ok(res, descr) || msg;
+END;
+$$ LANGUAGE plpgsql;
+
+
+-- casts_are( casts[], description )
+CREATE OR REPLACE FUNCTION casts_are ( TEXT[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _areni(
+        'casts',
+        ARRAY(
+            SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL)
+              FROM pg_catalog.pg_cast c
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL)
+              FROM pg_catalog.pg_cast c
+        ),
+        $2
+    );
+$$ LANGUAGE sql;
+
+-- casts_are( casts[] )
+CREATE OR REPLACE FUNCTION casts_are ( TEXT[] )
+RETURNS TEXT AS $$
+    SELECT casts_are( $1, 'There should be the correct casts');
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION display_oper ( NAME, OID )
+RETURNS TEXT AS $$
+    SELECT $1 || substring($2::regoperator::text, '[(][^)]+[)]$')
+$$ LANGUAGE SQL;
+
+-- operators_are( schema, operators[], description )
+CREATE OR REPLACE FUNCTION operators_are( NAME, TEXT[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _areni(
+        'operators',
+        ARRAY(
+            SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype
+              FROM pg_catalog.pg_operator o
+              JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid
+             WHERE n.nspname = $1
+            EXCEPT
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+            EXCEPT
+            SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype
+              FROM pg_catalog.pg_operator o
+              JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid
+             WHERE n.nspname = $1
+        ),
+        $3
+    );
+$$ LANGUAGE SQL;
+
+-- operators_are( schema, operators[] )
+CREATE OR REPLACE FUNCTION operators_are ( NAME, TEXT[] )
+RETURNS TEXT AS $$
+    SELECT operators_are($1, $2, 'Schema ' || quote_ident($1) || ' should have the correct operators' );
+$$ LANGUAGE SQL;
+
+-- operators_are( operators[], description )
+CREATE OR REPLACE FUNCTION operators_are( TEXT[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _areni(
+        'operators',
+        ARRAY(
+            SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype
+              FROM pg_catalog.pg_operator o
+              JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid
+             WHERE pg_catalog.pg_operator_is_visible(o.oid)
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+            EXCEPT
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $1[i]
+              FROM generate_series(1, array_upper($1, 1)) s(i)
+            EXCEPT
+            SELECT display_oper(o.oprname, o.oid) || ' RETURNS ' || o.oprresult::regtype
+              FROM pg_catalog.pg_operator o
+              JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid
+             WHERE pg_catalog.pg_operator_is_visible(o.oid)
+               AND n.nspname NOT IN ('pg_catalog', 'information_schema')
+        ),
+        $2
+    );
+$$ LANGUAGE SQL;
+
+-- operators_are( operators[] )
+CREATE OR REPLACE FUNCTION operators_are ( TEXT[] )
+RETURNS TEXT AS $$
+    SELECT operators_are($1, 'There should be the correct operators')
+$$ LANGUAGE SQL;
+
+-- columns_are( schema, table, columns[], description )
+CREATE OR REPLACE FUNCTION columns_are( NAME, NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'columns',
+        ARRAY(
+            SELECT a.attname
+              FROM pg_catalog.pg_namespace n
+              JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+              JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+             WHERE n.nspname = $1
+               AND c.relname = $2
+               AND a.attnum > 0
+               AND NOT a.attisdropped
+            EXCEPT
+            SELECT $3[i]
+              FROM generate_series(1, array_upper($3, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $3[i]
+              FROM generate_series(1, array_upper($3, 1)) s(i)
+            EXCEPT
+            SELECT a.attname
+              FROM pg_catalog.pg_namespace n
+              JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+              JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+             WHERE n.nspname = $1
+               AND c.relname = $2
+               AND a.attnum > 0
+               AND NOT a.attisdropped
+        ),
+        $4
+    );
+$$ LANGUAGE SQL;
+
+-- columns_are( schema, table, columns[] )
+CREATE OR REPLACE FUNCTION columns_are( NAME, NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT columns_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct columns' );
+$$ LANGUAGE SQL;
+
+-- columns_are( table, columns[], description )
+CREATE OR REPLACE FUNCTION columns_are( NAME, NAME[], TEXT )
+RETURNS TEXT AS $$
+    SELECT _are(
+        'columns',
+        ARRAY(
+            SELECT a.attname
+              FROM pg_catalog.pg_namespace n
+              JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+              JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+             WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
+               AND pg_catalog.pg_table_is_visible(c.oid)
+               AND c.relname = $1
+               AND a.attnum > 0
+               AND NOT a.attisdropped
+            EXCEPT
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+        ),
+        ARRAY(
+            SELECT $2[i]
+              FROM generate_series(1, array_upper($2, 1)) s(i)
+            EXCEPT
+            SELECT a.attname
+              FROM pg_catalog.pg_namespace n
+              JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace
+              JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
+             WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
+               AND pg_catalog.pg_table_is_visible(c.oid)
+               AND c.relname = $1
+               AND a.attnum > 0
+               AND NOT a.attisdropped
+        ),
+        $3
+    );
+$$ LANGUAGE SQL;
+
+-- columns_are( table, columns[] )
+CREATE OR REPLACE FUNCTION columns_are( NAME, NAME[] )
+RETURNS TEXT AS $$
+    SELECT columns_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct columns' );
+$$ LANGUAGE SQL;
+
+-- _get_db_owner( dbname )
+CREATE OR REPLACE FUNCTION _get_db_owner( NAME )
+RETURNS NAME AS $$
+    SELECT pg_catalog.pg_get_userbyid(datdba)
+      FROM pg_catalog.pg_database
+     WHERE datname = $1;
+$$ LANGUAGE SQL;
+
+-- db_owner_is ( dbname, user, description )
+CREATE OR REPLACE FUNCTION db_owner_is ( NAME, NAME, TEXT )
+RETURNS TEXT AS $$
+DECLARE
+    dbowner NAME := _get_db_owner($1);
+BEGIN
+    -- Make sure the database exists.
+    IF dbowner IS NULL THEN
+        RETURN ok(FALSE, $3) || E'\n' || diag(
+            E'    Database ' || quote_ident($1) || ' does not exist'
+        );
+    END IF;
+
+    RETURN is(dbowner, $2, $3);
+END;
+$$ LANGUAGE plpgsql;
+
+-- db_owner_is ( dbname, user )
+CREATE OR REPLACE FUNCTION db_owner_is ( NAME, NAME )
+RETURNS TEXT AS $$
+    SELECT db_owner_is(
+        $1, $2,
+        'Database ' || quote_ident($1) || ' should be owned by ' || quote_ident($2)
+    );
+$$ LANGUAGE sql;
+
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/sys/logs.sql b/legacyworlds-server-data/db-structure/tests/user/priv/sys/logs.sql
new file mode 100644
index 0000000..e7c7948
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/sys/logs.sql
@@ -0,0 +1,39 @@
+BEGIN;
+	SELECT plan( 3 );
+
+	--
+	-- Insertion through sys.write_log()
+	--
+	CREATE OR REPLACE FUNCTION _test_this( )
+		RETURNS BIGINT
+	AS $$
+		SELECT sys.write_log( 'test' , 'WARNING'::log_level , 'test' );
+	$$ LANGUAGE SQL;
+	SELECT lives_ok( 'SELECT _test_this()' );
+	DROP FUNCTION _test_this( );
+
+	--
+	-- Direct insertion must fail
+	--
+	CREATE FUNCTION _test_this( )
+		RETURNS VOID
+	AS $$
+		INSERT INTO sys.logs( component , level , message )
+			VALUES ( 'test' , 'WARNING'::log_level , 'test' );
+	$$ LANGUAGE SQL;
+	SELECT throws_ok( 'SELECT _test_this()' , 42501 );
+	DROP FUNCTION _test_this( );
+
+	--
+	-- Updates must fail
+	--
+	CREATE OR REPLACE FUNCTION _test_this( )
+		RETURNS VOID
+	AS $$
+		UPDATE sys.logs SET component = 'random' WHERE component = 'test';
+	$$ LANGUAGE SQL;
+	SELECT throws_ok( 'SELECT _test_this()' , 42501 );
+	DROP FUNCTION _test_this( );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
diff --git a/legacyworlds-server-main/data-source.xml b/legacyworlds-server-main/data-source.sample.xml
similarity index 100%
rename from legacyworlds-server-main/data-source.xml
rename to legacyworlds-server-main/data-source.sample.xml
diff --git a/legacyworlds/doc/TODO.txt b/legacyworlds/doc/TODO.txt
index 771e724..f1fc671 100644
--- a/legacyworlds/doc/TODO.txt
+++ b/legacyworlds/doc/TODO.txt
@@ -12,19 +12,14 @@ PROJECT:
 
   
 SERVER & DATABASE:
-  
-  ! Migrate to PostgreSQL 9.1
-		-> add logging to some of the bigger stored procedures through an
-			external connection 
 
   ! Add some form of database version control to allow easier updates
-		-> once migrated to Pg9.1, there are some interesting extensions that
-			may be satisfactory
-			
+		-> existing options were investigated, they are unsatisfactory
+
   * Replace all single-precision reals with double precision reals
 
   * Add a tool to initialise the database
-  
+
   * I18N loader: improve text file loading (use relative paths)
 
   * Replace current authentication information (pair of hashes) with a
@@ -41,6 +36,6 @@ GENERAL:
 	-> that would be "everywhere"
 	
   * Write unit tests
-	  ? Check out PostgreSQL extensions to test stored procedures
-	  * Write unit tests for new code
+	  * Write unit tests for all new Java code
+	  * Write unit tests for all new SQL code
 	  ? add more tests if possible	
diff --git a/legacyworlds/doc/database.txt b/legacyworlds/doc/database.txt
new file mode 100644
index 0000000..b2765e1
--- /dev/null
+++ b/legacyworlds/doc/database.txt
@@ -0,0 +1,91 @@
+Database structure and development
+===================================
+
+The database used by the Legacy Worlds server can be found in the db-structure
+directory of the legacyworlds-server-data package.
+
+
+Database configuration
+-----------------------
+
+The configuration used by the various SQL scripts must be placed in the
+db-config.txt file, at the root of the directory. This file is ignored by Git,
+which allows each developer to set up her or his own copy of the database
+without hassle. A sample is included in the repository in the
+db-config.sample.txt file.
+
+The configuration file contains a few fields, which are defined using a simple
+"<key>=<value>" syntax (warning: there should be no spaces before or after the
+'=' sign).
+
+The following fields must be defined:
+
+* admin - the name of the administrative user,
+* db - the name of the database,
+* user - the name of the user through which the server connects to the
+database,
+* password - the password used by the server to authenticate when accessing
+the database.
+
+
+Code structure
+---------------
+
+The root directory includes a "database.sql" script, which can be launched in
+psql to create the database and associated user.
+
+The parts/ sub-directory contains the various elements of the database's
+definition. It contains a few scripts, which will initialise the schemas and
+load other scripts from the sub-directories.
+
+ * parts/data/ contains all data structure definitions (types, tables,
+   indexes, constraints and some of the views).
+ * parts/functions/ contains all general function definitions; this includes
+   both functions that are called internally and functions which are called
+   by the server. It also includes view definitions that depend on functions.
+ * parts/updates/ contains all functions that implement the game's updates.
+
+ The tests/ sub-directory contains the SQL source code for the pgTAP testing
+ framework as well as the tests themselves. See below for more information.
+ 
+ 
+ Unit tests
+ ----------
+ 
+ There may be up to two sub-directories in the tests/ directory. The user/
+ sub-directory would contain unit tests that must be executed as the standard
+ user, while the admin/ directory would contain tests that required
+ administrative permissions on the database.
+ 
+ In order to run the database unit tests, the following steps must be taken:
+ 
+ 1) pg_prove must be installed. This can be achieved by running the following
+    command as root:
+ 
+ 		 cpan TAP::Parser::SourceHandler::pgTAP
+
+2) It must be possible to log on to the database through the local socket as
+   both the administrative and the standard user, without password.
+
+3) The database itself must be loaded using the aforementioned database.sql
+   script.
+
+4) The tests/pgtap.sql script must be loaded into the database as the
+   administrative user.
+
+At this point, it becomes possible to launch the test suites by issuing a
+command similar to:
+
+		pg_prove -d $DATABASE -U $USER `find $DIR/ -type f -name '*.sql'`
+
+where $DATABASE is the name of the database, $USER the name of the user that
+will execute the tests and $DIR being either admin or user.
+
+
+Build system
+-------------
+
+The build system will attempt to create the database using the scripts. It will
+stop at the first unsuccessful command. On success, it will proceed to loading
+pgTAP, then run all available unit tests. A failure will cause the build to be
+aborted. 
\ No newline at end of file

From 631f49fb863fb31ebe556e26fc33f54c7f2fdd2b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 17 Dec 2011 12:37:01 +0100
Subject: [PATCH 05/94] Command line tools

* Added base classes for all importable data. These new classes should
be used for all future loaders; all existing loaders that are modified
should be updated.

* I18N loader rewritten to make use of the new base classes. External
strings are now read using the XML data file's path as the base
directory.

* Updated all external I18N definitions and moved the existing files
around in an attempt to make the data directory somewhat more livable.

* Added dependency management entry for the server's main package to the
root project, updated server distribution package accordingly. Added
dependency on the server's main package to the server's testing package.
---
 legacyworlds-server-DIST/pom.xml              |   1 -
 legacyworlds-server-DIST/src/server.xml       |   4 +-
 legacyworlds-server-main/data/i18n-text.xml   |  58 ++--
 .../data/{ => i18n}/adminErrorMail.txt        |   0
 .../data/{ => i18n}/adminRecapMail.txt        |   0
 .../en/addressChangeMail.txt}                 |   0
 .../en/banLiftedMail.txt}                     |   0
 .../en/bannedMail.txt}                        |   0
 .../en/inactivityQuitMail.txt}                |   0
 .../en/inactivityWarningMail.txt}             |   0
 .../en/messageMail.txt}                       |   0
 .../en/passwordRecoveryMail.txt}              |   0
 .../{quitMail-en.txt => i18n/en/quitMail.txt} |   0
 .../en/reactivationMail.txt}                  |   0
 .../en/recapMail.txt}                         |   0
 .../en/registrationMail.txt}                  |   0
 .../fr/addressChangeMail.txt}                 |   0
 .../fr/banLiftedMail.txt}                     |   0
 .../fr/bannedMail.txt}                        |   0
 .../fr/inactivityQuitMail.txt}                |   0
 .../fr/inactivityWarningMail.txt}             |   0
 .../fr/messageMail.txt}                       |   0
 .../fr/passwordRecoveryMail.txt}              |   0
 .../{quitMail-fr.txt => i18n/fr/quitMail.txt} |   0
 .../fr/reactivationMail.txt}                  |   0
 .../fr/recapMail.txt}                         |   0
 .../fr/registrationMail.txt}                  |   0
 .../java/com/deepclone/lw/cli/ImportText.java | 314 +++++++++---------
 .../lw/cli/xmlimport/I18NLoader.java          | 121 +++++++
 .../xmlimport/data/DataImportException.java   |  43 +++
 .../lw/cli/xmlimport/data/ImportableData.java |  66 ++++
 .../cli/xmlimport/data/i18n/FileString.java   | 104 ++++++
 .../lw/cli/xmlimport/data/i18n/I18NText.java  | 120 +++++++
 .../cli/xmlimport/data/i18n/InlineString.java |  55 +++
 .../data/i18n/LanguageDefinition.java         | 145 ++++++++
 .../xmlimport/data/i18n/StringDefinition.java |  73 ++++
 .../src/main/resources/log4j.properties       |   1 +
 .../TestFiles/i18n-external-file/test.txt     |   1 +
 .../TestFiles/i18n-loader/bad-contents.xml    |   6 +
 .../TestFiles/i18n-loader/bad-data.xml        |  12 +
 .../TestFiles/i18n-loader/bad-xml.xml         |   2 +
 .../TestFiles/i18n-loader/good.xml            |  12 +
 .../TestFiles/i18n-loader/test.txt            |   1 +
 legacyworlds-server-tests/pom.xml             |   4 +
 .../src/main/java/.empty                      |   0
 .../src/main/resources/.empty                 |   0
 .../src/test/java/.empty                      |   0
 .../lw/cli/xmlimport/TestI18NLoader.java      | 152 +++++++++
 .../xmlimport/data/TestImportableData.java    |  73 ++++
 .../lw/cli/xmlimport/data/i18n/BaseTest.java  | 205 ++++++++++++
 .../data/i18n/TestExternalString.java         | 307 +++++++++++++++++
 .../cli/xmlimport/data/i18n/TestI18NText.java | 134 ++++++++
 .../xmlimport/data/i18n/TestInlineString.java | 210 ++++++++++++
 .../data/i18n/TestLanguageDefinition.java     | 264 +++++++++++++++
 .../src/test/resources/.empty                 |   0
 legacyworlds/doc/TODO.txt                     |   2 -
 legacyworlds/pom.xml                          |   5 +
 57 files changed, 2295 insertions(+), 200 deletions(-)
 rename legacyworlds-server-main/data/{ => i18n}/adminErrorMail.txt (100%)
 rename legacyworlds-server-main/data/{ => i18n}/adminRecapMail.txt (100%)
 rename legacyworlds-server-main/data/{addressChangeMail-en.txt => i18n/en/addressChangeMail.txt} (100%)
 rename legacyworlds-server-main/data/{banLiftedMail-en.txt => i18n/en/banLiftedMail.txt} (100%)
 rename legacyworlds-server-main/data/{bannedMail-en.txt => i18n/en/bannedMail.txt} (100%)
 rename legacyworlds-server-main/data/{inactivityQuitMail-en.txt => i18n/en/inactivityQuitMail.txt} (100%)
 rename legacyworlds-server-main/data/{inactivityWarningMail-en.txt => i18n/en/inactivityWarningMail.txt} (100%)
 rename legacyworlds-server-main/data/{messageMail-en.txt => i18n/en/messageMail.txt} (100%)
 rename legacyworlds-server-main/data/{passwordRecoveryMail-en.txt => i18n/en/passwordRecoveryMail.txt} (100%)
 rename legacyworlds-server-main/data/{quitMail-en.txt => i18n/en/quitMail.txt} (100%)
 rename legacyworlds-server-main/data/{reactivationMail-en.txt => i18n/en/reactivationMail.txt} (100%)
 rename legacyworlds-server-main/data/{recapMail-en.txt => i18n/en/recapMail.txt} (100%)
 rename legacyworlds-server-main/data/{registrationMail-en.txt => i18n/en/registrationMail.txt} (100%)
 rename legacyworlds-server-main/data/{addressChangeMail-fr.txt => i18n/fr/addressChangeMail.txt} (100%)
 rename legacyworlds-server-main/data/{banLiftedMail-fr.txt => i18n/fr/banLiftedMail.txt} (100%)
 rename legacyworlds-server-main/data/{bannedMail-fr.txt => i18n/fr/bannedMail.txt} (100%)
 rename legacyworlds-server-main/data/{inactivityQuitMail-fr.txt => i18n/fr/inactivityQuitMail.txt} (100%)
 rename legacyworlds-server-main/data/{inactivityWarningMail-fr.txt => i18n/fr/inactivityWarningMail.txt} (100%)
 rename legacyworlds-server-main/data/{messageMail-fr.txt => i18n/fr/messageMail.txt} (100%)
 rename legacyworlds-server-main/data/{passwordRecoveryMail-fr.txt => i18n/fr/passwordRecoveryMail.txt} (100%)
 rename legacyworlds-server-main/data/{quitMail-fr.txt => i18n/fr/quitMail.txt} (100%)
 rename legacyworlds-server-main/data/{reactivationMail-fr.txt => i18n/fr/reactivationMail.txt} (100%)
 rename legacyworlds-server-main/data/{recapMail-fr.txt => i18n/fr/recapMail.txt} (100%)
 rename legacyworlds-server-main/data/{registrationMail-fr.txt => i18n/fr/registrationMail.txt} (100%)
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/I18NLoader.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/DataImportException.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/ImportableData.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/FileString.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/I18NText.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/InlineString.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/LanguageDefinition.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/StringDefinition.java
 create mode 100644 legacyworlds-server-tests/TestFiles/i18n-external-file/test.txt
 create mode 100644 legacyworlds-server-tests/TestFiles/i18n-loader/bad-contents.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/i18n-loader/bad-data.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/i18n-loader/bad-xml.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/i18n-loader/good.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/i18n-loader/test.txt
 create mode 100644 legacyworlds-server-tests/src/main/java/.empty
 create mode 100644 legacyworlds-server-tests/src/main/resources/.empty
 create mode 100644 legacyworlds-server-tests/src/test/java/.empty
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestI18NLoader.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/TestImportableData.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/BaseTest.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestExternalString.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestI18NText.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestInlineString.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestLanguageDefinition.java
 create mode 100644 legacyworlds-server-tests/src/test/resources/.empty

diff --git a/legacyworlds-server-DIST/pom.xml b/legacyworlds-server-DIST/pom.xml
index a2fc403..fc85884 100644
--- a/legacyworlds-server-DIST/pom.xml
+++ b/legacyworlds-server-DIST/pom.xml
@@ -21,7 +21,6 @@
 		<dependency>
 			<groupId>com.deepclone.lw</groupId>
 			<artifactId>legacyworlds-server-main</artifactId>
-			<version>${project.version}</version>
 		</dependency>
 	</dependencies>
 
diff --git a/legacyworlds-server-DIST/src/server.xml b/legacyworlds-server-DIST/src/server.xml
index 20a5255..875f9b7 100644
--- a/legacyworlds-server-DIST/src/server.xml
+++ b/legacyworlds-server-DIST/src/server.xml
@@ -56,8 +56,8 @@
 			<directory>../legacyworlds-server-main/data</directory>
 			<outputDirectory>data</outputDirectory>
 			<includes>
-				<include>*.txt</include>
-				<include>*.xml</include>
+				<include>i18n/**</include>
+				<include>**.xml</include>
 			</includes>
 		</fileSet>
 
diff --git a/legacyworlds-server-main/data/i18n-text.xml b/legacyworlds-server-main/data/i18n-text.xml
index c2ef4e9..b148a45 100644
--- a/legacyworlds-server-main/data/i18n-text.xml
+++ b/legacyworlds-server-main/data/i18n-text.xml
@@ -1,23 +1,22 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text"
-xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text
-i18n-text.xsd">
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text i18n-text.xsd">
 
 	<language id="en" name="English">
-		<from-file id="registrationMail" source="data/registrationMail-en.txt" />
-		<from-file id="passwordRecoveryMail" source="data/passwordRecoveryMail-en.txt" />
-		<from-file id="reactivationMail" source="data/reactivationMail-en.txt" />
-		<from-file id="addressChangeMail" source="data/addressChangeMail-en.txt" />
-		<from-file id="adminRecapMail" source="data/adminRecapMail.txt" />
-		<from-file id="messageMail" source="data/messageMail-en.txt" />
-		<from-file id="recapMail" source="data/recapMail-en.txt" />
-		<from-file id="quitMail" source="data/quitMail-en.txt" />
-		<from-file id="bannedMail" source="data/bannedMail-en.txt" />
-		<from-file id="banLiftedMail" source="data/banLiftedMail-en.txt" />
-		<from-file id="adminErrorMail" source="data/adminErrorMail.txt" />
-		<from-file id="inactivityWarningMail" source="data/inactivityWarningMail-en.txt" />
-		<from-file id="inactivityQuitMail" source="data/inactivityQuitMail-en.txt" />
+		<from-file id="registrationMail" source="i18n/en/registrationMail.txt" />
+		<from-file id="passwordRecoveryMail" source="i18n/en/passwordRecoveryMail.txt" />
+		<from-file id="reactivationMail" source="i18n/en/reactivationMail.txt" />
+		<from-file id="addressChangeMail" source="i18n/en/addressChangeMail.txt" />
+		<from-file id="adminRecapMail" source="i18n/adminRecapMail.txt" />
+		<from-file id="messageMail" source="i18n/en/messageMail.txt" />
+		<from-file id="recapMail" source="i18n/en/recapMail.txt" />
+		<from-file id="quitMail" source="i18n/en/quitMail.txt" />
+		<from-file id="bannedMail" source="i18n/en/bannedMail.txt" />
+		<from-file id="banLiftedMail" source="i18n/en/banLiftedMail.txt" />
+		<from-file id="adminErrorMail" source="i18n/adminErrorMail.txt" />
+		<from-file id="inactivityWarningMail" source="i18n/en/inactivityWarningMail.txt" />
+		<from-file id="inactivityQuitMail" source="i18n/en/inactivityQuitMail.txt" />
 		
 		<inline-string id="instantNotification">
 			<value>
@@ -518,19 +517,20 @@ It was disbanded.</value>
 	</language>
 
 	<language id="fr" name="Français">
-		<from-file id="registrationMail" source="data/registrationMail-fr.txt" />
-		<from-file id="passwordRecoveryMail" source="data/passwordRecoveryMail-fr.txt" />
-		<from-file id="reactivationMail" source="data/reactivationMail-fr.txt" />
-		<from-file id="addressChangeMail" source="data/addressChangeMail-fr.txt" />
-		<from-file id="adminRecapMail" source="data/adminRecapMail.txt" />
-		<from-file id="messageMail" source="data/messageMail-fr.txt" />
-		<from-file id="recapMail" source="data/recapMail-fr.txt" />
-		<from-file id="quitMail" source="data/quitMail-fr.txt" />
-		<from-file id="bannedMail" source="data/bannedMail-fr.txt" />
-		<from-file id="banLiftedMail" source="data/banLiftedMail-fr.txt" />
-		<from-file id="adminErrorMail" source="data/adminErrorMail.txt" />
-		<from-file id="inactivityWarningMail" source="data/inactivityWarningMail-fr.txt" />
-		<from-file id="inactivityQuitMail" source="data/inactivityQuitMail-fr.txt" />
+		<from-file id="registrationMail" source="i18n/fr/registrationMail.txt" />
+		<from-file id="passwordRecoveryMail" source="i18n/fr/passwordRecoveryMail.txt" />
+		<from-file id="reactivationMail" source="i18n/fr/reactivationMail.txt" />
+		<from-file id="addressChangeMail" source="i18n/fr/addressChangeMail.txt" />
+		<from-file id="messageMail" source="i18n/fr/messageMail.txt" />
+		<from-file id="recapMail" source="i18n/fr/recapMail.txt" />
+		<from-file id="quitMail" source="i18n/fr/quitMail.txt" />
+		<from-file id="bannedMail" source="i18n/fr/bannedMail.txt" />
+		<from-file id="banLiftedMail" source="i18n/fr/banLiftedMail.txt" />
+		<from-file id="inactivityWarningMail" source="i18n/fr/inactivityWarningMail.txt" />
+		<from-file id="inactivityQuitMail" source="i18n/fr/inactivityQuitMail.txt" />
+
+		<from-file id="adminRecapMail" source="i18n/adminRecapMail.txt" />
+		<from-file id="adminErrorMail" source="i18n/adminErrorMail.txt" />
 
 		<inline-string id="instantNotification">
 			<value>
diff --git a/legacyworlds-server-main/data/adminErrorMail.txt b/legacyworlds-server-main/data/i18n/adminErrorMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/adminErrorMail.txt
rename to legacyworlds-server-main/data/i18n/adminErrorMail.txt
diff --git a/legacyworlds-server-main/data/adminRecapMail.txt b/legacyworlds-server-main/data/i18n/adminRecapMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/adminRecapMail.txt
rename to legacyworlds-server-main/data/i18n/adminRecapMail.txt
diff --git a/legacyworlds-server-main/data/addressChangeMail-en.txt b/legacyworlds-server-main/data/i18n/en/addressChangeMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/addressChangeMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/addressChangeMail.txt
diff --git a/legacyworlds-server-main/data/banLiftedMail-en.txt b/legacyworlds-server-main/data/i18n/en/banLiftedMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/banLiftedMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/banLiftedMail.txt
diff --git a/legacyworlds-server-main/data/bannedMail-en.txt b/legacyworlds-server-main/data/i18n/en/bannedMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/bannedMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/bannedMail.txt
diff --git a/legacyworlds-server-main/data/inactivityQuitMail-en.txt b/legacyworlds-server-main/data/i18n/en/inactivityQuitMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/inactivityQuitMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/inactivityQuitMail.txt
diff --git a/legacyworlds-server-main/data/inactivityWarningMail-en.txt b/legacyworlds-server-main/data/i18n/en/inactivityWarningMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/inactivityWarningMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/inactivityWarningMail.txt
diff --git a/legacyworlds-server-main/data/messageMail-en.txt b/legacyworlds-server-main/data/i18n/en/messageMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/messageMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/messageMail.txt
diff --git a/legacyworlds-server-main/data/passwordRecoveryMail-en.txt b/legacyworlds-server-main/data/i18n/en/passwordRecoveryMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/passwordRecoveryMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/passwordRecoveryMail.txt
diff --git a/legacyworlds-server-main/data/quitMail-en.txt b/legacyworlds-server-main/data/i18n/en/quitMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/quitMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/quitMail.txt
diff --git a/legacyworlds-server-main/data/reactivationMail-en.txt b/legacyworlds-server-main/data/i18n/en/reactivationMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/reactivationMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/reactivationMail.txt
diff --git a/legacyworlds-server-main/data/recapMail-en.txt b/legacyworlds-server-main/data/i18n/en/recapMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/recapMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/recapMail.txt
diff --git a/legacyworlds-server-main/data/registrationMail-en.txt b/legacyworlds-server-main/data/i18n/en/registrationMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/registrationMail-en.txt
rename to legacyworlds-server-main/data/i18n/en/registrationMail.txt
diff --git a/legacyworlds-server-main/data/addressChangeMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/addressChangeMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/addressChangeMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/addressChangeMail.txt
diff --git a/legacyworlds-server-main/data/banLiftedMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/banLiftedMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/banLiftedMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/banLiftedMail.txt
diff --git a/legacyworlds-server-main/data/bannedMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/bannedMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/bannedMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/bannedMail.txt
diff --git a/legacyworlds-server-main/data/inactivityQuitMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/inactivityQuitMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/inactivityQuitMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/inactivityQuitMail.txt
diff --git a/legacyworlds-server-main/data/inactivityWarningMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/inactivityWarningMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/inactivityWarningMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/inactivityWarningMail.txt
diff --git a/legacyworlds-server-main/data/messageMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/messageMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/messageMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/messageMail.txt
diff --git a/legacyworlds-server-main/data/passwordRecoveryMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/passwordRecoveryMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/passwordRecoveryMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/passwordRecoveryMail.txt
diff --git a/legacyworlds-server-main/data/quitMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/quitMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/quitMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/quitMail.txt
diff --git a/legacyworlds-server-main/data/reactivationMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/reactivationMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/reactivationMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/reactivationMail.txt
diff --git a/legacyworlds-server-main/data/recapMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/recapMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/recapMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/recapMail.txt
diff --git a/legacyworlds-server-main/data/registrationMail-fr.txt b/legacyworlds-server-main/data/i18n/fr/registrationMail.txt
similarity index 100%
rename from legacyworlds-server-main/data/registrationMail-fr.txt
rename to legacyworlds-server-main/data/i18n/fr/registrationMail.txt
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
index c3952ed..8d6e856 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
@@ -1,9 +1,7 @@
 package com.deepclone.lw.cli;
 
 
-import java.io.*;
-import java.util.LinkedList;
-import java.util.List;
+import java.io.File;
 
 import javax.sql.DataSource;
 
@@ -12,230 +10,186 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.AbstractApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 import org.springframework.context.support.FileSystemXmlApplicationContext;
-import org.springframework.jdbc.core.SqlParameter;
-import org.springframework.jdbc.core.simple.SimpleJdbcCall;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallback;
 import org.springframework.transaction.support.TransactionTemplate;
 
-import com.thoughtworks.xstream.XStream;
-import com.thoughtworks.xstream.annotations.XStreamAlias;
-import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
-import com.thoughtworks.xstream.annotations.XStreamImplicit;
+import com.deepclone.lw.cli.xmlimport.I18NLoader;
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.i18n.I18NText;
+import com.deepclone.lw.cli.xmlimport.data.i18n.LanguageDefinition;
+import com.deepclone.lw.cli.xmlimport.data.i18n.StringDefinition;
+import com.deepclone.lw.utils.StoredProc;
 
 
 
+/**
+ * I18N text import tool
+ * 
+ * <p>
+ * This class defines the body of the I18N text import tool. It loads the data file specified on the
+ * command line, validates it, then proceeds to loading the languages and strings to the database.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
 public class ImportText
 		extends CLITool
 {
 
+	/** Logging system */
 	private final Logger logger = Logger.getLogger( ImportText.class );
 
-	@SuppressWarnings( "serial" )
-	public abstract static class StringData
-			implements Serializable
-	{
-		@XStreamAsAttribute
-		public String id;
-
-
-		public abstract String getString( );
-	}
-
-	@SuppressWarnings( "serial" )
-	@XStreamAlias( "inline-string" )
-	public static class InlineString
-			extends StringData
-	{
-		public String value;
-
-
-		@Override
-		public String getString( )
-		{
-			return this.value;
-		}
-	}
-
-	@SuppressWarnings( "serial" )
-	@XStreamAlias( "from-file" )
-	public static class FileString
-			extends StringData
-	{
-		@XStreamAsAttribute
-		public String source;
-
-
-		@Override
-		public String getString( )
-		{
-			StringBuilder sBuilder = new StringBuilder( );
-			try {
-				BufferedReader in = new BufferedReader( new FileReader( source ) );
-				String str;
-				while ( ( str = in.readLine( ) ) != null ) {
-					sBuilder.append( str );
-					sBuilder.append( "\n" );
-				}
-				in.close( );
-			} catch ( IOException e ) {
-				throw new RuntimeException( "Could not read " + source );
-			}
-
-			return sBuilder.toString( );
-		}
-	}
-
-	@SuppressWarnings( "serial" )
-	public static class LanguageData
-			implements Serializable
-	{
-		@XStreamAsAttribute
-		public String id;
-
-		@XStreamAsAttribute
-		public String name;
-
-		@XStreamImplicit
-		public List< StringData > strings = new LinkedList< StringData >( );
-	}
-
-	@SuppressWarnings( "serial" )
-	@XStreamAlias( "lw-text-data" )
-	public static class TextData
-			implements Serializable
-	{
-		@XStreamImplicit( itemFieldName = "language" )
-		public List< LanguageData > languages = new LinkedList< LanguageData >( );
-	}
-
+	/** File to read the definitions from */
 	private File file;
+
+	/** Spring transaction template */
 	private TransactionTemplate tTemplate;
-	private SimpleJdbcCall uocTranslation;
-	private SimpleJdbcCall uocLanguage;
-
-
-	private XStream initXStream( )
-	{
-		XStream xstream = new XStream( );
-		xstream.processAnnotations( TextData.class );
-		xstream.processAnnotations( InlineString.class );
-		xstream.processAnnotations( FileString.class );
-		return xstream;
-	}
-
-
-	private TextData loadData( )
-	{
-		FileInputStream fis;
-		try {
-			fis = new FileInputStream( this.file );
-		} catch ( FileNotFoundException e ) {
-			return null;
-		}
-
-		try {
-			XStream xstream = this.initXStream( );
-			return (TextData) xstream.fromXML( fis );
-		} catch ( Exception e ) {
-			e.printStackTrace( );
-			return null;
-		} finally {
-			try {
-				fis.close( );
-			} catch ( IOException e ) {
-				// EMPTY
-			}
-		}
-	}
+
+	/** Language creation or update stored procedure */
+	private StoredProc uocLanguage;
+	/** Translation creation or update stored procedure */
+	private StoredProc uocTranslation;
 
 
+	/**
+	 * Create the Spring context
+	 * 
+	 * <p>
+	 * Load the definition of the data source as a Spring context, then adds the transaction
+	 * management component.
+	 * 
+	 * @return the Spring application context
+	 */
 	private ClassPathXmlApplicationContext createContext( )
 	{
-		// Load data source and Hibernate properties
-		String[] dataConfig = {
-			this.getDataSource( ) ,
-		};
-		FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( dataConfig );
+		FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( new String[] {
+			this.getDataSource( )
+		} );
 		ctx.refresh( );
 
-		// Load transaction manager bean
-		String[] cfg = {
+		return new ClassPathXmlApplicationContext( new String[] {
 			"configuration/transaction-bean.xml"
-		};
-		return new ClassPathXmlApplicationContext( cfg , true , ctx );
+		} , true , ctx );
 	}
 
 
+	/**
+	 * Create database access templates
+	 * 
+	 * <p>
+	 * Initialise the transaction template as well as both stored procedure definitions.
+	 * 
+	 * @param ctx
+	 *            the Spring application context
+	 */
 	private void createTemplates( ApplicationContext ctx )
 	{
 		DataSource dSource = ctx.getBean( DataSource.class );
 		PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
 
-		this.uocLanguage = new SimpleJdbcCall( dSource ).withCatalogName( "defs" ).withProcedureName( "uoc_language" );
-		this.uocLanguage.withoutProcedureColumnMetaDataAccess( );
-		this.uocLanguage.declareParameters( new SqlParameter( "lid" , java.sql.Types.VARCHAR ) , new SqlParameter(
-				"lname" , java.sql.Types.VARCHAR ) );
+		this.uocLanguage = new StoredProc( dSource , "defs" , "uoc_language" ).addParameter( "lid" ,
+				java.sql.Types.VARCHAR ).addParameter( "lname" , java.sql.Types.VARCHAR );
 
-		this.uocTranslation = new SimpleJdbcCall( dSource ).withCatalogName( "defs" ).withProcedureName(
-				"uoc_translation" );
-		this.uocTranslation.withoutProcedureColumnMetaDataAccess( );
-		this.uocTranslation.declareParameters( new SqlParameter( "lid" , java.sql.Types.VARCHAR ) , new SqlParameter(
-				"sid" , java.sql.Types.VARCHAR ) , new SqlParameter( "trans" , java.sql.Types.VARCHAR ) );
+		this.uocTranslation = new StoredProc( dSource , "defs" , "uoc_translation" )
+				.addParameter( "lid" , java.sql.Types.VARCHAR ).addParameter( "sid" , java.sql.Types.VARCHAR )
+				.addParameter( "trans" , java.sql.Types.VARCHAR );
 
 		this.tTemplate = new TransactionTemplate( tManager );
 	}
 
 
-	private void importText( TextData data )
+	/**
+	 * Import all languages and strings
+	 * 
+	 * <p>
+	 * Import all language and string definitions from the top-level I18N data instance.
+	 * 
+	 * @param data
+	 *            the top-level I18N data instance
+	 * 
+	 * @throws DataImportException
+	 *             when some external string definition fails to load
+	 */
+	private void importText( I18NText data )
+			throws DataImportException
 	{
-		for ( LanguageData ld : data.languages ) {
+		for ( LanguageDefinition ld : data ) {
 			this.importLanguage( ld );
 		}
 	}
 
 
-	private void importLanguage( LanguageData ld )
+	/**
+	 * Import a language definition and the translations it contains
+	 * 
+	 * <p>
+	 * Import the language's definition, then iterate over all string definitions and import them as
+	 * well.
+	 * 
+	 * @param ld
+	 *            the language's definition data
+	 * 
+	 * @throws DataImportException
+	 *             when some external string definition fails to load
+	 */
+	private void importLanguage( LanguageDefinition ld )
+			throws DataImportException
 	{
-		if ( ld.strings == null ) {
-			return;
-		}
-
 		// Try creating or updating the language
-		this.uocLanguage.execute( ld.id , ld.name );
+		this.uocLanguage.execute( ld.getId( ) , ld.getName( ) );
 
 		// Import translations
-		for ( StringData sd : ld.strings ) {
-			this.uocTranslation.execute( ld.id , sd.id , sd.getString( ) );
+		for ( StringDefinition sd : ld ) {
+			this.uocTranslation.execute( ld.getId( ) , sd.getId( ) , sd.getString( ) );
 		}
 	}
 
 
+	/**
+	 * Run the I18N definitions import tool
+	 * 
+	 * <p>
+	 * Load the data file, then connects to the database and create or update all definitions.
+	 */
 	@Override
 	public void run( )
 	{
-		final TextData data = this.loadData( );
-		if ( data == null ) {
-			System.err.println( "could not read data" );
+		I18NText data;
+		try {
+			data = new I18NLoader( this.file ).load( );
+		} catch ( DataImportException e ) {
+			System.err.println( "Error while loading '" + this.file + "': " + e.getMessage( ) );
+			this.logger.error( "Error while loading I18N data" , e );
 			return;
 		}
 
 		AbstractApplicationContext ctx = this.createContext( );
 		this.createTemplates( ctx );
+		this.executeImportTransaction( data );
+		ToolBase.destroyContext( ctx );
+	}
+
+
+	/**
+	 * Execute the I18N definitions importation transaction
+	 * 
+	 * <p>
+	 * Run a transaction and execute the importation code inside it. Roll back if anything goes
+	 * awry.
+	 * 
+	 * @param data
+	 *            the I18N definitions instance
+	 */
+	private void executeImportTransaction( final I18NText data )
+	{
 		boolean rv = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
 
 			@Override
 			public Boolean doInTransaction( TransactionStatus status )
 			{
-				boolean rv;
-				try {
-					importText( data );
-					rv = true;
-				} catch ( RuntimeException e ) {
-					logger.error( "Caught runtime exception" , e );
-					rv = false;
-				}
+				boolean rv = ImportText.this.doTransaction( data );
 				if ( !rv ) {
 					status.setRollbackOnly( );
 				}
@@ -243,15 +197,43 @@ public class ImportText
 			}
 
 		} );
-
 		if ( rv ) {
 			this.logger.info( "Text import successful" );
 		}
-
-		ToolBase.destroyContext( ctx );
 	}
 
 
+	/**
+	 * Import transaction body
+	 * 
+	 * <p>
+	 * Import all definitions and handle exceptions.
+	 * 
+	 * @param data
+	 *            the I18N definitions instance
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> otherwise
+	 */
+	private boolean doTransaction( I18NText data )
+	{
+		try {
+			ImportText.this.importText( data );
+			return true;
+		} catch ( RuntimeException e ) {
+			ImportText.this.logger.error( "Caught runtime exception" , e );
+		} catch ( DataImportException e ) {
+			ImportText.this.logger.error( "Error while importing external strings" , e );
+		}
+		return false;
+	}
+
+
+	/**
+	 * Obtain the name of the definitions file
+	 * 
+	 * <p>
+	 * Check the command line options, setting the definitions file accordingly.
+	 */
 	@Override
 	public boolean setOptions( String... options )
 	{
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/I18NLoader.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/I18NLoader.java
new file mode 100644
index 0000000..2932549
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/I18NLoader.java
@@ -0,0 +1,121 @@
+package com.deepclone.lw.cli.xmlimport;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.i18n.FileString;
+import com.deepclone.lw.cli.xmlimport.data.i18n.I18NText;
+import com.deepclone.lw.cli.xmlimport.data.i18n.InlineString;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.XStreamException;
+
+
+
+/**
+ * I18N text definitions loader
+ * 
+ * <p>
+ * This class can be used to load all I18N definitions. It will extract them from the XML file,
+ * verify them and set their origin.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class I18NLoader
+{
+	/** The file to read the XML from */
+	private final File file;
+
+
+	/**
+	 * Initialise the loader
+	 * 
+	 * @param file
+	 *            the XML file that contains the definitions
+	 */
+	public I18NLoader( File file )
+	{
+		this.file = file.getAbsoluteFile( );
+	}
+
+
+	/**
+	 * Initialise the necessary XStream instance
+	 * 
+	 * <p>
+	 * Initialise the XStream instance by processing annotations in all I18N importable data
+	 * classes.
+	 * 
+	 * @return the XStream instance to use when loading the data
+	 */
+	private XStream initXStream( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( I18NText.class );
+		xstream.processAnnotations( InlineString.class );
+		xstream.processAnnotations( FileString.class );
+		return xstream;
+	}
+
+
+	/**
+	 * Load the I18N definitions
+	 * 
+	 * <p>
+	 * Load the XML file and process the definitions file using XStream.
+	 * 
+	 * @return the top-level importable data instance
+	 * 
+	 * @throws DataImportException
+	 *             if reading from the file or parsing its contents fail
+	 */
+	private I18NText loadXMLFile( )
+			throws DataImportException
+	{
+		FileInputStream fis;
+		try {
+			fis = new FileInputStream( this.file );
+		} catch ( FileNotFoundException e ) {
+			throw new DataImportException( "Unable to load I18N definitions" , e );
+		}
+
+		try {
+			try {
+				XStream xstream = this.initXStream( );
+				return (I18NText) xstream.fromXML( fis );
+			} finally {
+				fis.close( );
+			}
+		} catch ( IOException e ) {
+			throw new DataImportException( "Input error while loading I18N definitions" , e );
+		} catch ( XStreamException e ) {
+			throw new DataImportException( "XML error while loading I18N definitions" , e );
+		}
+	}
+
+
+	/**
+	 * Load and process I18N definition
+	 * 
+	 * <p>
+	 * Attempt to load all I18N definitions, make sure they are valid, then set the original file's
+	 * path.
+	 * 
+	 * @return the top-level importable data instance
+	 * 
+	 * @throws DataImportException
+	 *             if loading or verifying the data fails
+	 */
+	public I18NText load( )
+			throws DataImportException
+	{
+		I18NText text = this.loadXMLFile( );
+		text.verifyData( );
+		text.setReadFrom( this.file );
+		return text;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/DataImportException.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/DataImportException.java
new file mode 100644
index 0000000..7c70c71
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/DataImportException.java
@@ -0,0 +1,43 @@
+package com.deepclone.lw.cli.xmlimport.data;
+
+
+/**
+ * Data import exception
+ * 
+ * <p>
+ * This exception is thrown by importable data classes when some error occurs while loading, or when
+ * verification fails.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+public class DataImportException
+		extends Exception
+{
+
+	/**
+	 * Initialise a data import exception using an error message
+	 * 
+	 * @param message
+	 *            the error message
+	 */
+	public DataImportException( String message )
+	{
+		super( message );
+	}
+
+
+	/**
+	 * Initialise a data import exception using an message and a cause
+	 * 
+	 * @param message
+	 *            the error message
+	 * @param cause
+	 *            the exception that caused this exception to be thrown
+	 */
+	public DataImportException( String message , Throwable cause )
+	{
+		super( message , cause );
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/ImportableData.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/ImportableData.java
new file mode 100644
index 0000000..fae56bd
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/ImportableData.java
@@ -0,0 +1,66 @@
+package com.deepclone.lw.cli.xmlimport.data;
+
+
+import java.io.File;
+import java.io.Serializable;
+
+
+
+/**
+ * Base class for importable data
+ * 
+ * <p>
+ * This abstract class serves as the base for all classes that represent data imported from XML
+ * files during the database's initialisation.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+public abstract class ImportableData
+		implements Serializable
+{
+
+	/** The file these data were read from */
+	private transient File readFrom;
+
+
+	/** @return the file these data were read from */
+	public final File getReadFrom( )
+	{
+		return this.readFrom;
+	}
+
+
+	/**
+	 * Validate the item
+	 * 
+	 * <p>
+	 * This method may be overridden to implement some form of validation of the loaded data.
+	 * 
+	 * @throws DataImportException
+	 *             if data validation failed
+	 */
+	public void verifyData( )
+			throws DataImportException
+	{
+		// EMPTY
+	}
+
+
+	/**
+	 * Set the source file
+	 * 
+	 * <p>
+	 * This method sets the file instance returned by {@link #getReadFrom()}. For classes that
+	 * represent collections of data, this method should be overridden in order to update all
+	 * contained items.
+	 * 
+	 * @param readFrom
+	 *            the file the data were read from
+	 */
+	public void setReadFrom( File readFrom )
+	{
+		this.readFrom = readFrom;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/FileString.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/FileString.java
new file mode 100644
index 0000000..4645170
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/FileString.java
@@ -0,0 +1,104 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * An external string definition
+ * 
+ * <p>
+ * This class corresponds to I18N string definitions which use external files to store the actual
+ * string.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "from-file" )
+public class FileString
+		extends StringDefinition
+{
+	/** The name of the file which contains the string's text */
+	@XStreamAsAttribute
+	private String source;
+
+
+	/**
+	 * Verify an external string definition
+	 * 
+	 * <p>
+	 * Make sure that the definition actually includes the name of the file to load the string from.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		super.verifyData( );
+		if ( this.source == null || "".equals( this.source.trim( ) ) ) {
+			throw new DataImportException( "Missing file name for string definition '" + this.getId( ) + "'" );
+		}
+	}
+
+
+	/**
+	 * Load the string
+	 * 
+	 * <p>
+	 * This implementation opens the file defined as the source of the string's text, reads it then
+	 * returns it.
+	 * 
+	 * @throws DataImportException
+	 *             if the file cannot be opened or read from
+	 */
+	@Override
+	public String getString( )
+			throws DataImportException
+	{
+		File source = getSourceFile( );
+
+		StringBuilder sBuilder = new StringBuilder( );
+		try {
+			BufferedReader in = new BufferedReader( new FileReader( source ) );
+			try {
+				String str;
+				while ( ( str = in.readLine( ) ) != null ) {
+					sBuilder.append( str );
+					sBuilder.append( "\n" );
+				}
+			} finally {
+				in.close( );
+			}
+		} catch ( IOException e ) {
+			throw new DataImportException( "Could not read from " + source.getPath( ) , e );
+		}
+
+		return sBuilder.toString( );
+	}
+
+
+	/**
+	 * Access the source file
+	 * 
+	 * <p>
+	 * Create the source file instance using the path to the data file the current instance was read
+	 * from as the base directory.
+	 * 
+	 * @return the source file
+	 */
+	private File getSourceFile( )
+	{
+		File parentDirectory = this.getReadFrom( ).getParentFile( );
+		if ( parentDirectory == null ) {
+			parentDirectory = new File( "." );
+		}
+		return new File( parentDirectory , this.source ).getAbsoluteFile( );
+	}
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/I18NText.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/I18NText.java
new file mode 100644
index 0000000..13cf4da
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/I18NText.java
@@ -0,0 +1,120 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+/**
+ * I18N text data
+ * 
+ * <p>
+ * This class represents the contents of the I18N text definitions file. It contains a list of
+ * language definitions.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "lw-text-data" )
+public class I18NText
+		extends ImportableData
+		implements Serializable , Iterable< LanguageDefinition >
+{
+	/** All present language definitions */
+	@XStreamImplicit( itemFieldName = "language" )
+	private List< LanguageDefinition > languages = new LinkedList< LanguageDefinition >( );
+
+
+	/**
+	 * Set the source file
+	 * 
+	 * <p>
+	 * Update the definition's own source file, then update all language definitions.
+	 */
+	@Override
+	public void setReadFrom( File readFrom )
+	{
+		super.setReadFrom( readFrom );
+		for ( LanguageDefinition languageDefinition : this.languages ) {
+			languageDefinition.setReadFrom( readFrom );
+		}
+	}
+
+
+	/**
+	 * Verify I18N text data
+	 * 
+	 * <p>
+	 * Check each definition, then make sure both identifiers and language names are unique.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		if ( this.languages == null || this.languages.isEmpty( ) ) {
+			throw new DataImportException( "No language definitions" );
+		}
+
+		HashSet< String > identifiers = new HashSet< String >( );
+		HashSet< String > names = new HashSet< String >( );
+		for ( LanguageDefinition definition : this.languages ) {
+			definition.verifyData( );
+			this.checkUniqueItem( "identifier" , identifiers , definition.getId( ) );
+			this.checkUniqueItem( "name" , names , definition.getName( ) );
+		}
+	}
+
+
+	/**
+	 * Check that some part of the definition is unique
+	 * 
+	 * <p>
+	 * This helper method is used by {@link #verifyData()} to make sure that names and identifiers
+	 * are unique.
+	 * 
+	 * @param exceptionText
+	 *            the name of the item
+	 * @param existing
+	 *            the set of existing items
+	 * @param value
+	 *            the item's value
+	 * 
+	 * @throws DataImportException
+	 *             if the item's value is already present in the set of existing items
+	 */
+	private void checkUniqueItem( String exceptionText , HashSet< String > existing , String value )
+			throws DataImportException
+	{
+		if ( existing.contains( value ) ) {
+			throw new DataImportException( "Duplicate language " + exceptionText + " '" + value + "'" );
+		}
+		existing.add( value );
+	}
+
+
+	/**
+	 * Language definition iterator
+	 * 
+	 * <p>
+	 * Grant access to the list of languages in read-only mode.
+	 * 
+	 * @return a read-only iterator on the list of languages.
+	 */
+	@Override
+	public Iterator< LanguageDefinition > iterator( )
+	{
+		return Collections.unmodifiableList( this.languages ).iterator( );
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/InlineString.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/InlineString.java
new file mode 100644
index 0000000..01e497e
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/InlineString.java
@@ -0,0 +1,55 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+
+
+/**
+ * An in-line string definition
+ * 
+ * <p>
+ * This class corresponds to string definitions which are stored directly inside the XML data file.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "inline-string" )
+public class InlineString
+		extends StringDefinition
+{
+
+	/** The string's text */
+	private String value;
+
+
+	/**
+	 * Verify the in-line string definition
+	 * 
+	 * <p>
+	 * Make sure that the definition actually contains a string.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		super.verifyData( );
+
+		if ( this.value == null || "".equals( this.value.trim( ) ) ) {
+			throw new DataImportException( "Missing or empty in-line string definition '" + this.getId( ) + "'" );
+		}
+	}
+
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see StringDefinition#getString()
+	 */
+	@Override
+	public String getString( )
+	{
+		return this.value;
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/LanguageDefinition.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/LanguageDefinition.java
new file mode 100644
index 0000000..77b17d1
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/LanguageDefinition.java
@@ -0,0 +1,145 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+/**
+ * A language definition
+ * 
+ * <p>
+ * Language definitions for the I18N text importer possess an identifier and a name. In addition,
+ * they may contain any amount of string definitions.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+public class LanguageDefinition
+		extends ImportableData
+		implements Serializable , Iterable< StringDefinition >
+{
+	/** The language's identifier */
+	@XStreamAsAttribute
+	private String id;
+
+	/** The language's name */
+	@XStreamAsAttribute
+	private String name;
+
+	/** All strings defined for this language */
+	@XStreamImplicit
+	private final List< StringDefinition > strings = new LinkedList< StringDefinition >( );
+
+
+	/**
+	 * Set the source file
+	 * 
+	 * <p>
+	 * Update the definition's own source file, then update all string definitions.
+	 */
+	@Override
+	public void setReadFrom( File readFrom )
+	{
+		super.setReadFrom( readFrom );
+		for ( StringDefinition definition : this.strings ) {
+			definition.setReadFrom( readFrom );
+		}
+	}
+
+
+	/**
+	 * Validate the language definition
+	 * 
+	 * <p>
+	 * Make sure that all definition characteristics are properly defined, then check all strings
+	 * and make sure they are unique.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		if ( this.id == null || "".equals( this.id.trim( ) ) ) {
+			throw new DataImportException( "Missing language identifier" );
+		}
+		if ( this.name == null || "".equals( this.name.trim( ) ) ) {
+			throw new DataImportException( "Missing language name for identifier '" + this.id + "'" );
+		}
+		if ( this.strings == null || this.strings.isEmpty( ) ) {
+			throw new DataImportException( "No strings defined for language '" + this.id + "'" );
+		}
+
+		this.checkStringDefinitions( );
+	}
+
+
+	/**
+	 * Check all string definitions
+	 * 
+	 * <p>
+	 * Iterate over all included string definitions, validating them and making sure they all have
+	 * unique identifiers.
+	 * 
+	 * @throws DataImportException
+	 *             if a string definition is invalid or if string identifiers contain duplicates.
+	 */
+	private void checkStringDefinitions( )
+			throws DataImportException
+	{
+		HashSet< String > strings = new HashSet< String >( );
+		for ( StringDefinition definition : this.strings ) {
+			definition.verifyData( );
+
+			String stringId = definition.getId( );
+			if ( strings.contains( stringId ) ) {
+				throw new DataImportException( "In language '" + this.id + "': duplicate string '" + stringId + "'" );
+			}
+			strings.add( stringId );
+		}
+	}
+
+
+	/** @return the language's identifier */
+	public String getId( )
+	{
+		return this.id;
+	}
+
+
+	/** @return the language's name */
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	/**
+	 * Iterate over all string definitions
+	 * 
+	 * <p>
+	 * This method grants access to the list of string definitions in read-only mode.
+	 * 
+	 * <p>
+	 * <strong>Warning:</strong> this method should not be called if {@link #containsStrings()}
+	 * returns <code>false</code>.
+	 * 
+	 * @return the read-only string definition iterator
+	 */
+	@Override
+	public Iterator< StringDefinition > iterator( )
+	{
+		return Collections.unmodifiableList( this.strings ).iterator( );
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/StringDefinition.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/StringDefinition.java
new file mode 100644
index 0000000..1e2701a
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/i18n/StringDefinition.java
@@ -0,0 +1,73 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import java.io.Serializable;
+import java.util.regex.Pattern;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * A string definition
+ * 
+ * <p>
+ * This class is the base for both types of string definitions used by the initial I18N text
+ * importer. String definitions always contain an identifier, and it is always possible to obtain
+ * the actual text they contain.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+public abstract class StringDefinition
+		extends ImportableData
+		implements Serializable
+{
+	/** Pattern followed by string identifiers */
+	private static final Pattern identifierCheck = Pattern.compile( "^[a-z][a-z0-9]*([A-Z][a-z0-9]*)*$" );
+
+	/** The string's identifier */
+	@XStreamAsAttribute
+	private String id;
+
+
+	/**
+	 * Check a string definition's identifier
+	 * 
+	 * <p>
+	 * Make sure that a string definition's identifier is both present and valid.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		if ( this.id == null ) {
+			throw new DataImportException( "Missing string identifier" );
+		} else if ( !identifierCheck.matcher( this.id ).find( ) ) {
+			throw new DataImportException( "Invalid string identifier '" + this.id + "'" );
+		}
+	}
+
+
+	/** @return the string's identifier */
+	public final String getId( )
+	{
+		return this.id;
+	}
+
+
+	/**
+	 * This method must be overridden to provide a way of reading the actual text of the string that
+	 * is being defined.
+	 * 
+	 * @return the string's text
+	 * 
+	 * @throws DataImportException
+	 *             if accessing the string's actual text fails
+	 */
+	public abstract String getString( )
+			throws DataImportException;
+
+}
diff --git a/legacyworlds-server-main/src/main/resources/log4j.properties b/legacyworlds-server-main/src/main/resources/log4j.properties
index 994c335..066fac3 100644
--- a/legacyworlds-server-main/src/main/resources/log4j.properties
+++ b/legacyworlds-server-main/src/main/resources/log4j.properties
@@ -36,3 +36,4 @@ log4j.logger.org.springframework=WARN, server
 log4j.logger.org.springframework=INFO, fullDebug
 log4j.logger.com.deepclone.lw=DEBUG, fullDebug
 log4j.logger.com.deepclone.lw.interfaces.eventlog=DEBUG, server
+log4j.logger.com.deepclone.lw.cli=INFO, stdout
diff --git a/legacyworlds-server-tests/TestFiles/i18n-external-file/test.txt b/legacyworlds-server-tests/TestFiles/i18n-external-file/test.txt
new file mode 100644
index 0000000..273c1a9
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/i18n-external-file/test.txt
@@ -0,0 +1 @@
+This is a test.
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/i18n-loader/bad-contents.xml b/legacyworlds-server-tests/TestFiles/i18n-loader/bad-contents.xml
new file mode 100644
index 0000000..f33b821
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/i18n-loader/bad-contents.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-text-data>
+
+	<does-not-exist />
+
+</lw-text-data>
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/i18n-loader/bad-data.xml b/legacyworlds-server-tests/TestFiles/i18n-loader/bad-data.xml
new file mode 100644
index 0000000..aec5ebc
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/i18n-loader/bad-data.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text ../../../legacyworlds-server-main/data/i18n-text.xsd">
+
+	<language name="test" id="test">
+		<inline-string id="test">
+			<value />
+		</inline-string>
+	</language>
+
+</lw-text-data>
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/i18n-loader/bad-xml.xml b/legacyworlds-server-tests/TestFiles/i18n-loader/bad-xml.xml
new file mode 100644
index 0000000..ee1d0ed
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/i18n-loader/bad-xml.xml
@@ -0,0 +1,2 @@
+This is not an XML file, obviously.
+We'll make that even more confusing: <<<<<< & >>!!!
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/i18n-loader/good.xml b/legacyworlds-server-tests/TestFiles/i18n-loader/good.xml
new file mode 100644
index 0000000..f1ca627
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/i18n-loader/good.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text ../../../legacyworlds-server-main/data/i18n-text.xsd">
+
+	<language name="test" id="test">
+	
+		<from-file id="test" source="test.txt" />
+
+	</language>
+
+</lw-text-data>
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/i18n-loader/test.txt b/legacyworlds-server-tests/TestFiles/i18n-loader/test.txt
new file mode 100644
index 0000000..30d74d2
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/i18n-loader/test.txt
@@ -0,0 +1 @@
+test
\ No newline at end of file
diff --git a/legacyworlds-server-tests/pom.xml b/legacyworlds-server-tests/pom.xml
index d1b1438..c9cd3a4 100644
--- a/legacyworlds-server-tests/pom.xml
+++ b/legacyworlds-server-tests/pom.xml
@@ -50,6 +50,10 @@
 			<artifactId>legacyworlds-server-beans-user</artifactId>
 			<groupId>com.deepclone.lw</groupId>
 		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-main</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+		</dependency>
 
 		<dependency>
 			<groupId>junit</groupId>
diff --git a/legacyworlds-server-tests/src/main/java/.empty b/legacyworlds-server-tests/src/main/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-tests/src/main/resources/.empty b/legacyworlds-server-tests/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-tests/src/test/java/.empty b/legacyworlds-server-tests/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestI18NLoader.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestI18NLoader.java
new file mode 100644
index 0000000..0b2ac67
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestI18NLoader.java
@@ -0,0 +1,152 @@
+package com.deepclone.lw.cli.xmlimport;
+
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.i18n.I18NText;
+import com.deepclone.lw.cli.xmlimport.data.i18n.LanguageDefinition;
+import com.deepclone.lw.cli.xmlimport.data.i18n.StringDefinition;
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.io.StreamException;
+
+
+
+/**
+ * Unit tests for the {@link I18NLoader} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class TestI18NLoader
+{
+
+	/**
+	 * Try initialising the loader with a <code>null</code> file instance.
+	 * 
+	 * @throws NullPointerException
+	 *             when the constructor is executed
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testNullFile( )
+			throws NullPointerException
+	{
+		new I18NLoader( null );
+	}
+
+
+	/**
+	 * Try loading a file that does not exist
+	 */
+	@Test
+	public void testMissingFile( )
+	{
+		I18NLoader loader = new I18NLoader( new File( "does-not-exist" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a " + e.getCause( ).getClass( ).getName( ) , e.getCause( ) instanceof IOException );
+			return;
+		}
+		fail( "no exception after trying to load a missing file" );
+	}
+
+
+	/**
+	 * Try loading a file that contains something that definitely isn't XML.
+	 */
+	@Test
+	public void testBadXML( )
+	{
+		I18NLoader loader = new I18NLoader( new File( "TestFiles/i18n-loader/bad-xml.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a " + e.getCause( ).getClass( ).getName( ) , e.getCause( ) instanceof StreamException );
+			return;
+		}
+		fail( "no exception after loading stuff that isn't XML" );
+	}
+
+
+	/**
+	 * Try loading a file that contains XML but which cannot be desertialised to an {@link I18NText}
+	 * instance.
+	 */
+	@Test
+	public void testBadContents( )
+	{
+		I18NLoader loader = new I18NLoader( new File( "TestFiles/i18n-loader/bad-contents.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a " + e.getCause( ).getClass( ).getName( ) ,
+					e.getCause( ) instanceof ConversionException );
+			return;
+		}
+		fail( "no exception after loading bad XML" );
+	}
+
+
+	/**
+	 * Try loading a file that contains valid XML for an {@link I18NText} instance with semantic
+	 * errors.
+	 */
+	@Test
+	public void testBadData( )
+	{
+		I18NLoader loader = new I18NLoader( new File( "TestFiles/i18n-loader/bad-data.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertNull( e.getCause( ) );
+			return;
+		}
+		fail( "no exception after loading bad data" );
+	}
+
+
+	/**
+	 * Try loading valid data, make sure that it contains exactly what is expected, and that it
+	 * loads external strings from the appropriate directory.
+	 */
+	@Test
+	public void testGoodData( )
+	{
+		I18NLoader loader = new I18NLoader( new File( "TestFiles/i18n-loader/good.xml" ) );
+		I18NText text;
+		try {
+			text = loader.load( );
+		} catch ( DataImportException e ) {
+			fail( "could not load valid file" );
+			return;
+		}
+		assertNotNull( text );
+
+		int lCount = 0;
+		for ( LanguageDefinition ld : text ) {
+			assertEquals( "test" , ld.getId( ) );
+			assertEquals( "test" , ld.getName( ) );
+
+			int tCount = 0;
+			for ( StringDefinition sd : ld ) {
+				assertEquals( "test" , sd.getId( ) );
+				try {
+					assertEquals( "test" , sd.getString( ).trim( ) );
+				} catch ( DataImportException e ) {
+					fail( "could not load string: " + e.getMessage( ) );
+				}
+				tCount++;
+			}
+
+			assertEquals( 1 , tCount );
+			lCount++;
+		}
+		assertEquals( 1 , lCount );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/TestImportableData.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/TestImportableData.java
new file mode 100644
index 0000000..f8f5880
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/TestImportableData.java
@@ -0,0 +1,73 @@
+package com.deepclone.lw.cli.xmlimport.data;
+
+
+import java.io.File;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+
+/**
+ * Unit tests for the {@link ImportableData} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestImportableData
+{
+
+	/**
+	 * Dummy importable data
+	 * 
+	 * <p>
+	 * This class is used in the test to have access to an importable data instance that corresponds
+	 * exactly to the base class.
+	 * 
+	 * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+	 */
+	@SuppressWarnings( "serial" )
+	private static class DummyData
+			extends ImportableData
+	{
+		// EMPTY
+	}
+
+
+	/** Make sure that {@link ImportableData#getReadFrom()} returns <code>null</code> by default */
+	@Test
+	public void testUninitialisedReadFrom( )
+	{
+		DummyData data = new DummyData( );
+		assertNull( data.getReadFrom( ) );
+	}
+
+
+	/** Checks the default implementation of {@link ImportableData#setReadFrom(File)} */
+	@Test
+	public void testSetReadFrom( )
+	{
+		DummyData data = new DummyData( );
+		File file = new File( "test" );
+
+		data.setReadFrom( file );
+		assertNotNull( data.getReadFrom( ) );
+		assertEquals( "test" , data.getReadFrom( ).getPath( ) );
+	}
+
+
+	/**
+	 * Make sure that the default implementation of {@link ImportableData#verifyData()} always
+	 * succeeds
+	 * 
+	 * @throws DataImportException
+	 *             if the default implementation fails somehow
+	 */
+	@Test
+	public void testDefaultVerify( )
+			throws DataImportException
+	{
+		DummyData data = new DummyData( );
+		data.verifyData( );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/BaseTest.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/BaseTest.java
new file mode 100644
index 0000000..1daeca8
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/BaseTest.java
@@ -0,0 +1,205 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import com.deepclone.lw.cli.xmlimport.I18NLoader;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.XStreamException;
+
+
+
+/**
+ * Base class for I18N import structures
+ * 
+ * <p>
+ * This class is used as a parent by tests of the I18N import structures. It includes the code
+ * required to actually create such resources, as this can normally only be done through XStream.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+abstract class BaseTest
+{
+
+	/**
+	 * Escape &amp;, &lt; and &gt; in XML strings
+	 * 
+	 * @param string
+	 *            the string to escape
+	 * 
+	 * @return the escaped string
+	 */
+	private String xmlString( String string )
+	{
+		return string.replace( "&" , "&amp;" ).replace( "<" , "&lt;" ).replace( ">" , "&gt;" );
+	}
+
+
+	/**
+	 * Escape &amp;, &lt; and &gt;, ' and " in XML strings
+	 * 
+	 * @param string
+	 *            the string to escape
+	 * 
+	 * @return the escaped string
+	 */
+	private String quoteString( String string )
+	{
+		return "\"" + this.xmlString( string ).replace( "\"" , "&quot;" ).replace( "'" , "&apos;" ) + "\"";
+	}
+
+
+	/**
+	 * Create an in-line string definition
+	 * 
+	 * <p>
+	 * Generate the XML code that corresponds to an in-line string definition.
+	 * 
+	 * @param identifier
+	 *            the string's identifier, or <code>null</code> to omit the identifier
+	 * @param contents
+	 *            the string's contents, or <code>null</code> to omit the contents.
+	 * 
+	 * @return the XML code corresponding to the string definition
+	 */
+	protected String createInlineStringDefinition( String identifier , String contents )
+	{
+		StringBuilder str = new StringBuilder( );
+		str.append( "<inline-string" );
+		if ( identifier != null ) {
+			str.append( " id=" ).append( this.quoteString( identifier ) );
+		}
+		str.append( '>' );
+		if ( contents != null ) {
+			str.append( "<value>" ).append( this.xmlString( contents ) ).append( "</value>" );
+		}
+		str.append( "</inline-string>" );
+		return str.toString( );
+	}
+
+
+	/**
+	 * Create an external string definition
+	 * 
+	 * <p>
+	 * Generate the XML code that corresponds to an external string definition.
+	 * 
+	 * @param identifier
+	 *            the string's identifier, or <code>null</code> to omit the identifier
+	 * @param source
+	 *            the string's source file, or <code>null</code> to omit the source.
+	 * 
+	 * @return the XML code corresponding to the string definition
+	 */
+	protected String createExternalStringDefinition( String identifier , String source )
+	{
+		StringBuilder str = new StringBuilder( );
+		str.append( "<from-file" );
+		if ( identifier != null ) {
+			str.append( " id=" ).append( this.quoteString( identifier ) );
+		}
+		if ( source != null ) {
+			str.append( " source=" ).append( this.quoteString( source ) );
+		}
+		str.append( " />" );
+		return str.toString( );
+	}
+
+
+	/**
+	 * Create a language definition
+	 * 
+	 * @param identifier
+	 *            the language's identifier, or <code>null</code> if the identifier is to be omitted
+	 * @param name
+	 *            the language's name, or <code>null</code> if the name is to be omitted
+	 * @param stringDefinitions
+	 *            XML definitions returned by {@link #createInlineStringDefinition(String, String)}
+	 *            or {@link #createExternalStringDefinition(String, String)}
+	 * 
+	 * @return the XML code corresponding to the language definition
+	 */
+	protected String createLanguageDefinition( String identifier , String name , String... stringDefinitions )
+	{
+		StringBuilder str = new StringBuilder( );
+
+		str.append( "<language" );
+		if ( identifier != null ) {
+			str.append( " id=" ).append( this.quoteString( identifier ) );
+		}
+		if ( name != null ) {
+			str.append( " name=" ).append( this.quoteString( name ) );
+		}
+		str.append( ">" );
+		for ( String definition : stringDefinitions ) {
+			str.append( definition );
+		}
+		str.append( "</language>" );
+
+		return str.toString( );
+	}
+
+
+	/**
+	 * Create the XML code for a top-level I18N definition element
+	 * 
+	 * @param languages
+	 *            XML definitions of languages, as returned by
+	 *            {@link #createLanguageDefinition(String, String, String...)}
+	 * 
+	 * @return the top-level element's XML code
+	 */
+	protected String createTopLevel( String... languages )
+	{
+		StringBuilder str = new StringBuilder( );
+		str.append( "<lw-text-data>" );
+		for ( String language : languages ) {
+			str.append( language );
+		}
+		str.append( "</lw-text-data>" );
+		return str.toString( );
+	}
+
+
+	/**
+	 * Create the necessary XStream instance
+	 * 
+	 * <p>
+	 * Initialise an XStream instance and set it up so it can process I18N data definitions. Unlike
+	 * the XStream instance generated by {@link I18NLoader}, this version also registers the alias
+	 * for language definitions.
+	 * 
+	 * @return the initialised XStream instance.
+	 */
+	private XStream createXStreamInstance( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( I18NText.class );
+		xstream.processAnnotations( InlineString.class );
+		xstream.processAnnotations( FileString.class );
+		xstream.alias( "language" , LanguageDefinition.class );
+		return xstream;
+	}
+
+
+	/**
+	 * Create an I18N definition object from its XML code
+	 * 
+	 * @param xml
+	 *            the XML code
+	 * @param cls
+	 *            the class of the object
+	 * 
+	 * @return the object that was created from the code
+	 * 
+	 * @throws ClassCastException
+	 *             if the code corresponds to some other type of object
+	 * @throws XStreamException
+	 *             if some error occurred while unserialising the object
+	 */
+	protected < T extends ImportableData > T createObject( String xml , Class< T > cls )
+			throws ClassCastException , XStreamException
+	{
+		return cls.cast( this.createXStreamInstance( ).fromXML( xml ) );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestExternalString.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestExternalString.java
new file mode 100644
index 0000000..6970672
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestExternalString.java
@@ -0,0 +1,307 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+
+
+
+/**
+ * Unit tests for the {@link FileString} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestExternalString
+		extends BaseTest
+{
+
+	/**
+	 * Test loading a valid external string record and the corresponding contents
+	 * 
+	 * @throws DataImportException
+	 *             if a failure occurs while reading the source file.
+	 */
+	@Test
+	public void testLoadStringOK( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "test" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.setReadFrom( new File( "TestFiles/i18n-external-file/file" ) );
+		assertEquals( "test" , instance.getId( ) );
+		assertEquals( "This is a test." , instance.getString( ).trim( ) );
+	}
+
+
+	/**
+	 * Test loading an external string record with no identifier
+	 * 
+	 * @throws DataImportException
+	 *             if a failure occurs while reading the source file.
+	 */
+	@Test
+	public void testLoadStringNullIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( null , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.setReadFrom( new File( "TestFiles/i18n-external-file/file" ) );
+		assertNull( instance.getId( ) );
+		assertEquals( "This is a test." , instance.getString( ).trim( ) );
+	}
+
+
+	/**
+	 * Test loading an external string record with no source file
+	 * 
+	 * @throws DataImportException
+	 *             if a failure occurs while reading the source file
+	 * @throws NullPointerException
+	 *             when {@link FileString#getString()} is called while its source is
+	 *             <code>null</code>
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testLoadStringNullSource( )
+			throws DataImportException , NullPointerException
+	{
+		String definition = this.createExternalStringDefinition( "test" , null );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.setReadFrom( new File( "TestFiles/i18n-external-file/file" ) );
+		assertEquals( "test" , instance.getId( ) );
+		instance.getString( );
+	}
+
+
+	/**
+	 * Test loading a valid external string record and obtaining the contents without setting the
+	 * path to the data file first.
+	 * 
+	 * @throws DataImportException
+	 *             if a failure occurs while reading the source file.
+	 * @throws NullPointerException
+	 *             when {@link FileString#getString()} is called while its original path is
+	 *             <code>null</code>
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testLoadStringNoPath( )
+			throws DataImportException , NullPointerException
+	{
+		String definition = this.createExternalStringDefinition( "test" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.getString( );
+	}
+
+
+	/**
+	 * Test loading a valid external string record and obtaining the contents when the source file
+	 * does not exist.
+	 * 
+	 * @throws DataImportException
+	 *             when a failure occurs while reading the source file.
+	 */
+	@Test( expected = DataImportException.class )
+	public void testLoadStringBadSource( )
+			throws DataImportException , NullPointerException
+	{
+		String definition = this.createExternalStringDefinition( "test" , "does-not-exist.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.setReadFrom( new File( "TestFiles/i18n-external-file/file" ) );
+		instance.getString( );
+	}
+
+
+	/**
+	 * Test loading a valid external string record and obtaining the contents when the source file
+	 * is a directory.
+	 * 
+	 * @throws DataImportException
+	 *             when a failure occurs while reading the source file.
+	 */
+	@Test( expected = DataImportException.class )
+	public void testLoadStringSourceIsDirectory( )
+			throws DataImportException , NullPointerException
+	{
+		String definition = this.createExternalStringDefinition( "test" , "." );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.setReadFrom( new File( "TestFiles/i18n-external-file/file" ) );
+		instance.getString( );
+	}
+
+
+	/**
+	 * Test loading a valid external string record and obtaining the contents when the original
+	 * file's path is incorrect.
+	 * 
+	 * @throws DataImportException
+	 *             when a failure occurs while reading the source file.
+	 */
+	@Test( expected = DataImportException.class )
+	public void testLoadStringBadParent( )
+			throws DataImportException , NullPointerException
+	{
+		String definition = this.createExternalStringDefinition( "test" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.setReadFrom( new File( "does-not-exist/does-not-exist" ) );
+		instance.getString( );
+	}
+
+
+	/**
+	 * Test loading a valid external string record and obtaining the contents when the original
+	 * file's path is a single file name.
+	 * 
+	 * @throws DataImportException
+	 *             if a failure occurs while reading the source file.
+	 */
+	@Test
+	public void testLoadStringParentIsCurrentDirectory( )
+			throws DataImportException , NullPointerException
+	{
+		String definition = this.createExternalStringDefinition( "test" , "TestFiles/i18n-external-file/test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.setReadFrom( new File( "does-not-exist" ) );
+		instance.getString( );
+	}
+
+
+
+	/** Test validating a file string record with a short identifier and a source */
+	@Test
+	public void testValidateSimple( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "test" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test validating an external string record with a camel case identifier and a source */
+	@Test
+	public void testValidateCamelCaseIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "testCamelCase" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating an external string record with an identifier that includes a sequence of
+	 * upper-case letters and a source
+	 */
+	@Test
+	public void testValidateCapsIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "testCAPSIdentifier" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating an external string record with an identifier that includes numbers and some
+	 * contents
+	 */
+	@Test
+	public void testValidateNumbersIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "test123Numbers123" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test rejecting an external string record with a <code>null</code> identifier and a source
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNullIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( null , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test rejecting an external string record with an identifier that starts with a bad character */
+	@Test( expected = DataImportException.class )
+	public void testValidateBadFirstCharacterIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( " test" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test rejecting an external string record with an identifier that starts with an upper-case
+	 * letter
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateFirstCharacterWrongCaseIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "Test" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test rejecting an external string record with an identifier that includes "bad" characters */
+	@Test( expected = DataImportException.class )
+	public void testValidateBadMiddleCharacterIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "test Test" , "test.txt" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test rejecting an external record with <code>null</code> contents */
+	@Test( expected = DataImportException.class )
+	public void testValidateNullSource( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "test" , null );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test rejecting an external record with empty contents */
+	@Test( expected = DataImportException.class )
+	public void testValidateEmptySource( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "test" , "" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test rejecting an external record with contents the include only white space (spaces, tabs,
+	 * new line, line feed)
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateSpacesOnlySource( )
+			throws DataImportException
+	{
+		String definition = this.createExternalStringDefinition( "test" , " \t\r\n" );
+		FileString instance = this.createObject( definition , FileString.class );
+		instance.verifyData( );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestI18NText.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestI18NText.java
new file mode 100644
index 0000000..f158d91
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestI18NText.java
@@ -0,0 +1,134 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+
+
+
+/**
+ * Unit tests for the {@link I18NText} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestI18NText
+		extends BaseTest
+{
+	/** The language definition that will be used in most tests */
+	private String languageDefinition;
+
+
+	/** Initialise a language definition to use while testing */
+	@Before
+	public void setUp( )
+	{
+		String xml = this.createInlineStringDefinition( "test" , "test" );
+		this.languageDefinition = this.createLanguageDefinition( "test" , "test" , xml );
+	}
+
+
+	/**
+	 * Try loading a valid top-level I18N definition and make sure its contents are correct
+	 * 
+	 * @throws DataImportException
+	 *             if the language definition check fails for some unknown reason
+	 */
+	@Test
+	public void testLoadValidDefinitions( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( this.languageDefinition );
+		I18NText text = this.createObject( xml , I18NText.class );
+
+		int count = 0;
+		for ( LanguageDefinition definition : text ) {
+			definition.verifyData( );
+			count++;
+		}
+		assertEquals( 1 , count );
+	}
+
+
+	/**
+	 * Try loading an empty top-level element
+	 * 
+	 * @throws NullPointerException
+	 *             when the iterator is accessed
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testLoadEmpty( )
+			throws NullPointerException
+	{
+		String xml = this.createTopLevel( );
+		I18NText text = this.createObject( xml , I18NText.class );
+		text.iterator( );
+	}
+
+
+	/**
+	 * Try validating a correct top-level element
+	 * 
+	 * @throws DataImportException
+	 *             if the check fails
+	 */
+	@Test
+	public void testValidateOK( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( this.languageDefinition );
+		I18NText text = this.createObject( xml , I18NText.class );
+		text.verifyData( );
+	}
+
+
+	/**
+	 * Try rejecting a top-level element that does not contain any language definition
+	 * 
+	 * @throws DataImportException
+	 *             when the check fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateEmpty( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( );
+		I18NText text = this.createObject( xml , I18NText.class );
+		text.verifyData( );
+	}
+
+
+	/**
+	 * Try rejecting a top-level element that contains an invalid language definition
+	 * 
+	 * @throws DataImportException
+	 *             when the check fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateBadDefinition( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( this.createLanguageDefinition( null , null ) );
+		I18NText text = this.createObject( xml , I18NText.class );
+		text.verifyData( );
+	}
+
+
+	/**
+	 * Try rejecting a top-level element that contains two valid but duplicate language definitions
+	 * 
+	 * @throws DataImportException
+	 *             when the check fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateDuplicateDefinition( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( this.languageDefinition , this.languageDefinition );
+		I18NText text = this.createObject( xml , I18NText.class );
+		text.verifyData( );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestInlineString.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestInlineString.java
new file mode 100644
index 0000000..501ce92
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestInlineString.java
@@ -0,0 +1,210 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import java.io.File;
+import java.lang.reflect.Method;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+
+import static org.junit.Assert.*;
+
+
+
+/**
+ * Tests for the {@link InlineString} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestInlineString
+		extends BaseTest
+{
+
+	/** Test loading a valid in-line string record */
+	@Test
+	public void testLoadStringOK( )
+	{
+		String definition = this.createInlineStringDefinition( "test" , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		assertEquals( "test" , instance.getId( ) );
+		assertEquals( "Test" , instance.getString( ) );
+	}
+
+
+	/** Test loading an in-line string record without identifier */
+	@Test
+	public void testLoadStringNoIdentifier( )
+	{
+		String definition = this.createInlineStringDefinition( null , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		assertNull( instance.getId( ) );
+		assertEquals( "Test" , instance.getString( ) );
+	}
+
+
+	/** Test loading an in-line string record without contents */
+	@Test
+	public void testLoadStringNoContents( )
+	{
+		String definition = this.createInlineStringDefinition( "test" , null );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		assertEquals( "test" , instance.getId( ) );
+		assertNull( instance.getString( ) );
+	}
+
+
+	/** Test validating an in-line string record with a short identifier and some contents */
+	@Test
+	public void testValidateSimple( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "test" , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test validating an in-line string record with a camel case identifier and some contents */
+	@Test
+	public void testValidateCamelCaseIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "testCamelCase" , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating an in-line string record with an identifier that includes a sequence of
+	 * upper-case letters and some contents
+	 */
+	@Test
+	public void testValidateCapsIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "testCAPSIdentifier" , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating an in-line string record with an identifier that includes numbers and some
+	 * contents
+	 */
+	@Test
+	public void testValidateNumbersIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "test123Numbers123" , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test rejecting an in-line string record with a <code>null</code> identifier and some contents
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNullIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( null , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test rejecting an in-line string record with an identifier that starts with a bad character */
+	@Test( expected = DataImportException.class )
+	public void testValidateBadFirstCharacterIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( " test" , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test rejecting an in-line string record with an identifier that starts with an upper-case
+	 * letter
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateFirstCharacterWrongCaseIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "Test" , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test rejecting an in-line string record with an identifier that includes "bad" characters */
+	@Test( expected = DataImportException.class )
+	public void testValidateBadMiddleCharacterIdentifier( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "test Test" , "Test" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test rejecting an in-line record with <code>null</code> contents */
+	@Test( expected = DataImportException.class )
+	public void testValidateNullContents( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "test" , null );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/** Test rejecting an in-line record with empty contents */
+	@Test( expected = DataImportException.class )
+	public void testValidateEmptyContents( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "test" , "" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test rejecting an in-line record with contents the include only white space (spaces, tabs,
+	 * new line, line feed)
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateSpacesOnlyContents( )
+			throws DataImportException
+	{
+		String definition = this.createInlineStringDefinition( "test" , " \t\r\n" );
+		InlineString instance = this.createObject( definition , InlineString.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Make sure the class does not override {@link ImportableData#setReadFrom(java.io.File)}; if it
+	 * does, new tests <strong>must</strong> be written.
+	 * 
+	 * @throws NoSuchMethodException
+	 *             if the method has been deleted
+	 * @throws SecurityException
+	 *             if the JVM set-up is getting in the wayy
+	 */
+	@Test
+	public void testNoSetReadFromOverride( )
+			throws SecurityException , NoSuchMethodException
+	{
+		Class< InlineString > cls = InlineString.class;
+		Method method = cls.getMethod( "setReadFrom" , File.class );
+		assertEquals( ImportableData.class , method.getDeclaringClass( ) );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestLanguageDefinition.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestLanguageDefinition.java
new file mode 100644
index 0000000..d9b18ef
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/i18n/TestLanguageDefinition.java
@@ -0,0 +1,264 @@
+package com.deepclone.lw.cli.xmlimport.data.i18n;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+
+
+
+/**
+ * Unit tests for the {@link LanguageDefinition} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestLanguageDefinition
+		extends BaseTest
+{
+
+	/** The in-line string definition that will be used in most tests */
+	private String inlineDefinition;
+
+
+	/**
+	 * Create a valid in-line string definition to be appended to language definitions.
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.inlineDefinition = this.createInlineStringDefinition( "test" , "test" );
+	}
+
+
+	/**
+	 * Test loading a valid language definition and make sure that all fields were set
+	 * appropriately.
+	 * 
+	 * @throws DataImportException
+	 *             if accessing the test string fails.
+	 */
+	@Test
+	public void testLoadValidDefinition( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( "t" , "Test" , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		assertEquals( "t" , language.getId( ) );
+		assertEquals( "Test" , language.getName( ) );
+
+		int count = 0;
+		for ( StringDefinition stringDefinition : language ) {
+			assertEquals( InlineString.class , stringDefinition.getClass( ) );
+			assertEquals( "test" , stringDefinition.getId( ) );
+			assertEquals( "test" , stringDefinition.getString( ) );
+			count++;
+		}
+		assertEquals( 1 , count );
+	}
+
+
+	/**
+	 * Test loading a language definition that has no identifier.
+	 */
+	@Test
+	public void testLoadNullIdentifier( )
+	{
+		String xml = this.createLanguageDefinition( null , "Test" , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		assertNull( language.getId( ) );
+	}
+
+
+	/**
+	 * Test loading a language definition that has no name
+	 */
+	@Test
+	public void testLoadNullName( )
+	{
+		String xml = this.createLanguageDefinition( "test" , null , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		assertNull( language.getName( ) );
+	}
+
+
+	/**
+	 * Test loading a language definition that has no contents
+	 * 
+	 * @throws NullPointerException
+	 *             when the language definition's iterator is accessed, as its contents will be
+	 *             <code>null</code>
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testLoadNullContents( )
+			throws NullPointerException
+	{
+		String xml = this.createLanguageDefinition( "test" , "test" );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.iterator( );
+	}
+
+
+	/**
+	 * Test validating a correct language definition.
+	 * 
+	 * @throws DataImportException
+	 *             if validating the definition fails
+	 */
+	@Test
+	public void testValidateOK( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( "t" , "Test" , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with a <code>null</code> identifier.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNullIdentifier( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( null , "Test" , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with an empty identifier.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateEmptyIdentifier( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( "" , "Test" , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with an identifier that contains white space only.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateWhiteSpaceIdentifier( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( " \t\n\r" , "Test" , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with a <code>null</code> name.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNullName( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( "t" , null , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with an empty name.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateEmptyName( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( "t" , "" , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with a name that contains white space only.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateWhiteSpaceName( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( "t" , " \t\n\r" , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with no contents.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNoContents( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( "t" , "Test" );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with invalid contents.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateBadContents( )
+			throws DataImportException
+	{
+		String strXML = this.createInlineStringDefinition( null , "test" );
+		String xml = this.createLanguageDefinition( "t" , "Test" , strXML );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a language definition with duplicate contents.
+	 * 
+	 * @throws DataImportException
+	 *             when validation fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateDuplicateContents( )
+			throws DataImportException
+	{
+		String xml = this.createLanguageDefinition( "t" , "Test" , this.inlineDefinition , this.inlineDefinition );
+		LanguageDefinition language = this.createObject( xml , LanguageDefinition.class );
+		language.verifyData( );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/resources/.empty b/legacyworlds-server-tests/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds/doc/TODO.txt b/legacyworlds/doc/TODO.txt
index f1fc671..448622a 100644
--- a/legacyworlds/doc/TODO.txt
+++ b/legacyworlds/doc/TODO.txt
@@ -20,8 +20,6 @@ SERVER & DATABASE:
 
   * Add a tool to initialise the database
 
-  * I18N loader: improve text file loading (use relative paths)
-
   * Replace current authentication information (pair of hashes) with a
 	salted SHA512 hash.
 		-> Make sure it is still possible to import old passwords using the
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index fd00897..773e818 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -177,6 +177,11 @@
 				<artifactId>legacyworlds-server-interfaces</artifactId>
 				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 			</dependency>
+			<dependency>
+				<groupId>com.deepclone.lw</groupId>
+				<artifactId>legacyworlds-server-main</artifactId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
 			<dependency>
 				<groupId>com.deepclone.lw</groupId>
 				<artifactId>legacyworlds-session</artifactId>

From 4e1bb91780bbe4612d6f7c0ebeefd9147de29655 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 19 Dec 2011 11:57:08 +0100
Subject: [PATCH 06/94] Resource database structures

* Added structures for resource definitions, natural resources
definitions, resource providers, empire resources and empire mining
settings (both empire-wide and planet-specific).

* Added a few common utility functions to the SQL test suite. These
functions allow test initialisation to be a little shorter.

* Added "MINE" production type and an associated building definition.
The production will not be added to the XML dump or to the output of the
planets summary page, as this is extremely temporary.
---
 .../db-structure/parts/010-data.sql           |   1 +
 .../db-structure/parts/data/000-typedefs.sql  |   2 +-
 .../parts/data/075-resources-data.sql         | 121 +++++
 .../parts/data/100-universe-data.sql          |  61 +++
 .../parts/data/110-empires-data.sql           | 128 +++++
 .../constraints/defs/07500-resources.sql      | 225 +++++++++
 .../defs/07501-natural-resources.sql          | 474 ++++++++++++++++++
 .../defs/10003-resource-providers.sql         | 331 ++++++++++++
 .../defs/11001-empire-resources.sql           | 191 +++++++
 .../defs/11002-empire-mining-settings.sql     | 119 +++++
 .../11003-empire-planet-mining-settings.sql   | 193 +++++++
 .../db-structure/tests/utils/accounts.sql     |  68 +++
 .../db-structure/tests/utils/naming.sql       |  93 ++++
 .../db-structure/tests/utils/resources.sql    |  63 +++
 .../db-structure/tests/utils/strings.sql      |  65 +++
 .../db-structure/tests/utils/universe.sql     |  71 +++
 .../lw/sqld/game/BuildingOutputType.java      |   3 +-
 legacyworlds-server-main/data/buildables.xml  |   3 +
 legacyworlds-server-main/data/buildables.xsd  |   1 +
 legacyworlds-server-main/data/i18n-text.xml   |  12 +
 20 files changed, 2223 insertions(+), 2 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/data/075-resources-data.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07500-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07501-natural-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/constraints/defs/10003-resource-providers.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11001-empire-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11002-empire-mining-settings.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11003-empire-planet-mining-settings.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/utils/accounts.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/utils/naming.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/utils/resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/utils/strings.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/utils/universe.sql

diff --git a/legacyworlds-server-data/db-structure/parts/010-data.sql b/legacyworlds-server-data/db-structure/parts/010-data.sql
index add939b..f5e52b7 100644
--- a/legacyworlds-server-data/db-structure/parts/010-data.sql
+++ b/legacyworlds-server-data/db-structure/parts/010-data.sql
@@ -16,6 +16,7 @@
 \i parts/data/055-bugs-data.sql
 \i parts/data/060-naming-data.sql
 \i parts/data/070-constants-data.sql
+\i parts/data/075-resources-data.sql
 \i parts/data/080-techs-data.sql
 \i parts/data/090-buildables-data.sql
 \i parts/data/100-universe-data.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql b/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
index 4ba3baa..f9fe790 100644
--- a/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
+++ b/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
@@ -12,7 +12,7 @@ CREATE TYPE processing_status
 	
 -- Building output types
 CREATE TYPE building_output_type
-	AS ENUM ( 'CASH', 'POP', 'DEF', 'WORK' );
+	AS ENUM ( 'CASH', 'POP', 'DEF', 'WORK' , 'MINE' );
 
 -- Fleet status
 CREATE TYPE fleet_status
diff --git a/legacyworlds-server-data/db-structure/parts/data/075-resources-data.sql b/legacyworlds-server-data/db-structure/parts/data/075-resources-data.sql
new file mode 100644
index 0000000..70f1347
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/data/075-resources-data.sql
@@ -0,0 +1,121 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Resource definitions
+--
+-- Copyright(C) 2004-2011, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Common resource definitions
+ * ----------------------------
+ * 
+ * The defs.resources table is used to describe the part of a resource's
+ * definition that's idependant 
+ */
+
+CREATE TABLE defs.resources(
+	/* The resource identifier is a reference to an I18N string that
+	 * represents the resource's name. It also serves as the table's
+	 * primary key.
+	 */
+	resource_name_id			INT NOT NULL PRIMARY KEY ,
+
+	/* Identifier of an I18N string which serves as the resource's
+	 * description. 
+	 */
+	resource_description_id		INT NOT NULL ,
+
+	/* Identifier of an I18N string that names the resource's category. This
+	 * field may be NULL for resources that do not belong to a category. 
+	 */
+	resource_category_id		INT ,
+
+	/* The weight is used when sorting resources. For resources that do not
+	 * have a category, it is used directly. Otherwise, categories themselves
+	 * are sorted by average weight of their contents, and resources in
+	 * categories are sorted by weight.
+	 */
+	resource_weight				INT NOT NULL
+									CHECK ( resource_weight > 0 )
+);
+
+CREATE UNIQUE INDEX idx_resources_description
+	ON defs.resources( resource_description_id );
+CREATE INDEX idx_resources_category
+	ON defs.resources( resource_category_id );
+CREATE INDEX idx_resources_weight
+	ON defs.resources( resource_weight );
+
+ALTER TABLE defs.resources
+	ADD CONSTRAINT fk_resources_name
+		FOREIGN KEY ( resource_name_id ) REFERENCES defs.strings ,
+	ADD CONSTRAINT fk_resources_description
+		FOREIGN KEY ( resource_description_id ) REFERENCES defs.strings ,
+	ADD CONSTRAINT fk_resources_category
+		FOREIGN KEY ( resource_category_id ) REFERENCES defs.strings;
+
+
+
+/*
+ * Natural resources
+ * ------------------
+ * 
+ * This table contains additional resource information that is used for
+ * natural resources only. These fields are used by the universe generator
+ * when it creates resource providers. 
+ */
+CREATE TABLE defs.natural_resources (
+	/* Identifier of the resource definition for this natural resource, which
+	 * also serves as the primary key for this table.
+	 */
+	resource_name_id			INT NOT NULL PRIMARY KEY ,
+
+	/* Presence probability, used by the universe generator to determine
+	 * whether some type of resource should be added to a planet or not. The
+	 * generator will try to enforce this probability on the universe in
+	 * general (that is, not assigning the resource to a planet will increase
+	 * the probability of it being assigned to another).
+	 */
+	natres_p_presence		DOUBLE PRECISION NOT NULL
+								CHECK ( natres_p_presence > 0
+									AND natres_p_presence < 1 ) ,
+
+	/* Average quantity in resource providers. */
+	natres_quantity_avg		DOUBLE PRECISION NOT NULL
+								CHECK( natres_quantity_avg > 0 ) ,
+	/* Maximal deviation from the average quantity. */
+	natres_quantity_dev		DOUBLE PRECISION NOT NULL
+								CHECK( natres_quantity_dev >= 0 ) ,
+
+	/* Average extraction difficulty assigned to resource providers */
+	natres_difficulty_avg	DOUBLE PRECISION NOT NULL
+								CHECK( natres_difficulty_avg BETWEEN 0 AND 1 ) ,
+	/* Maximal deviation from the average extraction difficulty */
+	natres_difficulty_dev	DOUBLE PRECISION NOT NULL
+								CHECK( natres_difficulty_dev >= 0 ) ,
+
+	/* Average recovery rate for resource providers */
+	natres_recovery_avg		DOUBLE PRECISION NOT NULL
+								CHECK( natres_recovery_avg > 0
+									AND natres_recovery_avg <= 1 ) ,
+	/* Maximal deviation from the average recovery rate */
+	natres_recovery_dev		DOUBLE PRECISION NOT NULL
+								CHECK( natres_recovery_dev >= 0 ) ,
+	
+	/*
+	 * For all values which include both an average and a maximal deviation,
+	 * make sure the range defined by the deviation is still within valid
+	 * boundaries for the value in question.
+	 */
+	CHECK( natres_quantity_avg - natres_quantity_dev > 0 ) ,
+	CHECK( natres_difficulty_avg + natres_difficulty_dev <= 1 ) ,
+	CHECK( natres_difficulty_avg - natres_difficulty_dev >= 0 ) ,
+	CHECK( natres_recovery_avg + natres_recovery_dev <= 1 ) ,
+	CHECK( natres_recovery_avg - natres_recovery_dev > 0 )
+);
+
+ALTER TABLE defs.natural_resources
+	ADD CONSTRAINT fk_natres_resource
+		FOREIGN KEY ( resource_name_id ) REFERENCES defs.resources;
diff --git a/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql b/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
index fb8f379..58ddf52 100644
--- a/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
+++ b/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
@@ -42,6 +42,67 @@ ALTER TABLE verse.planets
 		FOREIGN KEY (system_id) REFERENCES verse.systems;
 
 
+
+/*
+ * Resource providers
+ * -------------------
+ * 
+ * A resource provider allows natural resources to be extracted. Resource
+ * providers are initialised by the universe generator and updated when
+ * mining results are computed.
+ * 
+ * For now, resource providers are bound to planets. Each planet may only
+ * have one provider for a given type of natural resource.
+ */
+CREATE TABLE verse.resource_providers(
+
+	/* The identifier of the planet the resource provider is bound to. */
+	planet_id				INT NOT NULL ,
+
+	/* The identifier of the natural resource that can be mined from this
+	 * resource provider.
+	 */
+	resource_name_id		INT NOT NULL ,
+
+	/* The maximal quantity of resource units in the provider */ 
+	resprov_quantity_max	DOUBLE PRECISION NOT NULL
+								CHECK( resprov_quantity_max > 0 ) ,
+	/* The current quantity of resources */
+	resprov_quantity		DOUBLE PRECISION NOT NULL
+								CHECK( resprov_quantity >= 0 ) ,
+
+	/* The extraction difficulty, which affects the amount of work required to
+	 * extract resources.
+	 */
+	resprov_difficulty		DOUBLE PRECISION NOT NULL
+								CHECK( resprov_difficulty BETWEEN 0 AND 1 ) ,
+
+	/* The provider's recovery rate, which determines how fast the provider's
+	 * resources are regenerated.
+	 */
+	resprov_recovery		DOUBLE PRECISION NOT NULL
+								CHECK( resprov_recovery > 0
+									AND resprov_recovery <= 1 ) ,
+
+	/* Primary key on (planet,type of resource) */
+	PRIMARY KEY( planet_id , resource_name_id ) ,
+
+	/* Make sure the quantity is always equal or smaller than the maximal
+	 * quantity.
+	 */
+	CHECK( resprov_quantity <= resprov_quantity_max )
+);
+
+CREATE INDEX idx_resprov_resource
+	ON verse.resource_providers( resource_name_id );
+
+ALTER TABLE verse.resource_providers
+	ADD CONSTRAINT fk_resprov_planet
+		FOREIGN KEY ( planet_id ) REFERENCES verse.planets ,
+	ADD CONSTRAINT fk_resprov_resource
+		FOREIGN KEY ( resource_name_id ) REFERENCES defs.natural_resources;
+
+
 --
 -- Happiness
 --
diff --git a/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql b/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
index 5a3d00a..a92e197 100644
--- a/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
+++ b/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
@@ -24,6 +24,49 @@ ALTER TABLE emp.empires
 		FOREIGN KEY (name_id) REFERENCES naming.empire_names;
 
 
+/*
+ * Empire resources
+ * -----------------
+ * 
+ * This table contains the list of resources possessed and owed by each
+ * empire.
+ */
+
+CREATE TABLE emp.resources(
+	/* Identifier of the empire */
+	empire_id			INT NOT NULL ,
+	
+	/* Identifier of the type of resource */
+	resource_name_id	INT NOT NULL ,
+
+	/* Amount of that specific resource possessed by the empire */
+	empres_possessed	DOUBLE PRECISION NOT NULL
+							DEFAULT 0
+							CHECK( empres_possessed >= 0 ) ,
+
+	/* Amount of that specific resource owed by the empire. This value is
+	 * used to accumulate debts and that, in turn, is used when computing
+	 * debt-related fleet and building destruction.
+	 */
+	empres_owed			DOUBLE PRECISION NOT NULL
+							DEFAULT 0
+							CHECK( empres_owed >= 0 ) ,
+
+	/* There is only one entry for each (empire,type of resource) pair */
+	PRIMARY KEY( empire_id , resource_name_id )
+);
+
+CREATE INDEX idx_empres_resource
+	ON emp.resources ( resource_name_id );
+
+ALTER TABLE emp.resources
+	ADD CONSTRAINT fk_empres_empire
+		FOREIGN KEY ( empire_id ) REFERENCES emp.empires
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_empres_resource
+		FOREIGN KEY ( resource_name_id ) REFERENCES defs.resources;
+
+
 --
 -- Empire technologies
 --
@@ -69,6 +112,91 @@ ALTER TABLE emp.planets
 			ON DELETE CASCADE;
 
 
+/*
+ * Empire mining settings
+ * -----------------------
+ * 
+ * This table is used to store general empire mining settings. When the empire
+ * gains control over a new planet, or if the planet does not have specific
+ * settings, these settings are used to determine the amount of work units
+ * that go into extracting each type of resource.
+ * 
+ * Empire-wide settings are ignored if the planet has specific settings, or if
+ * none of the resources present on the planet have a positive weight (in
+ * which case all present resources are extracted as if they had the same
+ * weight).
+ */
+
+CREATE TABLE emp.mining_settings(
+	/* Identifier of the empire */
+	empire_id			INT NOT NULL ,
+
+	/* Identifier of the type of resources */
+	resource_name_id	INT NOT NULL ,
+
+	/* Weight to give to this type of resource when there are no planet-
+	 * specific settings.
+	 */ 
+	empmset_weight		INT NOT NULL
+							DEFAULT 1
+							CHECK( empmset_weight >= 0 ) ,
+
+	/* Primary key on (empire,resource type) pairs */
+	PRIMARY KEY( empire_id , resource_name_id )
+);
+
+CREATE INDEX idx_empmset_resource
+	ON emp.mining_settings ( resource_name_id );
+
+ALTER TABLE emp.mining_settings
+	ADD CONSTRAINT fk_empmset_empire
+		FOREIGN KEY ( empire_id ) REFERENCES emp.empires
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_empmset_resource
+		FOREIGN KEY ( resource_name_id ) REFERENCES defs.natural_resources;
+
+
+/*
+ * Planet-specific mining settings
+ * --------------------------------
+ * 
+ * Empire may set planet-specific mining settings when they own a planet. Even
+ * once the empire abandons the planet (or when it is taken away), the
+ * settings are kept in the database and restored if the empire takes control
+ * over the planet again.
+ */
+
+CREATE TABLE emp.planet_mining_settings(
+	/* Identifier of the empire */
+	empire_id			INT NOT NULL ,
+
+	/* The identifier of the planet */
+	planet_id				INT NOT NULL ,
+
+	/* Identifier of the type of resources */
+	resource_name_id	INT NOT NULL ,
+
+	/* Weight to give to this type of resource */ 
+	emppmset_weight		INT NOT NULL
+							DEFAULT 1
+							CHECK( emppmset_weight >= 0 ) ,
+
+	/* Primary key on (empire,resource type) pairs */
+	PRIMARY KEY( empire_id , planet_id , resource_name_id )
+);
+
+CREATE INDEX idx_emppmset_provider
+	ON emp.planet_mining_settings ( planet_id , resource_name_id );
+
+ALTER TABLE emp.planet_mining_settings
+	ADD CONSTRAINT fk_emppmset_empire
+		FOREIGN KEY ( empire_id ) REFERENCES emp.empires
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_emppmset_resource
+		FOREIGN KEY ( planet_id , resource_name_id )
+			REFERENCES verse.resource_providers;
+
+
 --
 -- Planets being abandonned
 --
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07500-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07500-resources.sql
new file mode 100644
index 0000000..216b857
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07500-resources.sql
@@ -0,0 +1,225 @@
+/*
+ * Test constraints and foreign keys on defs.resources
+ */
+BEGIN;
+	
+	/* We need a few strings to be used when creating resource definitions. */
+	\i utils/strings.sql
+	SELECT _create_test_strings( 6 );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 17 );
+
+	
+	/* Valid resource definition, no category */
+	SELECT diag_test_name( 'Valid resource definition without category' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( 'test1' ) , _get_string( 'test2' ) , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.resources;
+
+	/* Resource with valid fields, including a category */
+	SELECT diag_test_name( 'Valid resource definition with category' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_get_string( 'test1' ) , _get_string( 'test2' ) ,
+			_get_string( 'test3' ) , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.resources;
+
+
+	/* Resource definition with an invalid name */
+	SELECT diag_test_name( 'Resource definition with an invalid name' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_bad_string( ) , _get_string( 'test2' ) , 1
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+	/* Resource definition with a NULL name */
+	SELECT diag_test_name( 'Resource definition with a NULL name' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			NULL , _get_string( 'test2' ) , 1
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+	/* Resource definition with an invalid description */
+	SELECT diag_test_name( 'Resource definition with an invalid description' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( 'test1' ) , _get_bad_string( ) , 1
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+	/* Resource definition with a NULL description */
+	SELECT diag_test_name( 'Resource definition with a NULL description' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( 'test1' ) , NULL , 1
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+	/* Resource definition with an invalid category */
+	SELECT diag_test_name( 'Resource definition with an invalid category' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_get_string( 'test1' ) , _get_string( 'test2' ) ,
+			_get_bad_string( ) , 1
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	/* Resource definition with an invalid weight */
+	SELECT diag_test_name( 'Resource definition with an invalid weight' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( 'test1' ) , _get_string( 'test2' ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	/* Resource definition with a NULL weight */
+	SELECT diag_test_name( 'Resource definition with a NULL weight' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( 'test1' ) , _get_string( 'test2' ) , NULL
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+	/* Resource definitions using the same name */
+	INSERT INTO defs.resources (
+		resource_name_id , resource_description_id , resource_weight
+	) VALUES (
+		_get_string( 'test1' ) , _get_string( 'test2' ) , 1
+	);
+	SELECT diag_test_name( 'Resource definitions using the same name' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( 'test1' ) , _get_string( 'test3' ) , 1
+		);
+	SELECT throws_ok( '_test_this' , 23505 );
+	DEALLOCATE ALL;
+
+	/* Resource definitions using the same description */
+	SELECT diag_test_name( 'Resource definitions using the same description' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( 'test3' ) , _get_string( 'test2' ) , 1
+		);
+	SELECT throws_ok( '_test_this' , 23505 );
+	DEALLOCATE ALL;
+
+	/* Resources with distinct names and descriptions */
+	SELECT diag_test_name( 'Resources with distinct names and descriptions' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( 'test3' ) , _get_string( 'test4' ) , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.resources;
+
+
+	/* Resources with distinct categories */
+	INSERT INTO defs.resources (
+		resource_name_id , resource_description_id ,
+		resource_category_id , resource_weight
+	) VALUES (
+		_get_string( 'test1' ) , _get_string( 'test2' ) ,
+		_get_string( 'test3' ) , 1
+	);
+	SELECT diag_test_name( 'Resources with distinct categories' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_get_string( 'test4' ) , _get_string( 'test5' ) ,
+			_get_string( 'test6' ) , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.resources WHERE resource_name_id = _get_string( 'test4' );
+
+	/* Resources in the same category */
+	SELECT diag_test_name( 'Resources in the same category' );
+	PREPARE _test_this AS
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_get_string( 'test4' ) , _get_string( 'test5' ) ,
+			_get_string( 'test3' ) , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+
+
+	/* Resource definition name deletion impossible */
+	SELECT diag_test_name( 'Resource definition name deletion impossible' );
+	PREPARE _test_this AS
+		DELETE FROM defs.strings WHERE id = _get_string( 'test1' );
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+	/* Resource definition description deletion impossible */
+	SELECT diag_test_name( 'Resource definition description deletion impossible' );
+	PREPARE _test_this AS
+		DELETE FROM defs.strings WHERE id = _get_string( 'test2' );
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+	/*
+	 * 17/ Make sure it is impossible to delete a string definition used as
+	 *     a resource category
+	 */
+	SELECT diag_test_name( 'Resource definition description deletion impossible' );
+	PREPARE _test_this AS
+		DELETE FROM defs.strings WHERE id = _get_string( 'test3' );
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07501-natural-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07501-natural-resources.sql
new file mode 100644
index 0000000..b38b69c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07501-natural-resources.sql
@@ -0,0 +1,474 @@
+/*
+ * Test constraints and foreign keys on defs.natural_resources
+ */
+BEGIN;
+	
+	/* We need a few strings to be used when creating resource definitions. */
+	\i utils/strings.sql
+	SELECT _create_test_strings( 4 );
+	
+	/*
+	 * We need a a pair of resource definitions to be used when creating
+	 * natural resources.
+	 */
+	INSERT INTO defs.resources (
+		resource_name_id , resource_description_id , resource_weight
+	) VALUES ( _get_string( 'test1' ) , _get_string( 'test2' ) , 1 );
+	INSERT INTO defs.resources (
+		resource_name_id , resource_description_id , resource_weight
+	) VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 2 );
+
+	
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 33 );
+
+
+	SELECT diag_test_name( 'Valid natural resource definition' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources (
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT lives_ok( '_test_this' );
+
+	SELECT diag_test_name( 'Duplicate natural resource definition' );
+	SELECT throws_ok( '_test_this' , 23505 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Distinct natural resource definitions' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources (
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test3' ) , 0.5 , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.natural_resources;
+
+
+	SELECT diag_test_name( 'Natural resource definition with missing resource definition' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test2' ) , 0.5 , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with NULL resource definition' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			NULL , 0.5 , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	
+	SELECT diag_test_name( 'Natural resource definition with presence probability <= 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0 , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with presence probability >= 1' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 1 , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with NULL presence probability' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , NULL , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Natural resource definition with avg. quantity <= 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 0 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with NULL avg. quantity' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , NULL , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+
+	SELECT diag_test_name( 'Natural resource definition with quantity dev. < 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , -1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with NULL quantity dev.' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , NULL , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with quantity dev. = avg. quantity' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 100 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with quantity dev. = 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 0 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.natural_resources;
+
+
+
+	SELECT diag_test_name( 'Natural resource definition with avg. difficulty = 1' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 1 , 0 , 0.5 , 0.05
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.natural_resources;
+
+	SELECT diag_test_name( 'Natural resource definition with avg. difficulty = 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0 , 0 , 0.5 , 0.05
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.natural_resources;
+
+	SELECT diag_test_name( 'Natural resource definition with avg. difficulty > 1' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 1.0001 , 0 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Natural resource definition with avg. difficulty < 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , -0.0001 , 0 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with NULL avg. difficulty' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , NULL , 0.05 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+
+	SELECT diag_test_name( 'Natural resource definition with difficulty dev. < 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , -1 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with avg. difficulty - difficulty dev. < 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.25 , 0.5 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with difficulty dev. + avg. difficulty > 1' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.75 , 0.5 , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with NULL difficulty dev.' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , NULL , 0.5 , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Natural resource definition with avg. recovery = 1' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			1 , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.natural_resources;
+
+	SELECT diag_test_name( 'Natural resource definition with avg. recovery = 0+' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			0.0001 , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM defs.natural_resources;
+
+	SELECT diag_test_name( 'Natural resource definition with avg. recovery > 1' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			1.1 , 0
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with avg. recovery <= 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			0 , 0
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with NULL avg. recovery' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			NULL , 0.05
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Natural resource definition with recovery dev. < 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			0.5 , -1
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with avg.recovery - recovery dev. <= 0' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with avg.recovery - recovery dev. > 1' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			0.75 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Natural resource definition with NULL recovery dev.' );
+	PREPARE _test_this AS
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 ,
+			0.5 , NULL
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Deletion of the base definition for a natural resource' );
+	INSERT INTO defs.natural_resources(
+		resource_name_id , natres_p_presence , natres_quantity_avg ,
+		natres_quantity_dev , natres_difficulty_avg ,
+		natres_difficulty_dev , natres_recovery_avg ,
+		natres_recovery_dev
+	) VALUES (
+		_get_string( 'test1' ) , 0.5 , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+	);
+	PREPARE _test_this AS
+		DELETE FROM defs.resources WHERE resource_name_id = _get_string( 'test1' );
+	SELECT throws_ok( '_test_this' , 23503 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/10003-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/10003-resource-providers.sql
new file mode 100644
index 0000000..9e31d1d
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/10003-resource-providers.sql
@@ -0,0 +1,331 @@
+/*
+ * Test constraints and foreign keys on verse.resource_providers
+ */
+BEGIN;
+	
+	/* We need a pair of resource definitions and a pair of planets. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'testResource' );
+	SELECT _create_raw_planets( 2 , 'testPlanet' );
+	
+	
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 22 );
+
+	
+	SELECT diag_test_name( 'Valid resource provider' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0.5 , 0.5
+		);
+	SELECT lives_ok( '_test_this' );
+
+	SELECT diag_test_name( 'Duplicate resource provider' );
+	SELECT throws_ok( '_test_this' , 23505 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with same planet but different type' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource2' ) ,
+			100 , 50 ,
+			0.5 , 0.5
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with same type but different planet' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet2' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0.5 , 0.5
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM verse.resource_providers;
+
+
+	SELECT diag_test_name( 'Resource provider with NULL planet identifier' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			NULL , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+	
+	SELECT diag_test_name( 'Resource provider with invalid planet identifier' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_bad_map_name( ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+	
+	SELECT diag_test_name( 'Resource provider with NULL resource identifier' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , NULL ,
+			100 , 50 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+	
+	SELECT diag_test_name( 'Resource provider with invalid resource identifier' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_bad_string( ) ,
+			100 , 50 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Resource provider with NULL maximal quantity' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			NULL , 50 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with maximal quantity <= 0' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			0 , 0 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Resource provider with NULL quantity' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , NULL ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with quantity < 0' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , -1 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with quantity > max. quantity' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 101 ,
+			0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Resource provider with NULL difficulty' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			NULL , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with difficulty < 0' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			-0.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with difficulty > 1' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			1.5 , 0.5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+	
+	SELECT diag_test_name( 'Resource provider with difficulty = 0' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0 , 0.5
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM verse.resource_providers;
+
+	SELECT diag_test_name( 'Resource provider with difficulty = 1' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			1 , 0.5
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM verse.resource_providers;
+
+
+	SELECT diag_test_name( 'Resource provider with NULL recovery' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0.5 , NULL
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with recovery = 0' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0.5 , 0
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with recovery > 1' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0.5 , 1.5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Resource provider with recovery = 1' );
+	PREPARE _test_this AS
+		INSERT INTO verse.resource_providers(
+				planet_id , resource_name_id ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			100 , 50 ,
+			0.5 , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM verse.resource_providers;
+
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11001-empire-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11001-empire-resources.sql
new file mode 100644
index 0000000..06ace16
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11001-empire-resources.sql
@@ -0,0 +1,191 @@
+/*
+ * Test constraints and foreign keys on emp.resources
+ */
+BEGIN;
+
+	/* We need to create a pair of resources and a pair of empires */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	SELECT _create_natural_resources( 2 , 'testResource' );
+	SELECT _create_emp_names( 2 , 'testUser' );
+	INSERT INTO emp.empires ( name_id , cash )
+		SELECT id , 0 FROM naming.empire_names;
+
+	
+	
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 16 );
+
+
+	SELECT diag_test_name( 'Valid empire resources record' );
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) ,
+			1 , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DELETE FROM emp.resources;
+	DEALLOCATE ALL;
+
+	INSERT INTO emp.resources (
+		empire_id , resource_name_id
+	) VALUES (
+		_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' )
+	);
+	SELECT diag_test_name( 'Default possessed value in empire resources record' );
+	SELECT results_eq(
+		$$ SELECT empres_possessed FROM emp.resources $$ ,
+		$$ VALUES ( 0::DOUBLE PRECISION ) $$
+	);
+	SELECT diag_test_name( 'Default owed value in empire resources record' );
+	SELECT results_eq(
+		$$ SELECT empres_owed FROM emp.resources $$ ,
+		$$ VALUES ( 0::DOUBLE PRECISION ) $$
+	);
+	DELETE FROM emp.resources;
+
+	
+	SELECT diag_test_name( 'NULL empire identifier in empire resources record' );
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			NULL , _get_string( 'testResource1' ) ,
+			1 , 1
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Invalid empire identifier in empire resources record' );
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			_get_bad_emp_name( ) , _get_string( 'testResource1' ) ,
+			1 , 1
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'NULL resource identifier in empire resources record' );
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , NULL ,
+			1 , 1
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Invalid resource identifier in empire resources record' );
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_bad_string( ) ,
+			1 , 1
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+	
+	SELECT diag_test_name( 'Duplicate empire resources record' );
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) ,
+			1 , 1
+		);
+	EXECUTE _test_this;
+	SELECT throws_ok( '_test_this' , 23505 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire resources record with same empire but different types of resources' );
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource2' ) ,
+			1 , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire resources record with different empires but same type of resources' );
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			_get_emp_name( 'testUser2' ) , _get_string( 'testResource1' ) ,
+			1 , 1
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM emp.resources;
+
+
+	INSERT INTO emp.resources (
+		empire_id , resource_name_id
+	) VALUES (
+		_get_emp_name( 'testUser2' ) , _get_string( 'testResource1' )
+	);
+	SELECT diag_test_name( 'Empire deletion succeeds when empire resources records are present' );
+	PREPARE _test_this AS
+		DELETE FROM emp.empires WHERE name_id = _get_emp_name( 'testUser2' );
+	SELECT lives_ok( '_test_this' );
+	SELECT diag_test_name( 'Empire deletion causes empire resources record deletion' );
+	SELECT is_empty( 'SELECT * FROM emp.resources' );
+	DEALLOCATE ALL;
+
+
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , NULL
+		);
+	SELECT diag_test_name( 'NULL possessed value in empire resources record' );
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_possessed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , -1
+		);
+	SELECT diag_test_name( 'Negative possessed value in empire resources record' );
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_owed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , NULL
+		);
+	SELECT diag_test_name( 'NULL owed value in empire resources record' );
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	PREPARE _test_this AS
+		INSERT INTO emp.resources (
+			empire_id , resource_name_id , empres_owed
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , -1
+		);
+	SELECT diag_test_name( 'Negative owed value in empire resources record' );
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11002-empire-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11002-empire-mining-settings.sql
new file mode 100644
index 0000000..8fb1158
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11002-empire-mining-settings.sql
@@ -0,0 +1,119 @@
+/*
+ * Test constraints and foreign keys on emp.mining_settings
+ */
+BEGIN;
+
+	/* We need to create a pair of resources and a pair of empires */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	SELECT _create_natural_resources( 2 , 'testResource' );
+	SELECT _create_emp_names( 2 , 'testUser' );
+	INSERT INTO emp.empires ( name_id , cash )
+		SELECT id , 0 FROM naming.empire_names;
+	
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 10 );
+	
+	
+	SELECT diag_test_name( 'Valid empire mining settings record' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	
+	SELECT diag_test_name( 'Duplicate empire mining settings record' );
+	SELECT throws_ok( '_test_this' , 23505 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire mining settings records with same empire but different types' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource2' ) , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire mining settings records with same type but different empires' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser2' ) , _get_string( 'testResource1' ) , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM emp.mining_settings;
+
+
+	SELECT diag_test_name( 'Empire mining settings record with NULL empire identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			NULL , _get_string( 'testResource1' ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire mining settings record with invalid empire identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_bad_emp_name( ) , _get_string( 'testResource1' ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Empire mining settings record with NULL resource identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , NULL , 0
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire mining settings record with invalid resource identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_bad_string( ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Empire mining settings record with NULL weight' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , NULL
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire mining settings record with weight < 0' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , -1
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11003-empire-planet-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11003-empire-planet-mining-settings.sql
new file mode 100644
index 0000000..ea1e154
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11003-empire-planet-mining-settings.sql
@@ -0,0 +1,193 @@
+/*
+ * Test constraints and foreign keys on emp.mining_settings
+ */
+BEGIN;
+
+	/* We need to create a pair of resources, a pair of empires, a pair of planets, and resource providers */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'testResource' );
+	SELECT _create_emp_names( 2 , 'testUser' );
+	INSERT INTO emp.empires ( name_id , cash )
+		SELECT id , 0 FROM naming.empire_names;
+	SELECT _create_raw_planets( 2 , 'testPlanet' );
+	SELECT _create_resource_provider( 'testPlanet1' , 'testResource1' );
+	SELECT _create_resource_provider( 'testPlanet1' , 'testResource2' );
+	SELECT _create_resource_provider( 'testPlanet2' , 'testResource1' );
+	-- No provider for testResource2 on testPlanet2
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 14 );
+
+
+	SELECT diag_test_name( 'Valid empire planet mining settings record' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet1' ) ,
+			_get_string( 'testResource1' ) , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	
+	SELECT diag_test_name( 'Duplicate empire planet mining settings record' );
+	SELECT throws_ok( '_test_this' , 23505 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire planet mining settings records with different types' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet1' ) ,
+			_get_string( 'testResource2' ) , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire planet mining settings records with different empires' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser2' ) , _get_map_name( 'testPlanet1' ) ,
+			_get_string( 'testResource1' ) , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire planet mining settings records with different planets' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet2' ) ,
+			_get_string( 'testResource1' ) , 0
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+	DELETE FROM emp.mining_settings;
+
+
+	SELECT diag_test_name( 'Empire planet mining setting record with NULL empire identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			NULL , _get_map_name( 'testPlanet2' ) ,
+			_get_string( 'testResource1' ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire planet mining setting record with invalid empire identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_bad_emp_name( ) , _get_map_name( 'testPlanet2' ) ,
+			_get_string( 'testResource1' ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Empire planet mining setting record with NULL planet identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , NULL ,
+			_get_string( 'testResource1' ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire planet mining setting record with invalid planet identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_bad_map_name( ) ,
+			_get_string( 'testResource1' ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Empire planet mining setting record with NULL resource identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet2' ) ,
+			NULL , 0
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire planet mining setting record with invalid resource identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet2' ) ,
+			_get_bad_string( ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Empire planet mining setting record with invalid resource provider identifier' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet2' ) ,
+			_get_string( 'testResource2' ) , 0
+		);
+	SELECT throws_ok( '_test_this' , 23503 );
+	DEALLOCATE ALL;
+
+
+	SELECT diag_test_name( 'Empire planet mining setting record with NULL weight' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet1' ) ,
+			_get_string( 'testResource1' ) , NULL
+		);
+	SELECT throws_ok( '_test_this' , 23502 );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'Empire planet mining setting record with weight < 0' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet1' ) ,
+			_get_string( 'testResource1' ) , -1
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/accounts.sql b/legacyworlds-server-data/db-structure/tests/utils/accounts.sql
new file mode 100644
index 0000000..b93c82a
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/utils/accounts.sql
@@ -0,0 +1,68 @@
+/*
+ * Utility functions used by unit tests
+ *
+ * User accounts
+ */
+
+
+/*
+ * Find a test address
+ */
+CREATE FUNCTION _find_address( TEXT ) RETURNS INT AS $$
+	SELECT id FROM users.addresses WHERE address = $1 || '@example.org';
+$$ LANGUAGE SQL;
+
+
+/*
+ * Create a set of user addresses using some prefix
+ */
+CREATE FUNCTION _create_addresses( _quantity INT , _prefix TEXT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	i	INT;
+BEGIN
+	i := 0;
+	WHILE i < _quantity
+	LOOP
+		i := i + 1;
+		BEGIN
+			INSERT INTO users.addresses ( address )
+				VALUES ( _prefix || i::TEXT || '@example.org' );
+		EXCEPTION
+			WHEN unique_violation THEN
+				-- Address already exists, that's nice
+		END;
+	END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+
+/*
+ * Create a set of user accounts
+ */
+CREATE FUNCTION _create_accounts( _quantity INT , _prefix TEXT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	i	INT;
+BEGIN
+	PERFORM _create_test_strings( 0 );
+	PERFORM _create_addresses( _quantity , _prefix );
+	i := 0;
+	WHILE i < _quantity
+	LOOP
+		i := i + 1;
+		BEGIN
+			INSERT INTO users.credentials (
+				address_id , pass_md5 , pass_sha1 , language_id , credits
+			) VALUES (
+				_find_address( _prefix || i::TEXT ) , '' , '' , _get_language( 't' ) , 0
+			);
+		EXCEPTION
+			WHEN unique_violation THEN
+				-- Account already exists
+		END;
+	END LOOP;
+END;
+$$ LANGUAGE plpgsql;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/naming.sql b/legacyworlds-server-data/db-structure/tests/utils/naming.sql
new file mode 100644
index 0000000..56efb0a
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/utils/naming.sql
@@ -0,0 +1,93 @@
+/*
+ * Utility functions used by unit tests
+ *
+ * Naming system
+ */
+
+
+/*
+ * Obtain a map name identifier
+ */
+CREATE FUNCTION _get_map_name( TEXT ) RETURNS INT AS $$
+	SELECT id FROM naming.map_names WHERE name = $1;
+$$ LANGUAGE SQL;
+
+
+/*
+ * Obtain a map name identifier that does not exist
+ */
+CREATE FUNCTION _get_bad_map_name( ) RETURNS INT AS $$
+	SELECT MAX( id ) + 1 FROM naming.map_names;
+$$ LANGUAGE SQL;
+
+
+/*
+ * Create a few map names using a prefix
+ */
+CREATE FUNCTION _create_map_names( _quantity INT , _prefix TEXT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	i	INT;
+BEGIN
+	i := 0;
+	WHILE i < _quantity
+	LOOP
+		i := i + 1;
+		BEGIN
+			INSERT INTO naming.map_names (name) VALUES ( _prefix || i::TEXT );
+		EXCEPTION
+			WHEN unique_violation THEN
+				-- Ignore the error
+		END;
+	END LOOP;
+END;
+$$ LANGUAGE PLPGSQL;
+
+
+/*
+ * Get the empire that belongs to some user, based on that user's email
+ */
+CREATE FUNCTION _get_emp_name( TEXT ) RETURNS INT AS $$
+	SELECT id FROM naming.empire_names WHERE owner_id = _find_address( $1 );
+$$ LANGUAGE SQL;
+
+
+/*
+ * Obtain a map name identifier that does not exist
+ */
+CREATE FUNCTION _get_bad_emp_name( ) RETURNS INT AS $$
+	SELECT MAX( id ) + 1 FROM naming.empire_names;
+$$ LANGUAGE SQL;
+
+
+/*
+ * Create a few empire names using a prefix for user accounts.
+ * Empires are named "testX" independently of the user accounts.
+ */
+CREATE FUNCTION _create_emp_names( _quantity INT , _prefix TEXT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	i	INT;
+	j	INT;
+BEGIN
+	PERFORM _create_accounts( _quantity , _prefix );
+	i := 0;
+	WHILE i < _quantity
+	LOOP
+		i := i + 1;
+		j := 0;
+		LOOP
+			BEGIN
+				INSERT INTO naming.empire_names ( owner_id , name )
+					VALUES ( _find_address( _prefix || i::TEXT ) , 'test' || j::TEXT );
+				EXIT;
+			EXCEPTION
+				WHEN unique_violation THEN
+					j := j + 1;
+			END;
+		END LOOP;
+	END LOOP;
+END;
+$$ LANGUAGE PLPGSQL;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/resources.sql b/legacyworlds-server-data/db-structure/tests/utils/resources.sql
new file mode 100644
index 0000000..8cc3415
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/utils/resources.sql
@@ -0,0 +1,63 @@
+/*
+ * Utility functions used by unit tests
+ *
+ * Resources and natural resources
+ */
+
+
+/*
+ * Function that creates some quantity of resources
+ * All resources are created using the specified prefix for the strings
+ */
+CREATE FUNCTION _create_resources( _quantity INT , _prefix TEXT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	i	INT;
+BEGIN
+	PERFORM _create_test_strings( _quantity , _prefix );
+	PERFORM _create_test_strings( _quantity , _prefix || 'Description' );
+	
+	i := 0;
+	WHILE i < _quantity
+	LOOP
+		i := i + 1;
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			_get_string( _prefix || i::TEXT ) ,
+			_get_string( _prefix || 'Description' || i::TEXT ) ,
+			i
+		);
+	END LOOP;
+END;
+$$ LANGUAGE PLPGSQL;
+
+
+/*
+ * Function that creates some quantity of /natural/ resources
+ * All resources are created using the specified prefix for the strings
+ */
+CREATE FUNCTION _create_natural_resources( _quantity INT , _prefix TEXT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	i	INT;
+BEGIN
+	PERFORM _create_resources( _quantity , _prefix );
+
+	i := 0;
+	WHILE i < _quantity
+	LOOP
+		i := i + 1;
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence , natres_quantity_avg ,
+			natres_quantity_dev , natres_difficulty_avg ,
+			natres_difficulty_dev , natres_recovery_avg ,
+			natres_recovery_dev
+		) VALUES (
+			_get_string( _prefix || i::TEXT ) , 0.5 , 100 , 1 , 0.5 , 0.05 , 0.5 , 0.05
+		);
+	END LOOP;
+END;
+$$ LANGUAGE PLPGSQL;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/strings.sql b/legacyworlds-server-data/db-structure/tests/utils/strings.sql
new file mode 100644
index 0000000..33ead96
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/utils/strings.sql
@@ -0,0 +1,65 @@
+/*
+ * Utility functions used by unit tests
+ *
+ * I18N string creation and access
+ */
+
+
+/*
+ * Function that returns an invalid string identifier.
+ */
+CREATE FUNCTION _get_bad_string( ) RETURNS INT AS $$
+	SELECT MAX( id ) + 1 FROM defs.strings;
+$$ LANGUAGE SQL;
+
+
+/*
+ * Function that returns a language's identifier
+ */
+CREATE FUNCTION _get_language( TEXT ) RETURNS INT AS $$
+	SELECT id FROM defs.languages WHERE language = $1;
+$$ LANGUAGE SQL;
+
+
+/*
+ * Function that returns a string's identifier
+ */
+CREATE FUNCTION _get_string( TEXT ) RETURNS INT AS $$
+	SELECT id FROM defs.strings WHERE name = $1;
+$$ LANGUAGE SQL;
+
+
+/*
+ * Function that creates some quantity of test strings
+ */
+CREATE FUNCTION _create_test_strings( _quantity INT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	i	INT;
+BEGIN
+	PERFORM _create_test_strings( _quantity , 'test' );
+END;
+$$ LANGUAGE PLPGSQL;
+
+
+/*
+ * Function that creates some quantity of test strings using a specific prefix
+ */
+CREATE FUNCTION _create_test_strings( _quantity INT , _prefix TEXT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	i	INT;
+BEGIN
+	PERFORM defs.uoc_language( 't' , 'Test' );
+
+	i := 0;
+	WHILE i < _quantity
+	LOOP
+		i := i + 1;
+		PERFORM defs.uoc_translation( 't' , _prefix || i::TEXT ,
+			'Test string #' || i::TEXT );
+	END LOOP;
+END;
+$$ LANGUAGE PLPGSQL;
diff --git a/legacyworlds-server-data/db-structure/tests/utils/universe.sql b/legacyworlds-server-data/db-structure/tests/utils/universe.sql
new file mode 100644
index 0000000..8a2a4bc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/utils/universe.sql
@@ -0,0 +1,71 @@
+/*
+ * Utility functions used by unit tests
+ *
+ * Universe
+ */
+
+/*
+ * Create a new system at some coordinates and return its identifier
+ */
+CREATE FUNCTION _create_system( INT , INT ) RETURNS INT AS $$
+	INSERT INTO verse.systems ( x , y )
+		VALUES ( $1 , $2 )
+		RETURNING id;
+$$ LANGUAGE SQL;
+
+
+/*
+ * Create "raw" planets
+ */
+CREATE FUNCTION _create_raw_planets( _quantity INT , _prefix TEXT )
+		RETURNS VOID
+	AS $$
+DECLARE
+	_system	INT;
+	_orbit	INT;
+	i		INT;
+BEGIN
+	PERFORM _create_map_names( _quantity , _prefix );
+	
+	i := 0;
+	WHILE i < _quantity
+	LOOP
+		i := i + 1;
+		
+		IF _system IS NULL
+		THEN
+			_system := _create_system( i , i );
+			_orbit := 1;
+		END IF;
+		
+		INSERT INTO verse.planets(
+			name_id , system_id , orbit , picture , population
+		) VALUES (
+			_get_map_name( _prefix || i::TEXT ) , _system , _orbit , 1 , 1
+		);
+		
+		IF _orbit = 5
+		THEN
+			_system := NULL;
+		ELSE
+			_orbit := _orbit + 1;
+		END IF;
+	END LOOP;
+END;
+$$ LANGUAGE PLPGSQL;
+
+
+/*
+ * Create a resource provider
+ */
+CREATE FUNCTION _create_resource_provider( TEXT , TEXT ) RETURNS VOID AS $$
+	INSERT INTO verse.resource_providers(
+		planet_id , resource_name_id ,
+		resprov_quantity_max , resprov_quantity ,
+		resprov_difficulty , resprov_recovery
+	) VALUES (
+		_get_map_name( $1 ) , _get_string( $2 ) ,
+		100 , 50 ,
+		0.5 , 0.5
+	);
+$$ LANGUAGE SQL;
\ No newline at end of file
diff --git a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java
index 1301ef0..22b5bda 100644
--- a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java
+++ b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/BuildingOutputType.java
@@ -6,6 +6,7 @@ public enum BuildingOutputType {
 	WORK ,
 	CASH ,
 	DEF ,
-	POP
+	POP ,
+	MINE
 
 }
diff --git a/legacyworlds-server-main/data/buildables.xml b/legacyworlds-server-main/data/buildables.xml
index 5ba9554..5f9ed15 100644
--- a/legacyworlds-server-main/data/buildables.xml
+++ b/legacyworlds-server-main/data/buildables.xml
@@ -5,6 +5,9 @@
 	<building name="milFactory" description="milFactoryDescription" type="WORK" output="1" workers="200">
 		<cost build="100" upkeep="10" work="4800" />
 	</building>
+	<building name="mine" description="mineDescription" type="MINE" output="1" workers="50">
+		<cost build="100" upkeep="10" work="4800" />
+	</building>
 	<building name="turret" description="turretDescription" type="DEF" output="10" workers="5">
 		<cost build="40" upkeep="2" work="600" />
 	</building>
diff --git a/legacyworlds-server-main/data/buildables.xsd b/legacyworlds-server-main/data/buildables.xsd
index a3e80ab..0447edf 100644
--- a/legacyworlds-server-main/data/buildables.xsd
+++ b/legacyworlds-server-main/data/buildables.xsd
@@ -19,6 +19,7 @@
 			<xs:enumeration value="DEF" />
 			<xs:enumeration value="POP" />
 			<xs:enumeration value="WORK" />
+			<xs:enumeration value="MINE" />
 		</xs:restriction>
 	</xs:simpleType>
 
diff --git a/legacyworlds-server-main/data/i18n-text.xml b/legacyworlds-server-main/data/i18n-text.xml
index b148a45..aaf18ca 100644
--- a/legacyworlds-server-main/data/i18n-text.xml
+++ b/legacyworlds-server-main/data/i18n-text.xml
@@ -99,6 +99,12 @@ ${text}
 			<value>Technology has advanced. The ultimate weapon is now available. Claim the awesome power of the Dreadnought and crush your enemies. Ever wanted a tank in space? Well, now you have it. All their base are belong to you.</value>
 		</inline-string>
 
+		<inline-string id="mine">
+			<value>Mine</value>
+		</inline-string>
+		<inline-string id="mineDescription">
+			<value>Exactly what it says on the cover. Not the exploding kind, though. Even if that happens from time to time, anyway.</value>
+		</inline-string>
 		<inline-string id="milFactory">
 			<value>Ship parts factory</value>
 		</inline-string>
@@ -613,6 +619,12 @@ ${text}
 			<value>La technologie a évolué. L'arme ultime est maintenant disponible. Revendiquez la puissance écrasante du cuirassé et pulvérisez vos opposants. Déjà rêvé d'un tank de l'espace ? Eh bien, maintenant, vous l'avez. All their base are belong to you.</value>
 		</inline-string>
 
+		<inline-string id="mine">
+			<value>Mine</value>
+		</inline-string>
+		<inline-string id="mineDescription">
+			<value>Exactement ce qu'il y a marqué sur l'emballage. Enfin, ce n'est pas la variété qui explose. Même si parfois ça se produit quand même.</value>
+		</inline-string>
 		<inline-string id="milFactory">
 			<value>Fabrique de pièces de vaisseaux</value>
 		</inline-string>

From bed784a8e1881189b700a93d1a8a48babe08e061 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 19 Dec 2011 16:00:01 +0100
Subject: [PATCH 07/94] Resource management functions

 * Added defs.uoc_resources() set of functions which create or update
basic resources.

 * Added defs.uoc_natural_resources() set of functions which create or
update natural resources.
---
 .../db-structure/parts/020-functions.sql      |   1 +
 .../functions/025-resources-functions.sql     | 467 ++++++++++++++++++
 .../defs/02500-uoc-resource-internal.sql      |  74 +++
 .../functions/defs/02501-uoc-resource.sql     |  90 ++++
 .../defs/02502-uoc-natres-internal.sql        | 175 +++++++
 .../defs/02503-uoc-natural-resource.sql       | 242 +++++++++
 legacyworlds/doc/TODO.txt                     |   8 +-
 7 files changed, 1056 insertions(+), 1 deletion(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/defs/02500-uoc-resource-internal.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/defs/02501-uoc-resource.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/defs/02502-uoc-natres-internal.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/defs/02503-uoc-natural-resource.sql

diff --git a/legacyworlds-server-data/db-structure/parts/020-functions.sql b/legacyworlds-server-data/db-structure/parts/020-functions.sql
index 5a55389..d97d3cd 100644
--- a/legacyworlds-server-data/db-structure/parts/020-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/020-functions.sql
@@ -11,6 +11,7 @@
 \i parts/functions/005-logs-functions.sql
 \i parts/functions/010-constants-functions.sql
 \i parts/functions/020-naming-functions.sql
+\i parts/functions/025-resources-functions.sql
 \i parts/functions/030-tech-functions.sql
 \i parts/functions/035-users-view.sql
 \i parts/functions/040-empire-functions.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql
new file mode 100644
index 0000000..b401eb0
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql
@@ -0,0 +1,467 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Resource definitions management functions
+--
+-- Copyright(C) 2004-2011, DeepClone Development
+-- --------------------------------------------------------
+
+/*
+ * Return codes for resource creation or update functions.
+ */
+DROP TYPE IF EXISTS defs.resource_update_result CASCADE;
+CREATE TYPE defs.resource_update_result
+	AS ENUM(
+		/* The resource definition was created */
+		'CREATED' ,
+
+		/* The resource definition was updated */
+		'UPDATED' ,
+
+		/* The resource definition already existed, and was either a basic
+		 * resource definition while the update required a natural resource,
+		 * or a natural resource definition when the update required a basic
+		 * resource.
+		 */
+		'BAD_TYPE' ,
+
+		/* The name, description or category string identifiers were not valid
+		 * string identifiers.
+		 */
+		'BAD_STRINGS' ,
+
+		/* One (or more) of the numeric parameters is invalid */
+		'BAD_VALUE' ,
+
+		/* The specified description was in use by another resource */
+		'DUP_DESCR'
+	);
+
+
+
+/*
+ * Create or update a basic resource
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function is called by the variants of defs.uoc_resource() to actually
+ * update or create the resource. It will make sure that all string
+ * identifiers exist, then try to insert the resource. If that fails because
+ * the resource already exists, make sure it's a basic resource then update
+ * it.
+ * 
+ * Parameters:
+ *		_name			the identifier of the resource's name
+ *		_description	the identifier of the resource's description
+ *		_category		the identifier of the resource's category, or NULL if
+ *							the resource does not belong to a category
+ *		_weight			the resource's ordering weight
+ *
+ * Returns:
+ *		?				the result code for the operation
+ */
+CREATE OR REPLACE FUNCTION defs.uoc_resource_internal(
+				_name TEXT ,
+				_description TEXT ,
+				_category TEXT ,
+				_weight INT )
+		RETURNS defs.resource_update_result
+		CALLED ON NULL INPUT
+		VOLATILE
+		SECURITY INVOKER
+	AS $$
+DECLARE
+	_ret		defs.resource_update_result;
+	_name_id	INT;
+	_desc_id	INT;
+	_cat_id		INT;
+BEGIN
+	-- Get all string identifiers
+	SELECT INTO _name_id id FROM defs.strings WHERE name = _name;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+	SELECT INTO _desc_id id FROM defs.strings WHERE name = _description;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+	IF _category IS NULL THEN
+		_cat_id := NULL;
+	ELSE
+		SELECT INTO _cat_id id FROM defs.strings WHERE name = _category;
+		IF NOT FOUND THEN
+			RETURN 'BAD_STRINGS';
+		END IF;
+	END IF;
+
+	-- Try inserting the record
+	BEGIN
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_name_id , _desc_id , _cat_id , _weight
+		);
+		RETURN 'CREATED';
+	EXCEPTION
+		WHEN unique_violation THEN
+			IF SQLERRM LIKE '%_description_%' THEN
+				RETURN 'DUP_DESCR';
+			END IF;
+	END;
+
+	-- Insertion failed, make sure the resource is a basic resource
+	PERFORM *
+		FROM defs.resources basic_res
+			LEFT OUTER JOIN defs.natural_resources nat_res
+				USING ( resource_name_id )
+		WHERE basic_res.resource_name_id = _name_id
+			AND nat_res.resource_name_id IS NULL
+		FOR UPDATE OF basic_res;
+	IF NOT FOUND THEN
+		RETURN 'BAD_TYPE';
+	END IF;
+
+	-- Update the resource
+	BEGIN
+		UPDATE defs.resources
+			SET resource_description_id = _desc_id ,
+				resource_category_id = _cat_id ,
+				resource_weight = _weight
+			WHERE resource_name_id = _name_id;
+		RETURN 'UPDATED';
+	EXCEPTION
+		WHEN unique_violation THEN
+			RETURN 'DUP_DESCR';
+	END;
+EXCEPTION
+	WHEN check_violation THEN
+		RETURN 'BAD_VALUE';
+END;
+$$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.uoc_resource_internal( TEXT , TEXT , TEXT , INT )
+	FROM PUBLIC;
+
+
+
+/*
+ * Update or create a basic resource definition with no category
+ * 
+ * Parameters:
+ *		_name			the identifier of the resource's name
+ *		_description	the identifier of the resource's description
+ *		_weight			the resource's ordering weight
+ *
+ * Returns:
+ *		?				the result code for the operation
+ */
+CREATE OR REPLACE FUNCTION defs.uoc_resource(
+			_name TEXT ,
+			_description TEXT ,
+			_weight INT )
+	RETURNS defs.resource_update_result
+	STRICT
+	VOLATILE
+	SECURITY DEFINER
+AS $$
+	SELECT defs.uoc_resource_internal( $1 , $2 , NULL , $3 );
+$$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.uoc_resource( TEXT , TEXT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION defs.uoc_resource( TEXT , TEXT , INT )
+	TO :dbuser;
+
+
+
+/*
+ * Update or create a basic resource definition with a category
+ * 
+ * Parameters:
+ *		_name			the identifier of the resource's name
+ *		_description	the identifier of the resource's description
+ *		_category		the identifier of the resource's category
+ *		_weight			the resource's ordering weight
+ *
+ * Returns:
+ *		?				the result code for the operation
+ */
+CREATE OR REPLACE FUNCTION defs.uoc_resource(
+			_name TEXT ,
+			_description TEXT ,
+			_category TEXT ,
+			_weight INT )
+	RETURNS defs.resource_update_result
+	STRICT
+	VOLATILE
+	SECURITY DEFINER
+AS $$
+	SELECT defs.uoc_resource_internal( $1 , $2 , $3 , $4 );
+$$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.uoc_resource( TEXT , TEXT , TEXT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION defs.uoc_resource( TEXT , TEXT , TEXT , INT )
+	TO :dbuser;
+
+
+
+/*
+ * Create or update a natural resource
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function is called by the variants of defs.uoc_natural_resource() to
+ * actually update or create the resource. It will make sure that all string
+ * identifiers exist, then try to insert the resource. If that fails because
+ * the resource already exists, make sure it's a natural resource then update
+ * it.
+ * 
+ * Parameters:
+ *		_name			the identifier of the resource's name
+ *		_description	the identifier of the resource's description
+ *		_category		the identifier of the resource's category, or NULL if
+ *							the resource does not belong to a category
+ *		_weight			the resource's ordering weight
+ *		_presence		the presence probability
+ *		_quantity_avg	the average quantity
+ *		_quantity_dev	the deviation from the average quantity
+ *		_difficulty_avg	the average extraction difficulty
+ *		_difficulty_dev	the deviation from the average extraction difficulty
+ *		_recovery_avg	the average recovery rate
+ *		_recovery_dev	the deviation from the average recovery rate
+ *
+ * Returns:
+ *		?				the result code for the operation
+ */
+CREATE OR REPLACE FUNCTION defs.uoc_natres_internal(
+				_name TEXT ,
+				_description TEXT ,
+				_category TEXT ,
+				_weight INT ,
+				_presence DOUBLE PRECISION ,
+				_quantity_avg DOUBLE PRECISION ,
+				_quantity_dev DOUBLE PRECISION ,
+				_difficulty_avg DOUBLE PRECISION ,
+				_difficulty_dev DOUBLE PRECISION ,
+				_recovery_avg DOUBLE PRECISION ,
+				_recovery_dev DOUBLE PRECISION )
+		RETURNS defs.resource_update_result
+		CALLED ON NULL INPUT
+		VOLATILE
+		SECURITY INVOKER
+	AS $$
+DECLARE
+	_ret		defs.resource_update_result;
+	_name_id	INT;
+	_desc_id	INT;
+	_cat_id		INT;
+	_inserted	BOOLEAN;
+BEGIN
+	-- Get all string identifiers
+	SELECT INTO _name_id id FROM defs.strings WHERE name = _name;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+	SELECT INTO _desc_id id FROM defs.strings WHERE name = _description;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+	IF _category IS NULL THEN
+		_cat_id := NULL;
+	ELSE
+		SELECT INTO _cat_id id FROM defs.strings WHERE name = _category;
+		IF NOT FOUND THEN
+			RETURN 'BAD_STRINGS';
+		END IF;
+	END IF;
+
+	-- Try inserting the basic record
+	BEGIN
+		INSERT INTO defs.resources (
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_name_id , _desc_id , _cat_id , _weight
+		);
+		_inserted := TRUE;
+	EXCEPTION
+		WHEN unique_violation THEN
+			IF SQLERRM LIKE '%_description_%' THEN
+				RETURN 'DUP_DESCR';
+			END IF;
+		_inserted := FALSE;
+	END;
+
+	-- If insertion succeeded, insert the rest of the record
+	IF _inserted THEN
+		INSERT INTO defs.natural_resources(
+			resource_name_id , natres_p_presence ,
+			natres_quantity_avg , natres_quantity_dev ,
+			natres_difficulty_avg , natres_difficulty_dev ,
+			natres_recovery_avg , natres_recovery_dev
+		) VALUES (
+			_name_id , _presence ,
+			_quantity_avg , _quantity_dev ,
+			_difficulty_avg , _difficulty_dev ,
+			_recovery_avg , _recovery_dev
+		);
+		RETURN 'CREATED';
+	END IF;
+
+	-- Insertion failed, make sure it is a natural resource
+	PERFORM *
+		FROM defs.resources basic_res
+			INNER JOIN defs.natural_resources nat_res
+				USING ( resource_name_id )
+		WHERE basic_res.resource_name_id = _name_id
+		FOR UPDATE;
+	IF NOT FOUND THEN
+		RETURN 'BAD_TYPE';
+	END IF;
+
+	-- Update the resource
+	BEGIN
+		UPDATE defs.resources
+			SET resource_description_id = _desc_id ,
+				resource_category_id = _cat_id ,
+				resource_weight = _weight
+			WHERE resource_name_id = _name_id;
+		UPDATE defs.natural_resources
+			SET natres_p_presence = _presence ,
+				natres_quantity_avg = _quantity_avg ,
+				natres_quantity_dev = _quantity_dev ,
+				natres_difficulty_avg = _difficulty_avg ,
+				natres_difficulty_dev = _difficulty_dev ,
+				natres_recovery_avg = _recovery_avg ,
+				natres_recovery_dev = _recovery_dev
+			WHERE resource_name_id = _name_id;
+		RETURN 'UPDATED';
+	EXCEPTION
+		WHEN unique_violation THEN
+			RETURN 'DUP_DESCR';
+	END;
+EXCEPTION
+	WHEN check_violation THEN
+		RETURN 'BAD_VALUE';
+END;
+$$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.uoc_natres_internal( TEXT ,  TEXT , TEXT , INT ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION )
+	FROM PUBLIC;
+
+
+
+/*
+ * Create or update a natural resource with no category
+ * 
+ * Parameters:
+ *		_name			the identifier of the resource's name
+ *		_description	the identifier of the resource's description
+ *		_weight			the resource's ordering weight
+ *		_presence		the presence probability
+ *		_quantity_avg	the average quantity
+ *		_quantity_dev	the deviation from the average quantity
+ *		_difficulty_avg	the average extraction difficulty
+ *		_difficulty_dev	the deviation from the average extraction difficulty
+ *		_recovery_avg	the average recovery rate
+ *		_recovery_dev	the deviation from the average recovery rate
+ *
+ * Returns:
+ *		?				the result code for the operation
+ */
+CREATE OR REPLACE FUNCTION defs.uoc_natural_resource(
+			_name TEXT ,
+			_description TEXT ,
+			_weight INT ,
+			_presence DOUBLE PRECISION ,
+			_quantity_avg DOUBLE PRECISION ,
+			_quantity_dev DOUBLE PRECISION ,
+			_difficulty_avg DOUBLE PRECISION ,
+			_difficulty_dev DOUBLE PRECISION ,
+			_recovery_avg DOUBLE PRECISION ,
+			_recovery_dev DOUBLE PRECISION )
+	RETURNS defs.resource_update_result
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $$
+	SELECT defs.uoc_natres_internal( $1 , $2 , NULL , $3 , $4 , $5 , $6 , $7 ,
+		$8 , $9 , $10 );
+$$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.uoc_natural_resource( TEXT ,  TEXT , INT ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION defs.uoc_natural_resource( TEXT ,  TEXT , INT ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION )
+	TO :dbuser;
+
+
+
+/*
+ * Create or update a natural resource with a category
+ * 
+ * Parameters:
+ *		_name			the identifier of the resource's name
+ *		_description	the identifier of the resource's description
+ *		_category		the identifier of the resource's category
+ *		_weight			the resource's ordering weight
+ *		_presence		the presence probability
+ *		_quantity_avg	the average quantity
+ *		_quantity_dev	the deviation from the average quantity
+ *		_difficulty_avg	the average extraction difficulty
+ *		_difficulty_dev	the deviation from the average extraction difficulty
+ *		_recovery_avg	the average recovery rate
+ *		_recovery_dev	the deviation from the average recovery rate
+ *
+ * Returns:
+ *		?				the result code for the operation
+ */
+CREATE OR REPLACE FUNCTION defs.uoc_natural_resource(
+			_name TEXT ,
+			_description TEXT ,
+			_category TEXT ,
+			_weight INT ,
+			_presence DOUBLE PRECISION ,
+			_quantity_avg DOUBLE PRECISION ,
+			_quantity_dev DOUBLE PRECISION ,
+			_difficulty_avg DOUBLE PRECISION ,
+			_difficulty_dev DOUBLE PRECISION ,
+			_recovery_avg DOUBLE PRECISION ,
+			_recovery_dev DOUBLE PRECISION )
+	RETURNS defs.resource_update_result
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $$
+	SELECT defs.uoc_natres_internal( $1 , $2 , $3 , $4 , $5 , $6 , $7 , $8 ,
+		$9 , $10 , $11 );
+$$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.uoc_natural_resource( TEXT ,  TEXT , TEXT , INT ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION defs.uoc_natural_resource( TEXT , TEXT , TEXT , INT ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION )
+	TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02500-uoc-resource-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02500-uoc-resource-internal.sql
new file mode 100644
index 0000000..10bee48
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02500-uoc-resource-internal.sql
@@ -0,0 +1,74 @@
+/*
+ * Test the defs.uoc_resource_internal() function
+ */
+BEGIN;
+	
+	/* We need a few strings to be used when creating resource definitions, and a natural resource. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	SELECT _create_test_strings( 7 );
+	SELECT _create_natural_resources( 1 , 'natRes' );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 15 );
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - creation without category' );
+	SELECT is( defs.uoc_resource_internal( 'test3' , 'test4' , NULL , 1 ) , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - creation results without category' );
+	SELECT results_eq(
+		$$ SELECT resource_name_id , resource_description_id , resource_weight
+				FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' )
+					AND resource_category_id IS NULL $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 1 ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - creation with category' );
+	SELECT is( defs.uoc_resource_internal( 'test5' , 'test6' , 'test7' , 1 ) , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - creation results with category' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
+		$$ VALUES ( _get_string( 'test5' ) , _get_string( 'test6' ) , _get_string( 'test7' ) , 1 ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - update without category' );
+	SELECT is( defs.uoc_resource_internal( 'test3' , 'test7' , NULL , 2 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - update results without category' );
+	SELECT results_eq(
+		$$ SELECT resource_name_id , resource_description_id , resource_weight
+				FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' )
+					AND resource_category_id IS NULL $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test7' ) , 2 ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - update with category' );
+	SELECT is( defs.uoc_resource_internal( 'test3' , 'test4' , 'test7' , 1 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - update results with category' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , _get_string( 'test7' ) , 1 ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - incorrect name string' );
+	SELECT is( defs.uoc_resource_internal( 'does-not-exist' , 'test2' , NULL , 1 ) , 'BAD_STRINGS' );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - incorrect description string' );
+	SELECT is( defs.uoc_resource_internal( 'test1' , 'does-not-exist' , NULL , 1 ) , 'BAD_STRINGS' );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - incorrect category string' );
+	SELECT is( defs.uoc_resource_internal( 'test1' , 'test2' , 'does-not-exist' , 1 ) , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - duplicate description on new resource' );
+	SELECT is( defs.uoc_resource_internal( 'test1' , 'test4' , NULL , 1 ) , 'DUP_DESCR' );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - duplicate description on existing resource' );
+	SELECT is( defs.uoc_resource_internal( 'test5' , 'test4' , NULL , 1 ) , 'DUP_DESCR' );
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - update on natural resource' );
+	SELECT is( defs.uoc_resource_internal( 'natRes1' , 'test2' , NULL , 1 ) , 'BAD_TYPE' );
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - weight <= 0' );
+	SELECT is( defs.uoc_resource_internal( 'test1' , 'test2' , NULL , 0 ) , 'BAD_VALUE' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02501-uoc-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02501-uoc-resource.sql
new file mode 100644
index 0000000..113634d
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02501-uoc-resource.sql
@@ -0,0 +1,90 @@
+/*
+ * Test the defs.uoc_resource() functions
+ */
+BEGIN;
+	
+	/* We need a few strings to be used when creating resource definitions, and a natural resource. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	SELECT _create_test_strings( 7 );
+	SELECT _create_natural_resources( 1 , 'natRes' );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 22 );
+
+	SELECT diag_test_name( 'defs.uoc_resource() - NULL name (no category)' );
+	SELECT is( defs.uoc_resource( NULL , 'test2' , 1 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_resource() - NULL description (no category)' );
+	SELECT is( defs.uoc_resource( 'test1' , NULL , 1 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_resource() - NULL weight (no category)' );
+	SELECT is( defs.uoc_resource( 'test1' , 'test2' , NULL ) , NULL );
+
+	SELECT diag_test_name( 'defs.uoc_resource() - NULL name (with category)' );
+	SELECT is( defs.uoc_resource( NULL , 'test2' , 'test3' , 1 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_resource() - NULL description (with category)' );
+	SELECT is( defs.uoc_resource( 'test1' , NULL , 'test3' , 1 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_resource() - NULL category' );
+	SELECT is( defs.uoc_resource( 'test1' , 'test2' , NULL , 1 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_resource() - NULL weight (with category)' );
+	SELECT is( defs.uoc_resource( 'test1' , 'test2' , 'test3' , NULL ) , NULL );
+
+	SELECT diag_test_name( 'defs.uoc_resource() - creation without category' );
+	SELECT is( defs.uoc_resource( 'test3' , 'test4' , 1 ) , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_resource() - creation results without category' );
+	SELECT results_eq(
+		$$ SELECT resource_name_id , resource_description_id , resource_weight
+				FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' )
+					AND resource_category_id IS NULL $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 1 ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_resource() - creation with category' );
+	SELECT is( defs.uoc_resource( 'test5' , 'test6' , 'test7' , 1 ) , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_resource() - creation results with category' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
+		$$ VALUES ( _get_string( 'test5' ) , _get_string( 'test6' ) , _get_string( 'test7' ) , 1 ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_resource() - update without category' );
+	SELECT is( defs.uoc_resource( 'test3' , 'test7', 2 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_resource() - update results without category' );
+	SELECT results_eq(
+		$$ SELECT resource_name_id , resource_description_id , resource_weight
+				FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' )
+					AND resource_category_id IS NULL $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test7' ) , 2 ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_resource() - update with category' );
+	SELECT is( defs.uoc_resource( 'test3' , 'test4' , 'test7' , 1 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_resource() - update results with category' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , _get_string( 'test7' ) , 1 ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_resource() - incorrect name string' );
+	SELECT is( defs.uoc_resource( 'does-not-exist' , 'test2' , 1 ) , 'BAD_STRINGS' );
+	SELECT diag_test_name( 'defs.uoc_resource() - incorrect description string' );
+	SELECT is( defs.uoc_resource( 'test1' , 'does-not-exist' , 1 ) , 'BAD_STRINGS' );
+	SELECT diag_test_name( 'defs.uoc_resource() - incorrect category string' );
+	SELECT is( defs.uoc_resource( 'test1' , 'test2' , 'does-not-exist' , 1 ) , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_resource() - duplicate description on new resource' );
+	SELECT is( defs.uoc_resource( 'test1' , 'test4' , 1 ) , 'DUP_DESCR' );
+	SELECT diag_test_name( 'defs.uoc_resource() - duplicate description on existing resource' );
+	SELECT is( defs.uoc_resource( 'test5' , 'test4' , 1 ) , 'DUP_DESCR' );
+
+	SELECT diag_test_name( 'defs.uoc_resource() - update on natural resource' );
+	SELECT is( defs.uoc_resource( 'natRes1' , 'test2' , 1 ) , 'BAD_TYPE' );
+
+	SELECT diag_test_name( 'defs.uoc_resource() - weight <= 0' );
+	SELECT is( defs.uoc_resource( 'natRes1' , 'test2' , 0 ) , 'BAD_VALUE' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02502-uoc-natres-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02502-uoc-natres-internal.sql
new file mode 100644
index 0000000..d442020
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02502-uoc-natres-internal.sql
@@ -0,0 +1,175 @@
+/*
+ * Test the defs.uoc_natres_internal() function
+ */
+BEGIN;
+	
+	/* We need a few strings to be used when creating resource definitions, and a basic resource. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	SELECT _create_test_strings( 7 );
+	SELECT _create_resources( 1 , 'basicRes' );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 37 );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation without category' );
+	SELECT is( defs.uoc_natres_internal( 'test3' , 'test4' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation results without category - basic' );
+	SELECT results_eq(
+		$$ SELECT resource_name_id , resource_description_id , resource_weight
+				FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' )
+					AND resource_category_id IS NULL $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 1 ) $$
+	);
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation results without category - nat. res.' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.natural_resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
+			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
+			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation with category' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test6' , 'test7' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation results with category - basic' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
+		$$ VALUES ( _get_string( 'test5' ) , _get_string( 'test6' ) , _get_string( 'test7' ) , 1 ) $$
+	);
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation results with category - nat. res.' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.natural_resources
+				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
+		$$ VALUES ( _get_string( 'test5' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
+			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
+			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - update without category' );
+	SELECT is( defs.uoc_natres_internal( 'test3' , 'test7' , NULL , 2 ,
+		0.3 , 300 , 30 , 0.3 , 0.03 , 0.3 , 0.03 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - update results without category - basic' );
+	SELECT results_eq(
+		$$ SELECT resource_name_id , resource_description_id , resource_weight
+				FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' )
+					AND resource_category_id IS NULL $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test7' ) , 2 ) $$
+	);
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - update results without category - nat. res.' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.natural_resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , 0.3::DOUBLE PRECISION , 300::DOUBLE PRECISION ,
+			30::DOUBLE PRECISION , 0.3::DOUBLE PRECISION , 0.03::DOUBLE PRECISION ,
+			0.3::DOUBLE PRECISION , 0.03::DOUBLE PRECISION ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - update with category' );
+	SELECT is( defs.uoc_natres_internal( 'test3' , 'test4' , 'test7' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - update results with category' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , _get_string( 'test7' ) , 1 ) $$
+	);
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - update results with category - nat. res.' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.natural_resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
+			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
+			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - incorrect name string' );
+	SELECT is( defs.uoc_natres_internal( 'does-not-exist' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - incorrect description string' );
+	SELECT is( defs.uoc_natres_internal( 'test1' , 'does-not-exist' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - incorrect category string' );
+	SELECT is( defs.uoc_natres_internal( 'test1' , 'test2' , 'does-not-exist' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - duplicate description on new resource' );
+	SELECT is( defs.uoc_natres_internal( 'test1' , 'test4' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'DUP_DESCR' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - duplicate description on existing resource' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test4' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'DUP_DESCR' );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - update on basic resource' );
+	SELECT is( defs.uoc_natres_internal( 'basicRes1' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_TYPE' );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - weight <= 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 0 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - P(presence) <= 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - P(presence) >= 1' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - max. quantity <= 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 0 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - quantity dev. < 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , -1 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - quantity max. - dev. <= 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 100 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - difficulty max. = 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0 , 0 , 0.5 , 0.05 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - difficulty max. < 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , -0.00001 , 0 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - difficulty max. = 1' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 1 , 0 , 0.5 , 0.05 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - difficulty max. > 1' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 1.00001 , 0 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - difficulty dev. < 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , -1 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - difficulty max. - dev. < 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.25 , 0.5 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - difficulty max. + dev. > 1' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.75 , 0.5 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - recovery max. <= 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0 , 0 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - recovery max. = 1' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 1 , 0 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - recovery max. > 1' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 1.0001 , 0 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - recovery dev. < 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , -0.25 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - recovery max. - dev. <= 0' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.25 , 0.25 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - recovery max. + dev. > 1' );
+	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.75 , 0.5 ) , 'BAD_VALUE' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02503-uoc-natural-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02503-uoc-natural-resource.sql
new file mode 100644
index 0000000..3f8f119
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02503-uoc-natural-resource.sql
@@ -0,0 +1,242 @@
+/*
+ * Test the defs.uoc_natural_resource() functions
+ */
+BEGIN;
+	
+	/* We need a few strings to be used when creating resource definitions, and a basic resource. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	SELECT _create_test_strings( 7 );
+	SELECT _create_resources( 1 , 'basicRes' );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 58 );
+
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL name (no category)' );
+	SELECT is( defs.uoc_natural_resource( NULL , 'test4' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL description (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL weight (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , NULL ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL presence (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
+		NULL , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average quantity (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
+		0.5 , NULL , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL quantity deviation (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
+		0.5 , 100 , NULL , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average difficulty (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
+		0.5 , 100 , 50 , NULL , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL difficulty deviation (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
+		0.5 , 100 , 50 , 0.5 , NULL , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average recovery (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , NULL , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL recovery deviation (no category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , NULL ) , NULL );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL name' );
+	SELECT is( defs.uoc_natural_resource( NULL , 'test4' , 'test5' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL description' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , NULL , 'test5' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL category' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL weight' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , NULL ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL presence' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
+		NULL , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average quantity' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
+		0.5 , NULL , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL quantity deviation' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
+		0.5 , 100 , NULL , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average difficulty' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
+		0.5 , 100 , 50 , NULL , 0.05 , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL difficulty deviation' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
+		0.5 , 100 , 50 , 0.5 , NULL , 0.5 , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average recovery' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , NULL , 0.05 ) , NULL );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL recovery deviation' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , NULL ) , NULL );
+
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation without category' );
+	SELECT is( defs.uoc_natural_resource( 'test3' , 'test4' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results without category - basic' );
+	SELECT results_eq(
+		$$ SELECT resource_name_id , resource_description_id , resource_weight
+				FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' )
+					AND resource_category_id IS NULL $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 1 ) $$
+	);
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results without category - nat. res.' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.natural_resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
+			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
+			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation with category' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test6' , 'test7' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results with category - basic' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
+		$$ VALUES ( _get_string( 'test5' ) , _get_string( 'test6' ) , _get_string( 'test7' ) , 1 ) $$
+	);
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results with category - nat. res.' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.natural_resources
+				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
+		$$ VALUES ( _get_string( 'test5' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
+			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
+			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - update without category' );
+	SELECT is( defs.uoc_natural_resource( 'test3' , 'test7' , 2 ,
+		0.3 , 300 , 30 , 0.3 , 0.03 , 0.3 , 0.03 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results without category - basic' );
+	SELECT results_eq(
+		$$ SELECT resource_name_id , resource_description_id , resource_weight
+				FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' )
+					AND resource_category_id IS NULL $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test7' ) , 2 ) $$
+	);
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results without category - nat. res.' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.natural_resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , 0.3::DOUBLE PRECISION , 300::DOUBLE PRECISION ,
+			30::DOUBLE PRECISION , 0.3::DOUBLE PRECISION , 0.03::DOUBLE PRECISION ,
+			0.3::DOUBLE PRECISION , 0.03::DOUBLE PRECISION ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - update with category' );
+	SELECT is( defs.uoc_natural_resource( 'test3' , 'test4' , 'test7' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results with category' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , _get_string( 'test7' ) , 1 ) $$
+	);
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results with category - nat. res.' );
+	SELECT results_eq(
+		$$ SELECT * FROM defs.natural_resources
+				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
+		$$ VALUES ( _get_string( 'test3' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
+			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
+			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
+	);
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect name string' );
+	SELECT is( defs.uoc_natural_resource( 'does-not-exist' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect description string' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'does-not-exist' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect category string' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test2' , 'does-not-exist' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - duplicate description on new resource' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'DUP_DESCR' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - duplicate description on existing resource' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test4' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'DUP_DESCR' );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - update on basic resource' );
+	SELECT is( defs.uoc_natural_resource( 'basicRes1' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_TYPE' );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - weight <= 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 0 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - P(presence) <= 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - P(presence) >= 1' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - max. quantity <= 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 0 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - quantity dev. < 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , -1 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - quantity max. - dev. <= 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 100 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. = 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0 , 0 , 0.5 , 0.05 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. < 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , -0.00001 , 0 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. = 1' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 1 , 0 , 0.5 , 0.05 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. > 1' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 1.00001 , 0 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty dev. < 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , -1 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. - dev. < 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.25 , 0.5 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. + dev. > 1' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.75 , 0.5 , 0.5 , 0.05 ) , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. <= 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0 , 0 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. = 1' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 1 , 0 ) , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. > 1' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 1.0001 , 0 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery dev. < 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , -0.25 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. - dev. <= 0' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.25 , 0.25 ) , 'BAD_VALUE' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. + dev. > 1' );
+	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.75 , 0.5 ) , 'BAD_VALUE' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds/doc/TODO.txt b/legacyworlds/doc/TODO.txt
index 448622a..6c64d19 100644
--- a/legacyworlds/doc/TODO.txt
+++ b/legacyworlds/doc/TODO.txt
@@ -16,7 +16,13 @@ SERVER & DATABASE:
   ! Add some form of database version control to allow easier updates
 		-> existing options were investigated, they are unsatisfactory
 
-  * Replace all single-precision reals with double precision reals
+  ! SQL code clean-up:
+	  * Replace all single-precision reals with double precision reals
+	  * Make sure internal functions cannot be called by the main user
+	  * Make sure functions that are supposed to be executed by the main
+	    user are not public
+	  * Rename all views to v_*
+	  * Rename all table fields to use a prefix
 
   * Add a tool to initialise the database
 

From d768766214263b7fffcb3f65f7c6b6a080817d2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 2 Jan 2012 08:57:06 +0100
Subject: [PATCH 08/94] Main web interface update

* Added the new interface style, including the necessary pictures and page
layout template.

* Updated existing style sheet in order to accommodate the new fluid
layout and to use some more CSS3 goodness.

* Upgraded JQuery from 1.4.2 to 1.7.1.
---
 .../Raw/WEB-INF/fm/en/containers/chat.ftl     |  29 +-
 .../Raw/WEB-INF/fm/en/containers/external.ftl |  55 ++-
 .../Raw/WEB-INF/fm/en/containers/game.ftl     |  90 ++--
 .../Raw/WEB-INF/fm/en/containers/offline.ftl  |  36 +-
 .../WEB-INF/fm/en/containers/restricted.ftl   |  40 +-
 .../Content/Raw/WEB-INF/fm/en/types/chat.ftl  |   2 +-
 .../Raw/WEB-INF/fm/fr/containers/chat.ftl     |  29 +-
 .../Raw/WEB-INF/fm/fr/containers/external.ftl |  57 ++-
 .../Raw/WEB-INF/fm/fr/containers/game.ftl     |  92 ++--
 .../Raw/WEB-INF/fm/fr/containers/offline.ftl  |  38 +-
 .../WEB-INF/fm/fr/containers/restricted.ftl   |  40 +-
 .../Content/Raw/WEB-INF/fm/fr/types/chat.ftl  |   2 +-
 .../Content/Raw/css/main.css                  | 466 ++++++++----------
 .../Content/Raw/img/background.jpg            | Bin 91318 -> 133286 bytes
 .../Content/Raw/img/button-0.png              | Bin 32007 -> 0 bytes
 .../Content/Raw/img/button-1.png              | Bin 31317 -> 0 bytes
 .../Content/Raw/img/button-2.png              | Bin 30310 -> 0 bytes
 .../Content/Raw/img/button-3.png              | Bin 25647 -> 0 bytes
 .../Content/Raw/img/button-4.png              | Bin 22337 -> 0 bytes
 .../Content/Raw/img/button-5.png              | Bin 22034 -> 0 bytes
 .../Content/Raw/img/button-6.png              | Bin 19169 -> 0 bytes
 .../Content/Raw/img/logo.png                  | Bin 0 -> 19320 bytes
 .../Content/Raw/img/rust.png                  | Bin 0 -> 80507 bytes
 .../Content/Raw/js/jquery-1.4.2.min.js        | 154 ------
 .../Content/Raw/js/jquery-1.7.1.min.js        |   4 +
 legacyworlds-web-main/Content/Raw/js/main.js  |  91 ++--
 26 files changed, 576 insertions(+), 649 deletions(-)
 delete mode 100644 legacyworlds-web-main/Content/Raw/img/button-0.png
 delete mode 100644 legacyworlds-web-main/Content/Raw/img/button-1.png
 delete mode 100644 legacyworlds-web-main/Content/Raw/img/button-2.png
 delete mode 100644 legacyworlds-web-main/Content/Raw/img/button-3.png
 delete mode 100644 legacyworlds-web-main/Content/Raw/img/button-4.png
 delete mode 100644 legacyworlds-web-main/Content/Raw/img/button-5.png
 delete mode 100644 legacyworlds-web-main/Content/Raw/img/button-6.png
 create mode 100644 legacyworlds-web-main/Content/Raw/img/logo.png
 create mode 100644 legacyworlds-web-main/Content/Raw/img/rust.png
 delete mode 100644 legacyworlds-web-main/Content/Raw/js/jquery-1.4.2.min.js
 create mode 100644 legacyworlds-web-main/Content/Raw/js/jquery-1.7.1.min.js

diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/chat.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/chat.ftl
index 28deee5..18eb983 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/chat.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/chat.ftl
@@ -6,22 +6,29 @@
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<!-- Title and planet list -->
-				<div class="top-centered">
-					<p style="padding: 22px 0px 0px 0px" id="tc-title">${title?xhtml}</p>
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="#" title="Legacy Worlds home page"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			<div id="hd-bar">
+				<div id="hd-summary" />
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
 				</div>
-				
-				<div class="cframe full-width"><#nested></div>
-			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 			</div>
+			<div id="hd-text">current version: <@full_version/></div>
+		</div>
+
+		<div id="page-contents" class="chat-page"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/external.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/external.ftl
index 5f9e1ef..011f210 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/external.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/external.ftl
@@ -5,19 +5,19 @@
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<!-- Header -->
-				<a id="hbutton" href="home" title="Legacy Worlds home page"><span>Legacy Worlds Beta 6 <@version/></span></a>
-				<div id="version">current version: <@full_version/></div>
-				<div class="top-centered" id="tc-title">${title?xhtml}</div>
-
-				<!-- Log-in box -->
-				<div id="lbox">
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="home" title="Legacy Worlds home page"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			
+			<!-- Title / login bar -->
+			<div id="hd-bar">
+				<div id="hd-summary">
 					<form method="post" action="login.action">
 						<div>
 							E-mail address: <input type="text" name="mail" value="" size="15" maxlength="128" class='input' /><br/>
@@ -27,17 +27,34 @@
 						</div>
 					</form>
 				</div>
-
-				<!-- Menu -->
-				<a class="mbutton" id="b0" href="scope" title="Scope of this milestone">Scope</a>
-				<a class="mbutton" id="b1" href="rules" title="Game rules">Rules</a>
-				<a class="mbutton" id="b2" href="register" title="Register">Register</a>
-
-				<div class="cframe"><#nested></div>
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
+				</div>
 			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
+			<div id="hd-text">current version: <@full_version/></div>
+		</div>
+
+		<!-- Menu
+		   -
+		   - FIXME: set "current" style on button for the current page
+		  -->
+		<div id="menu-left">
+			<div class="button">
+				<a href="scope" title="Scope of this milestone">Scope</a>
 			</div>
+			<div class="button">
+				<a href="rules" title="Game rules">Rules</a>
+			</div>
+			<div class="button">
+				<a href="register" title="Register">Register</a>
+			</div>
+		</div>
+
+		<div id="page-contents"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
index 3d07f3f..e74fbd5 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
@@ -6,33 +6,17 @@
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<a id="hbutton" href="overview" title="Overview page"><span>Legacy Worlds Beta 6 <@version/></span></a>
-				<div id="version"><@abbr_gt/>: <@game_time record=data.page.gameTime /> / <@abbr_st/>: ${data.page.serverTime?string("yyyy-MM-dd HH:mm:ss ZZZZZ")}</div>
-				
-				<!-- Title and planet list -->
-				<div class="top-centered">
-					<p id="tc-title">${title?xhtml}</p>
-					<p>&nbsp;</p>
-					<#if !hidePlanets>
-						<#if data.page.planets?size == 0>
-							<p id="get-new-planet"><a href="get-planet">Get new planet</a></p>
-						<#else>
-							<p id="jump-to-planet"><span class="jtp-text">Jump to planet</span>: <span>
-								<#list data.page.planets as planet>
-									<a href="planet-${planet.id}">${planet.name?xhtml}</a>
-								</#list>
-							</span></p> 
-						</#if>
-					</#if>
-				</div>
-				
-				<div id="lbox" class="game-info">
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="home" title="Legacy Worlds home page"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			<div id="hd-bar">
+				<div id="hd-summary">
 					<strong>${data.page.empire}</strong>
 					<#if data.page.alliance?has_content>
 						[<strong>${data.page.alliance}</strong>]
@@ -50,20 +34,54 @@
 					<br/>
 					<a href="account">Account</a> - <a href="logout.action">Log out</a>
 				</div>
-
-				<a class="mbutton" id="b0" href="planets" title="Planets">Planets</a>
-				<a class="mbutton" id="b1" href="fleets" title="Fleets">Fleets</a>
-				<a class="mbutton" id="b2" href="map" title="Map of the universe">Map</a>
-				<a class="mbutton" id="b3" href="alliance" title="Alliance">Alliance</a>
-				<a class="mbutton" id="b4" href="enemies" title="Manage enemy players and alliances">Enemy list</a>
-				<a class="mbutton" id="b5" href="messages" title="Messages">Messages</a>
-				<a class="mbutton" id="b6" href="bugtrack" title="Bug tracking application">Bug tracker</a>
-
-				<div class="cframe"><#nested></div>
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
+				</div>
 			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
+			<div id="hd-text"><@abbr_gt/>: <@game_time record=data.page.gameTime /> / <@abbr_st/>: ${data.page.serverTime?string("yyyy-MM-dd HH:mm:ss ZZZZZ")}</div>
+		</div>
+		<div id="menu-left">
+			<div class="button">
+				<a href="planets" title="Planets">Planets</a>
 			</div>
+			<#if !hidePlanets>
+				<#if data.page.planets?size == 0>
+					<div class="button sub-menu">
+						<a href="get-planet">Get new planet</a>
+					</div>
+				<#else>
+					<#list data.page.planets as planet>
+						<div class="button sub-menu">
+							<a href="planet-${planet.id}">${planet.name?xhtml}</a>
+						</div>
+					</#list>
+				</#if>
+			</#if>
+			<div class="button">
+				<a href="fleets" title="Fleets">Fleets</a>
+			</div>
+			<div class="button">
+				<a href="map" title="Map of the universe">Map</a>
+			</div>
+			<div class="button">
+				<a href="alliance" title="Alliance">Alliance</a>
+			</div>
+			<div class="button">
+				<a href="enemies" title="Manage enemy players and alliances">Enemy list</a>
+			</div>
+			<div class="button">
+				<a href="messages" title="Messages">Messages</a>
+			</div>
+			<div class="button">
+				<a href="bugtrack" title="Bug tracking application">Bug tracker</a>
+			</div>
+		</div>
+
+		<div id="page-contents"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/offline.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/offline.ftl
index 37af005..9f11354 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/offline.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/offline.ftl
@@ -5,22 +5,34 @@
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<!-- Header -->
-				<a id="hbutton" href="player-session" title="Legacy Worlds home page"><span>Legacy Worlds Beta 6 <@version/></span></a>
-				<div id="version">current version: <@full_version/></div>
-				<div class="top-centered" id="tc-title">${title?xhtml}</div>
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="home" title="Legacy Worlds home page"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			<div id="hd-bar">
+				<div id="hd-summary" />
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
+				</div>
+			</div>
+			<div id="hd-text">current version: <@full_version/></div>
+		</div>
+		<div id="menu-left">
+			<div class="button">
+				<a href="?" title="Try again">Retry</a>
+			</div>
+		</div>
 
-				<div class="cframe full-width offline"><#nested></div>
-			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
-			</div>
+		<div id="page-contents"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/restricted.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/restricted.ftl
index 6f5efb8..b3677e5 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/restricted.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/restricted.ftl
@@ -5,28 +5,34 @@
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<!-- Header -->
-				<a id="hbutton" href="home" title="Legacy Worlds home page"><span>Legacy Worlds Beta 6 <@version/></span></a>
-				<div id="version">current version: <@full_version/></div>
-				<div class="top-centered" id="tc-title">${title?xhtml}</div>
-
-				<!-- Log-in box -->
-				<div id="lbox">
-					<br/><br/><br/>
-					<a href="logout.action">Log out</a>
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="home" title="Legacy Worlds home page"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			<div id="hd-bar">
+				<div id="hd-summary" />
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
 				</div>
+			</div>
+			<div id="hd-text">current version: <@full_version/></div>
+		</div>
+		<div id="menu-left">
+			<div class="button">
+				<a href="logout.action" title="Log out">Log out</a>
+			</div>
+		</div>
 
-				<div class="cframe full-width"><#nested></div>
-			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
-			</div>
+		<div id="page-contents"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/chat.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/chat.ftl
index f62c595..8bbd44a 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/chat.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/chat.ftl
@@ -1,6 +1,6 @@
 <#macro render>
 <@page title="Legacy Worlds chat">
-	<applet code="IRCApplet.class" archive="irc.jar,pixx.jar" codebase="pjirc" width="100%" height="100%">
+	<applet code="IRCApplet.class" archive="irc.jar,pixx.jar" codebase="pjirc" width="100%" height="400px">
 		<param name="CABINETS" value="irc.cab,securedirc.cab,pixx.cab" />
 		<param name="nick" value="${(data.page.empire?replace(' ' , '_'))?xhtml}" />
 		<param name="alternatenick" value="LW-Player-???" />
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/chat.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/chat.ftl
index 28deee5..48a47f3 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/chat.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/chat.ftl
@@ -6,22 +6,29 @@
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<!-- Title and planet list -->
-				<div class="top-centered">
-					<p style="padding: 22px 0px 0px 0px" id="tc-title">${title?xhtml}</p>
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="#" title="Page d'accueil de Legacy Worlds"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			<div id="hd-bar">
+				<div id="hd-summary" />
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
 				</div>
-				
-				<div class="cframe full-width"><#nested></div>
-			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 			</div>
+			<div id="hd-text">version courante : <@full_version/></div>
+		</div>
+
+		<div id="page-contents" class="chat-page"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/external.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/external.ftl
index 040e9d7..9e9a0e8 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/external.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/external.ftl
@@ -1,23 +1,23 @@
 <#macro page title>
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<!-- Header -->
-				<a id="hbutton" href="home" title="Page d'accueil de Legacy Worlds"><span>Legacy Worlds Beta 6 <@version/></span></a>
-				<div id="version">version courante : <@full_version/></div>
-				<div class="top-centered" id="tc-title">${title?xhtml}</div>
-
-				<!-- Log-in box -->
-				<div id="lbox">
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="home" title="Page d'accueil de Legacy Worlds"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			
+			<!-- Title / login bar -->
+			<div id="hd-bar">
+				<div id="hd-summary">
 					<form method="post" action="login.action">
 						<div>
 							Adresse e-mail : <input type="text" name="mail" value="" size="15" maxlength="128" class='input' /><br/>
@@ -27,17 +27,34 @@
 						</div>
 					</form>
 				</div>
-
-				<!-- Menu -->
-				<a class="mbutton" id="b0" href="scope" title="Portée de ce jalon">Portée</a>
-				<a class="mbutton" id="b1" href="rules" title="Règles du jeu">Règles</a>
-				<a class="mbutton" id="b2" href="register" title="Inscription">Inscription</a>
-
-				<div class="cframe"><#nested></div>
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
+				</div>
 			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
+			<div id="hd-text">version courante : <@full_version/></div>
+		</div>
+
+		<!-- Menu
+		   -
+		   - FIXME: set "current" style on button for the current page
+		  -->
+		<div id="menu-left">
+			<div class="button">
+				<a href="scope" title="Portée de ce jalon">Portée</a>
 			</div>
+			<div class="button">
+				<a href="rules" title="Règles du jeu">Règles</a>
+			</div>
+			<div class="button">
+				<a href="register" title="Inscription">Inscription</a>
+			</div>
+		</div>
+
+		<div id="page-contents"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
index 2f8e922..c82690e 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
@@ -2,37 +2,21 @@
 <#macro page title hidePlanets=false>
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<a id="hbutton" href="overview" title="Page Empire"><span>Legacy Worlds Beta 6 <@version/></span></a>
-				<div id="version"><@abbr_gt/>: <@game_time record=data.page.gameTime /> / <@abbr_st/>: ${data.page.serverTime?string("yyyy-MM-dd HH:mm:ss ZZZZZ")}</div>
-				
-				<!-- Title and planet list -->
-				<div class="top-centered">
-					<p id="tc-title">${title?xhtml}</p>
-					<#if !hidePlanets>
-						<p>&nbsp;</p>
-						<#if data.page.planets?size == 0>
-							<p id="get-new-planet"><a href="get-planet">Obtenir une nouvelle planète</a></p>
-						<#else>
-							<p id="jump-to-planet"><span class="jtp-text">Aller à la planète</span>: <span>
-								<#list data.page.planets as planet>
-									<a href="planet-${planet.id}">${planet.name?xhtml}</a>
-								</#list>
-							</span></p> 
-						</#if>
-					</#if>
-				</div>
-				
-				<div id="lbox" class="game-info">
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="overview" title="Page Empire"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			<div id="hd-bar">
+				<div id="hd-summary">
 					<strong>${data.page.empire}</strong>
 					<#if data.page.alliance?has_content>
 						[<strong>${data.page.alliance}</strong>]
@@ -50,20 +34,54 @@
 					<br/>
 					<a href="account">Compte</a> - <a href="logout.action">Déconnexion</a>
 				</div>
-
-				<a class="mbutton" id="b0" href="planets" title="Planètes">Planètes</a>
-				<a class="mbutton" id="b1" href="fleets" title="Flottes">Flottes</a>
-				<a class="mbutton" id="b2" href="map" title="Carte de l'univers">Carte</a>
-				<a class="mbutton" id="b3" href="alliance" title="Alliance">Alliance</a>
-				<a class="mbutton" id="b4" href="enemies" title="Gèstion des listes de joueurs et alliances ennemis">Listes d'ennemis</a>
-				<a class="mbutton" id="b5" href="messages" title="Messages">Messages</a>
-				<a class="mbutton" id="b6" href="bugtrack" title="Application de suivi des bugs">Suivi des bugs</a>
-
-				<div class="cframe"><#nested></div>
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
+				</div>
 			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
+			<div id="hd-text"><@abbr_gt/>: <@game_time record=data.page.gameTime /> / <@abbr_st/>: ${data.page.serverTime?string("yyyy-MM-dd HH:mm:ss ZZZZZ")}</div>
+		</div>
+		<div id="menu-left">
+			<div class="button">
+				<a href="planets" title="Planètes">Planètes</a>
 			</div>
+			<#if !hidePlanets>
+				<#if data.page.planets?size == 0>
+					<div class="button sub-menu">
+						<a href="get-planet" title="Obtenir une nouvelle planète">Nouvelle planète</a>
+					</div>
+				<#else>
+					<#list data.page.planets as planet>
+						<div class="button sub-menu">
+							<a href="planet-${planet.id}">${planet.name?xhtml}</a>
+						</div>
+					</#list>
+				</#if>
+			</#if>
+			<div class="button">
+				<a href="fleets" title="Flottes">Flottes</a>
+			</div>
+			<div class="button">
+				<a href="map" title="Carte de l'univers">Carte</a>
+			</div>
+			<div class="button">
+				<a href="alliance" title="Alliance">Alliance</a>
+			</div>
+			<div class="button">
+				<a href="enemies" title="Gèstion des listes de joueurs et alliances ennemis">Listes d'ennemis</a>
+			</div>
+			<div class="button">
+				<a href="messages" title="Messages">Messages</a>
+			</div>
+			<div class="button">
+				<a href="bugtrack" title="Application de suivi des bugs">Suivi des bugs</a>
+			</div>
+		</div>
+
+		<div id="page-contents"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/offline.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/offline.ftl
index 9261f58..115707c 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/offline.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/offline.ftl
@@ -1,26 +1,38 @@
 <#macro page title>
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<!-- Header -->
-				<a id="hbutton" href="player-session" title="Page d'accueil de Legacy Worlds"><span>Legacy Worlds Beta 6 <@version/></span></a>
-				<div id="version">Version courante : <@full_version/></div>
-				<div class="top-centered" id="tc-title">${title?xhtml}</div>
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="home" title="Page d'accueil de Legacy Worlds"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			<div id="hd-bar">
+				<div id="hd-summary" />
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
+				</div>
+			</div>
+			<div id="hd-text">version courante : <@full_version/></div>
+		</div>
+		<div id="menu-left">
+			<div class="button">
+				<a href="?">Réessayer</a>
+			</div>
+		</div>
 
-				<div class="cframe full-width offline"><#nested></div>
-			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
-			</div>
+		<div id="page-contents"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/restricted.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/restricted.ftl
index 7f1db1a..7bdb362 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/restricted.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/restricted.ftl
@@ -5,28 +5,34 @@
 	<head>
 		<title>Legacy Worlds Beta 6 <@version/> - ${title?xhtml}</title>
 		<link rel="stylesheet" type="text/css" href="css/main.css" />
-		<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.2.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery-1.7.1.min.js"></script>
 		<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
 	</head>
 	<body>
-		<div id="extframe">
-			<div class="internal" id="intframe">
-				<!-- Header -->
-				<a id="hbutton" href="home" title="Page d'accueil de Legacy Worlds"><span>Legacy Worlds Beta 6 <@version/></span></a>
-				<div id="version">Version courante : <@full_version/></div>
-				<div class="top-centered" id="tc-title">${title?xhtml}</div>
-
-				<!-- Log-in box -->
-				<div id="lbox">
-					<br/><br/><br/>
-					<a href="logout.action">Déconnexion</a>
+		<!-- Header -->
+		<div id="hd-top">
+			<div id="hd-logo">
+				<a href="home" title="Page d'accueil de Legacy Worlds"><span>Legacy Worlds Beta 6 <@version/></span></a>
+			</div>
+			<div id="hd-bar">
+				<div id="hd-summary" />
+				<div id="hd-page">
+					<h1>${title?xhtml}</h1>
 				</div>
+			</div>
+			<div id="hd-text">version courante : <@full_version/></div>
+		</div>
+		<div id="menu-left">
+			<div class="button">
+				<a href="logout.action" title="Déconnexion">Déconnexion</a>
+			</div>
+		</div>
 
-				<div class="cframe full-width"><#nested></div>
-			</div>
-			<div class="internal" id="footer">
-				Copyright (C) 2004-2010, <a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
-			</div>
+		<div id="page-contents"><#nested></div>
+
+		<div id="footer">
+			Copyright (C) 2004-2012,
+			<a title="DeepClone Development" href="http://www.deepclone.com">DeepClone Development</a>
 		</div>
 	</body>
 </html>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/chat.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/chat.ftl
index 52708bf..d2e827a 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/chat.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/chat.ftl
@@ -1,6 +1,6 @@
 <#macro render>
 <@page title="Legacy Worlds - Discussion">
-	<applet code="IRCApplet.class" archive="irc.jar,pixx.jar" codebase="pjirc" width="100%" height="100%">
+	<applet code="IRCApplet.class" archive="irc.jar,pixx.jar" codebase="pjirc" width="100%" height="400px">
 		<param name="CABINETS" value="irc.cab,securedirc.cab,pixx.cab" />
 		<param name="nick" value="${(data.page.empire?replace(' ' , '_'))?xhtml}" />
 		<param name="alternatenick" value="LW-Player-???" />
diff --git a/legacyworlds-web-main/Content/Raw/css/main.css b/legacyworlds-web-main/Content/Raw/css/main.css
index 48ff3b4..f18397b 100644
--- a/legacyworlds-web-main/Content/Raw/css/main.css
+++ b/legacyworlds-web-main/Content/Raw/css/main.css
@@ -5,13 +5,6 @@
 	padding: 0px;
 	margin: 0px;
 	color: white;
-	scrollbar-face-color: #1f1f1f;
-	scrollbar-highlight-color: #1f1f1f;
-	scrollbar-3dlight-color: #3f3f3f;
-	scrollbar-darkshadow-color: #3f3f3f;
-	scrollbar-shadow-color: #1f1f1f;
-	scrollbar-arrow-color: #afafaf;
-	scrollbar-track-color: #1f1f1f;
 }
 
 h1 {
@@ -38,304 +31,259 @@ h6 {
 	font-size: 10pt
 }
 
-body {
-	background-color: #000;
-	color: #888;
-	overflow: auto;
+a {
+	text-decoration: none;
+}
+
+a:hover {
+	text-decoration: underline;
 }
 
 /* Page layout */
-#extframe {
-	position: absolute;
-	top: 50%;
-	left: 0px;
-	width: 100%;
-	height: 1px
-}
-
-.internal {
-	margin-left: -475px;
-	position: absolute;
-	left: 50%;
-	width: 950px;
-}
-
-#intframe {
-	top: -280px;
-	height: 560px;
+body {
+	background: black;
 	background-image: url(../img/background.jpg);
+	background-position: right bottom;
+	background-attachment: fixed;
 	background-repeat: no-repeat;
+	background-size: cover;
+	min-width: 960px;
+}
+
+#hd-logo {
+	float: left;
+	position: relative;
+	background: url(../img/rust.png) repeat;
+	border-radius: 0 0 30px 0;
+	box-shadow: inset -2px -2px 10px #bfbfbf, 2px 2px 30px black;
+	margin: 0;
+	padding: 0;
+	z-index: 1;
+}
+
+#hd-logo>a {
+	display: block;
+	width: 260px;
+	height: 96px;
+	background: url(../img/logo.png) no-repeat;
+	margin: 0;
+	padding: 0;
+}
+
+#hd-logo>a>span {
+	display: none;
+}
+
+#hd-summary {
+	float: right;
+	font-size: 10pt;
+	text-align: right;
+	padding: 3px 10px 0 0;
+}
+
+#hd-page {
+	padding: 5px;
+}
+
+#hd-page h1 {
+	padding: 0;
+	margin: 0 0 5px 0;
+	font-size: 18pt;
+	font-variant: small-caps;
+	text-shadow: 0 0 1px white, 2px 2px 4px black;
+}
+
+#hd-page ul {
+	padding: 0;
+	margin: 0;
+}
+
+#hd-page li {
+	display: inline-block;
+}
+
+#hd-page li a {
+	font-size: 12px;
+	font-variant: small-caps;
+	display: block;
+	margin: 0 10px;
+	padding: 4px 8px;
+	background-color: rgba(127, 127, 127, 0.2);
+	border-radius: 10px;
+	border-style: solid;
+	border-width: 1px;
+	border-color: rgba(255, 255, 255, 0.3);
+	color: #bfbfbf;
+	text-shadow: 1px 1px 1px black;
+}
+
+#hd-page li.selected a {
+	color: white;
+	background-color: rgba(127, 127, 127, 0.8);
+}
+
+#hd-page li a:hover {
+	text-decoration: none;
+	border-color: rgba(255, 255, 255, 1);
+	color: white;
+}
+
+#hd-bar {
+	background: url(../img/rust.png) repeat;
+	height: 76px;
+	box-shadow: inset 0 -2px 10px #bfbfbf, 2px 2px 30px black;
+	z-index: 0;
+	padding: 0 0 0 260px;
+	margin: 0 10px 0 0;
+	border-radius: 0 0 20px 0;
+}
+
+#hd-text {
+	color: white;
+	height: 12px;
+	font-size: 12px;
+	text-align: right;
+	font-weight: bold;
+	font-style: italic;
+	padding: 4px 20px 4px 0px;
+}
+
+/* Menu */
+#menu-left {
+	float: left;
+	position: relative;
+	width: 200px;
+	background: url(../img/rust.png) repeat;
+	background-position: 0 -96px;
+	border-radius: 0 0 20px 0;
+	margin: -20px 0 0 -20px;
+	padding: 20px 0 0 20px;
+	box-shadow: inset -2px -2px 5px #bfbfbf, 2px 2px 30px black;
+	z-index: 0;
+}
+
+#menu-left div.button {
+	margin: 10px 9px;
+}
+
+#menu-left div.button.sub-menu {
+	margin: 5px 9px 5px 30px;
+}
+
+div.button a {
+	display: block;
+	color: #bfbfbf;
+	text-decoration: none;
+	font-weight: bold;
+	font-size: 16px;
+	text-align: center;
+	padding: 5px;
+	box-shadow: inset -2px -2px 5px #7f7f7f, 0px 0px 5px #3f3f3f;
+	border-radius: 15px;
+	border: 1px solid transparent;
+	text-shadow: 2px 2px 2px black;
+}
+
+div.button.sub-menu a {
+	font-size: 11px;
+	font-weight: normal;
+	text-align: right;
+	padding: 3px 10px 3px 3px;
+}
+
+div.button.current a {
+	border-color: white;
+	color: white;
+}
+
+div.button a:focus,div.button a:hover {
+	color: white;
+	box-shadow: inset -2px -2px 5px white, 0px 0px 10px black;
 }
 
 /* Footer (DCD link) */
 #footer {
-	top: 280px;
 	text-align: center;
+	margin: 0 0 10px 0;
+}
+
+#footer,#footer a {
 	font-size: 8pt;
-	font-style: italic;
-	padding: 2px 0 0 0;
-}
-
-#footer a,#footer a:visited {
-	font-size: 8pt;
-	text-decoration: none;
-	color: white;
-}
-
-#footer a:hover {
-	text-decoration: underline;
-}
-
-/* "Home" button */
-#hbutton {
-	display: block;
-	position: absolute;
-	top: 12px;
-	left: 10px;
-	width: 272px;
-	height: 90px;
-	background-color: transparent;
-}
-
-#hbutton span {
-	display: none;
-}
-
-/* Current version text */
-#version {
-	position: absolute;
-	top: 89px;
-	left: 299px;
-	width: 640px;
-	height: 22px;
-	font-size: 10pt;
-	text-align: center;
-	font-style: italic;
-	font-weight: bold;
-}
-
-/* Log-in/player info box */
-#lbox {
-	position: absolute;
-	top: 9px;
-	left: 699px;
-	width: 230px;
-	height: 60px;
-	text-align: right;
-	font-size: 10pt;
-}
-
-#lbox a {
-	color: white;
-	text-decoration: none;
-	font-style: italic;
-}
-
-#lbox a:hover {
-	text-decoration: underline;
-}
-
-/* Menu and buttons */
-a.mbutton {
-	display: block;
-	position: absolute;
-	height: 21px;
-	width: 250px;
-	left: 14px;
-	padding: 18px 0px;
-	background-repeat: no-repeat;
-	border-style: none;
-	border-width: 0px;
-	font-weight: bold;
-	text-align: center;
-	text-decoration: none;
-	color: #ddd;
-	background-position: 0px 0px;
-	font-size: 12pt;
-}
-
-a.mbutton:hover {
-	color: white;
-	background-position: 0px -57px;
-}
-
-a#b0 {
-	top: 136px;
-	background-image: url(../img/button-0.png);
-}
-
-a#b1 {
-	top: 193px;
-	background-image: url(../img/button-1.png);
-}
-
-a#b2 {
-	top: 250px;
-	background-image: url(../img/button-2.png);
-}
-
-a#b3 {
-	top: 307px;
-	background-image: url(../img/button-3.png);
-}
-
-a#b4 {
-	top: 364px;
-	background-image: url(../img/button-4.png);
-}
-
-a#b5 {
-	top: 421px;
-	background-image: url(../img/button-5.png);
-}
-
-a#b6 {
-	top: 478px;
-	background-image: url(../img/button-6.png);
 }
 
 /* Content frame */
-.cframe {
-	position: absolute;
-	left: 299px;
-	top: 117px;
-	width: 637px;
-	height: 426px;
-	overflow: auto;
-	background-color: transparent;
-	color: white;
+#page-contents {
+	margin: 0 20px 10px -40px;
+	padding: 20px 40px 20px 260px;
+	border-radius: 40px;
+	background-color: rgba(0, 0, 0, 0.7);
+	min-height: 400px;
 }
 
-.cframe.full-width {
-	left: 15px;
-	width: 921px;
+.chat-page#page-contents {
+	padding: 20px 40px 20px 60px;
 }
 
-.cframe.offline {
-	height: 279px;
-	top: 264px;
-	overflow: hidden;
-}
-
-.cframe p {
+#page-contents p {
 	color: #CCCCCC;
 }
 
-.cframe ul,.cframe ol {
+#page-contents ul,#page-contents ol {
 	margin: 0px 0px 0px 20px;
 	padding: 0px 0px 0px 10px;
 }
 
-.cframe li {
+#page-contents li {
 	margin: 0px 0px 0px 20px;
 	padding: 0px 0px 0px 0px;
 }
 
 /* Text in the content frame */
-.cframe ul {
+#page-contents ul {
 	list-style-type: square;
 }
 
-.cframe li {
+#page-contents li {
 	font-size: 10pt;
 }
 
-.cframe li:first-letter {
+#page-contents li:first-letter {
 	font-size: 11pt;
 	font-weight: bold;
 }
 
-.cframe p {
+#page-contents p {
 	margin: 5px 10px 5px 30px;
 	text-align: justify;
 	text-indent: 10px;
 	font-size: 10pt;
 }
 
-.cframe p:first-letter {
+#page-contents p:first-letter {
 	font-size: 11pt;
 	font-weight: bold;
 }
 
-/* Tabs */
-.tab-buttons {
-	width: 100%;
-	text-align: center;
-	margin: 0 0 5px 0;
-	padding: 5px 0 0 0;
-	height: 20px;
-}
-
-a.tab-button {
-	padding: 4px;
-	margin: 1px 0 0 5px;
-	background-color: #3f3f3f;
-	border: 1px solid #7f7f7f;
-	text-decoration: none;
-	font-style: normal;
-}
-
-a.tab-button:hover {
-	background-color: #4f4f4f;
-	border-color: #8f8f8f;
-}
-
-a.tab-button.selected-tab,a.tab-button.selected-tab:hover {
-	background-color: #7f7f7f;
-	border-color: white;
-}
-
-/* Top/centered layer (title, planet links) */
-div.top-centered {
-	position: absolute;
-	left: 330px;
-	width: 320px;
-	top: 10px;
-	height: 50px;
-	text-align: center;
-}
-
-#tc-title {
-	font-weight: bold;
-	font-size: 13pt;
-	text-align: center;
-}
-
-div#tc-title {
-	top: 30px;
-	height: 30px;
-}
-
-span.special-info {
-	color: red;
-	font-weight: bold;
-}
-
 /* Forms */
 .form-container {
-	width: 509px;
-	margin: 0 64px;
-}
-
-.full-width .form-container {
-	width: 793px;
+	width: -moz-calc( 100% - 128px );
+	width: -webkit-calc( 100% - 128px );
+	width: -o-calc( 100% - 128px );
+	width: calc( 100% - 128px );
 	margin: 0 64px;
 }
 
 .form-container table {
-	width: 509px;
+	width: 100%;
 	table-layout: fixed;
 }
 
-.full-width .form-container table {
-	width: 793px;
-}
-
 .form-field th,.form-submit th {
 	text-align: right;
 	vertical-align: middle;
 	height: 16px;
-	width: 200px;
+	min-width: 200px;
+	width: 25%;
 	padding: 4px;
 	font-weight: normal;
 }
@@ -356,10 +304,10 @@ span.special-info {
 	padding: 5px 20px;
 }
 
-.form-submit .input:hover {
+.form-submit .input:hover , .form-submit .input:focus {
 	padding: 5px 20px;
 	border-color: #dfdfdf;
-	background-color: #7f7f7f;
+	background-color: rgba(127,127,127,0.6);
 }
 
 .form-extra td {
@@ -375,20 +323,23 @@ span.special-info {
 .form-error td {
 	font-size: 11pt;
 	color: white;
-	background-color: red;
+	background-color: rgba(255,0,0,0.4);
 	font-weight: bold;
 	margin: 2px 0px;
-	padding: 5px;
+	padding: 5px 10px;
+	border-radius: 10px;
 }
 
 .input {
 	border-style: solid;
 	border-width: 1px;
 	border-color: #afafaf;
-	background-color: #3f3f3f;
+	background-color: rgba(63,63,63,0.6);
 	color: white;
 	font-size: 10pt;
-	margin: 1px 0px
+	margin: 1px 0px;
+	border-radius: 8px;
+	padding: 0 4px;
 }
 
 /* Data display */
@@ -420,7 +371,10 @@ span.special-info {
 
 /* List display */
 .list-view {
-	width: 573px;
+	width: -moz-calc( 100% - 64px );
+	width: -webkit-calc( 100% - 64px );
+	width: -o-calc( 100% - 64px );
+	width: calc( 100% - 64px );
 	margin: 0 32px 20px 32px;
 	border-collapse: collapse;
 }
@@ -443,7 +397,7 @@ span.special-info {
 
 /* Column layout */
 .column {
-	width: 310px
+	width: 50%
 }
 
 .left-column {
@@ -451,7 +405,7 @@ span.special-info {
 }
 
 .right-column {
-	padding: 0 0 0 310px;
+	padding: 0 0 0 50%;
 }
 
 /* Misc */
@@ -650,14 +604,20 @@ table.fleets-planet,table.fleets-moving {
 	border: 1px solid white;
 	border-collapse: collapse;
 	margin: 0 0 20px 5px;
-	width: 610px;
+	width: -moz-calc( 100% - 20px );
+	width: -webkit-calc( 100% - 20px );
+	width: -o-calc( 100% - 20px );
+	width: calc( 100% - 20px );
 }
 
 table.selected-fleets {
 	border: 1px solid white;
 	border-collapse: collapse;
 	margin: 10px 0 20px 15px;
-	width: 600px;
+	width: -moz-calc( 100% - 30px );
+	width: -webkit-calc( 100% - 30px );
+	width: -o-calc( 100% - 30px );
+	width: calc( 100% - 30px );
 }
 
 table.fleets-planet td {
diff --git a/legacyworlds-web-main/Content/Raw/img/background.jpg b/legacyworlds-web-main/Content/Raw/img/background.jpg
index b5bd6dfb26dadf0f9aba4c700350887a8f6abe34..6f101d0ccf6563ade312ed09da53bda19ced4c33 100644
GIT binary patch
literal 133286
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
S$}xuT#Vr(6l?o`LfdAQPpi`j$

literal 91318
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
HP=Ei~=%g*K

diff --git a/legacyworlds-web-main/Content/Raw/img/button-0.png b/legacyworlds-web-main/Content/Raw/img/button-0.png
deleted file mode 100644
index ed18c52667e73bed017489bdbfe4cbfdefccac20..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 32007
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

diff --git a/legacyworlds-web-main/Content/Raw/img/button-1.png b/legacyworlds-web-main/Content/Raw/img/button-1.png
deleted file mode 100644
index c62487e6a7697292d8db1e68083f95a343b58706..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 31317
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
aT{FkoI$$%c26<dGin_9n(pLqmnEwO%dI${w

diff --git a/legacyworlds-web-main/Content/Raw/img/button-2.png b/legacyworlds-web-main/Content/Raw/img/button-2.png
deleted file mode 100644
index 2ffec4f4c7bb138c45493c85a295e6bda52cd4d2..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 30310
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
D3FE;y

diff --git a/legacyworlds-web-main/Content/Raw/img/button-3.png b/legacyworlds-web-main/Content/Raw/img/button-3.png
deleted file mode 100644
index f35a722f68f63de5aff2f47b66cfc7af40ab5e41..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 25647
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
a_P+t8MIa$qj`?8#0000<MNUMnLSTZLVe=>e

diff --git a/legacyworlds-web-main/Content/Raw/img/button-4.png b/legacyworlds-web-main/Content/Raw/img/button-4.png
deleted file mode 100644
index e2c95485e2459af921723bbe84f8a14cb06efbc2..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 22337
zcmV+JKoP%*P)<h;3K|Lk000e1NJLTq008;`0043b0ssI2^=(_<001BWNkl<Zc-qyy
z+m2*Mb|q-r%+7I-OJ<Tq7O82KN+2MiTi^h?L4YuVpa;E3m`3Tj>K_E@KlBZH)+Y#!
z2Bv`?bfYy-0=gbXt*)6?NkbN?B$<&B;qK=gGu!j9%+}d{+&$cbWKH)3s))=;_j57Z
zw)fg=-$cB)V?R>=qwb;~ao_Yu&2{wj1@+9m<nU?ai4V&~caZj5l`~&9ud??MncvJV
zsR(W+XO^&SgsS<XeK)yq2V`%NPu*C>iP?QM-;??nd#@uRa^z{9?!D{$f3w6Q^CyK1
zqWc@C`zt8Uxs*#$^&%oZ#t=eSuh*;9s_*;NdYx4_+wJF{eyYwnRc-4QkIP%Vsvf++
z&q>xVmgckf(z;zvxxS~b*|uZ3S;ys9_ZLa`7R%}WRoeBJ$IlATynpirngCt6sPpsa
zMf<S7tw+D5`vR^DnQ(hjd-O8)xa)GMk1w(L<Wk-ghg{9c6z9B1DJ7R8Qt;uFlGmcc
zX1G`9C9bc6NXG6e4vY(OxJh!j?{b0s>17hB=fqc@bA--s@=_0;{iN=xzf)fo>Qs9Q
z<e27={Rh!@5a*Q_b6Gw5^hUMo-glM9OyoKGM-FeHOS2<SeR*F)9AD*q-OhCocjQI(
z9!3t{LCj6XIp;;xsjBjFBCgM4x7(~WuY(MCcQJU^buo6*7D~j+DG7YeqUmKiSWjF*
zk0A$l?4M46hVycKsj(M5&@XcGaLJLAp(oZ+8csMS%Jv?r!V8}gccZi{C|bVflUPmm
zK<mk4CMVZaowoBP?$`t9i7$!}y-?y+Pah{eco22YY0;Dm{@#1Jj<OcFawT_NQMdN4
z+}!Mnzq)zloQqw|xrm57A;y<UygmnV9F@_dhD`Pi>l24W&glE&7F5oh5PkAcpS67=
zr`Af2qiqY&De&fFd)ghAKo9P9OicHdNB4zE<fvTA=~H8^lUAhRM2?&gLi__IEqg*N
zC(hd`O=mz<(VUBOdK2Vjs77HB8J)k1-ludgoy6WdExD}L>-Y?r{5G+>TySR8Eq$f4
z7)GB4^p{;^7A?8#a{A0wIPrO1+_>IGk3O}W7lrCu@4H~jL#UTLyzvlpeg@V31)tZG
z4%D%ybqC&w?(dr(+ezIs0>;txUP{)i8odu*T*|xRlFP~YZPHPvkkvU=bz_?RB^&dt
z{>X)bak!TI|Fes&7Ye<$|Fw_ki5u$Puk`4}%_eF6l<I+&I{jcvX*sbzYOkpK@+$JA
zsH*w89!S-*H+m*Go)AiNw)FT`%d!2{Csi!pKo5T{A|m2cw<#BO+v0L5nzeK4#fiA$
zv^b~Y%5hiq61MK=Ld!TyGknc&Ui^6b{GXmZkj5FaACTiVN<_{q+=1zrQ-42nEM*Bs
z4*4cILg~r{gpZuM?7=bKTaWq5M<7~=o%g;yiyo@{1MDo9@fmfcy}0O{s>`LQbKdE#
zcPZzwxZ=IVMHh(s8HxYKHvAMQ)@L6jIewdG^6nQ3>C--pWlQALhZp<3pm&ZPBv}A%
zy2^$IRdbrv;^1Rs%M(!l3BfRIsmxxo94=_^-g__J-`?C__uaVL7H!mwoD^bt2E4A9
zN~&WnMV)u9s2iN@R3`CGe0?ZYCl`wQQxY#1U{`%oVDuzrztnR$r4=r4xt|r5y8Mvr
z`;JdjdPm63%XHgbDDaTqI7W#q*`YHpb86Sk$&>}6Qv3Q|oKqDEt1{{1TXkMT7h=4+
zzIINul*=G4*s47O>gYBW@ssnTlgat2*Y{m<xm4T_R{r;Y|M!3IfBC&%{ncN2|NZaY
z+}se?iHNF}Qc5YnRizZ~J^rfg$9tc1#&4W+-h1a<DMeN7@9_sbBz}X>;0m~A&Y2hS
z-rHN?N$_1<1i!?imQq3pIcIzTSDxXZbG*2HNPKedMj}NOpX3+uPay;>#R3G+<a4_?
z7K+bf?`{{?E(KnJ7r~#quEQr&N?ZZ_lih1BrgQ4O_deLQDkaC*;WOH<uFm0gu(nP$
z<?NgbAz<+=z~C+F(#?w~lsZw(B}0f+)q7EOxOhs*tb=n-#&Nj2yZij}&wuihpZu4<
z`@56QnQIKGmp$uOS$=soV))??fB3Ke*MI%thacjBC?#rsu+R`(=NyFwn_>5f4}h!j
zgy6C~B!~~-^QsC%no^?V@i9UOyoePk{y><$5B33gFOhm#{8I=)Rbz}PC4QZk%{k+C
zlt0KB?}D?*rz)ioc%+nYghZs2TsH-hWX`{HF6RVIV~>sZf{WvswVgLUVb^u_Ayd-%
zTq%!p&gJm<k<F*{1COY>Qc5XV)rH_oQRi@LkG<oG;`y(C_FlD=TwKXcL`6irmJ+>3
z$S0RF<$`q<=fr!}QrUd*DW#IL55D9gzH0aT`?vr3KmYju_?!Rao?^HBfj{$1asTyS
z|Mfrr^FROPU;gEs^Dqp%-42prHdu1blyb^FHDW2*xvKADjQBlNnw4p8KyLbDnkI@A
zt{-CX;=T97FyMGVyXTy7!b1rBJRgJCgzAkknw_pnk+P0m#N)*nLkN%(=s##g$QiVq
zcrPL`grYi4Q_jiz5JIrJs_%OqEl4oW3{IMw91jr*!Hf85nxy^UD|~F80e%1<R#g$_
zy$>PcGc<<S1@F20V$}g&4U38A!PoJSO~I9RY=v9zy-(AmPF3q^#1_;IbuQ;rR7=s=
z-dCJDk(6i4PlLeIg2!-9MSKVz;Zg7X7hk;o-~au;|EGWYr{g519Fh>v5%(Yb;0J&D
zr++FU5cJ({H;&`u<KuR_ou&!W1_Mgfjz^C%;$Nx1opZx5;H|o@!+P-w_=@*_nx=Z_
z+d+ykLhhj&u|Vtf8X`50V^xY;f)CK3SaDp35Q{?J_dR|Cxwll?IF8cPu(dppdt55U
zn9_tj%DJc(xNo=`4U}^ZivZPtOT`!$dU~41zFz^P;f^?O-utfWRLeL{PBp{`O~FOB
z+li-w*iI=qRUZO&#2TD-w2JouCYNR{1V2p)0^RpL<lB2cjbl-TInq)>dj_YP3)FLg
zvWLKyl2gjw`w&9cbt$EslT+t|hs~*r05DS%V5&LAyXTagb0LOON(fakh2Xoc$1~@g
z|KorB4?q6#kM~)M!*oM^p1A+u2S50;Kl`(k(q^;SY&H)M4|jKW_xJau6o>^j(n1j`
z8=L`%9i@%t659{WF-?;_-1ry-1q%T^3U!!EPAQdKXboPxc;TFb&B3lr(-dQjF^=O1
z{1aou65!b2Xc5+1#2jOE;$n!fSE`ERgnL0)Lx3r-x)bj*+n*31Z6Sn|CM@`B)#qHM
zX{w*`-ov<+()B%#77ta<d7<xd(r^q!WW8SFWHuC}BA&XVJxoIUyz4reL5n}}2ZDa<
zY^PKopzFGJ$a8MW7`ZFhyWD<{(+jOvO368?YR<)byi`mnr8I>Qym+g~bI$Z}yDqB2
z(JIq86)kwL5JKN~xMPgndcDp$|IOe0ryu|L$5zM7@sty}NZfzpH-6(U{^Bok&X13e
zZ{EDQzrVk`yW4KJ*qhB}1Noz~*ks-9b_0Kpg<h}MA~KF6^tx3}<2XVsQK90HSevj~
ztx`%cM%)nxth67fe+a=eO~WvBU8hdRVFY#(FF9qZK%5+0gh&^^-|cohs1P!qAL`V;
z?^Iok!8xIzTW6X_H+Wwe8WET*m}4G{Qi>PJ#$ljbHdt7Q4G5`ADZAYcHmcn^hY*0X
z>n&8Z$<s8A5SkYL*QS6RT~+UW*F`us)>YW;b~)$1>$<+j2Bnl@2)TJ99#Es1CJ|9B
zUc7iO&P~;CE-7UZ_|qO|-g{Z~t5S;NQ>hN)D9(itLX5%tqN-Y;bV|{_>(=Xa-}eAy
zs`~H#-T(H<C!d^%#ywNq|MD;Y@|S+;m+tQFzWCyc`}_O1Z{I#VJZ!gH2m#PD>>!je
zln3Mzzl2_dy;JA9*jXpiq8*w7EGXm>L7U*KcE??)>A5SggLI&~t^+zsDM6aIyR8@B
z_dT2ee7Ea596WpsyWaObOwG*OXmRRRs}*D}r346vuS2=wD^*5Srv`k9WGHHcc0g04
z;Z<QMl%9`s4mQeiSp+k5Gn~_uvSpu|OvCpmurrM|D5c;ckUz10R0I8?xzQ!134g^a
z=bTroRg4jErDf1~a#P-W*u80*y!WauhTxjl;k}en6sNa28(?Oj@}+5(VHlv-LI^<Z
zkeX?neDEQ})oQg`twiL_o3H-*umAc~c!z&E&%6KVM?d<#-}^o2_0K;0?Dgx{4-XH+
zFyLROX&T27b`atP(@zkB<>N|0l|lrkX-X+UVdtFTV9|)<3IyxW%CM@~8@dXVu$=Qa
zjyQxfsPGj@=bUMgp<-hU*hScDe34#(rH@Eo5L)0&;*{=oJAAV5dpvMTN!#(m*=Ryg
z4<t1-Sw7$aaTZQgUm3g*W!=aJG%+lvdXJm<c0^ruA#$P5H;yCx%f9ce?tqo(x=82J
z_`x_%LQ#fxn8vZ>()WGe_fDiV42ETIZa@(6-b>D997ojxy<X8~vu_ROc<^y)np8E!
z2o<hcMBFqb*g#M;A>e%GoD!^h*Y%%&{<Gr&4UyH=)$Pky=e7G^{ncOH-rj!p*=L`8
z@(GkWTvg~Byb?|=1d+RfJ;vdK65=7j^O4KLML=2bG~+DMPc5Zvw_6x`{#ag_fHjxA
z-E6Ce)OVm>EeFqMwvGfozuj&vny0)1eL?<su)X*2rF<if@Ln-S7zZ*W_7oYa!I9PB
z`D8qyF^QT|rq}@-*B1vm7l4JjM2#~La3l4<rRsIqTP!K|0<;fcq-COji5o5Ek$YGS
z*;>!PbK^9{5MmdhI7-ebXK+)EO$H?MIcE=&pp+N_P*qCFi&y8o$dnS_&PG?du0ucy
z))IUd*v4E+2;TePoQ~u4Z~yJTSqOUUy7kr7_4SR2sGigAfA9x?@JE02M|XF3Uw--J
z+qZ8YA0NkY%sF3OT|r?}F9I!q6STSkY6+AdM4#dU>BK*A5wY&X_qaIzsbBRmL~JUw
zI1W`VImYOv>P{~nkOTipSD4fav;t{0>z<OkBsW3`frrFj$%@3T^C3V1;{@_x<27Rp
z4N^ytm~<j(@p%++7oMn6$}}bHH`HWPy(Q=3AS>;?=r~barfC933NdJ5Xsn*_7$Yzk
zjwqg?dN|_7u_~JNYUP|jyhDgwG&02@1a&T_T<wqdz%7UscrV@uS4|eJmQOjCqNSAJ
z1BmvNGicN5o96680MbL%5}j#kL+`y0-Z{yo<dU^$O6l(I&N=lyT;1Hf^WJ;6FJAVm
zb+yuG!ORbT_`_fLg<pVg3eFK*0xBTJNM?XkI^~QpMJTgz9DzY0>ja=!vcB)FlAERp
zA7ivAw3o*ppg!wXiMRG0ABI~2MO#XKe0(I(2NjL|5Rn)|P5c)J9*vxm^#m9ty}rIC
zhl67Y&x8_V@dkvr30CpJL!cXq4G=ZPVhxGzz;9HOoeN$m#_C5p=Rym9!6DFn_Py^{
zAw<MJ#a9{^=dI+Nb1FH<uH*4AZgFE9RiWKt3|>5xSM%2@sp3QEVifN~J7ZAkjVQ#a
zn-vYjG{yk^)}qnQOAV<8AJhR@%67Zs!_XumXhhe-dmmzO>ZWP*KHS~kzw_RE@4o-t
zn-?!a=yFc6>zs2rr8G@v#QjG<`q65&dj0zKS6_VvlV=!QA$16M4Ppw-0Yy#>fLDd|
zV+BDb5ReevLNcu2TG)e2apSA1)}lqbzKfx<-Mih6x)AHjGsfSjI_vEL5)fJ-zQf1B
z#^WUOFjLu7f-h%AEJ;;4=Z0Yi0w=}}CzCdsfWgB0<`vAQ9LS!gQbhW$b5e6o;Mj#2
zwG<ybKFk0r2otQvFjVR3`yS*vq|+u%h|C9uN3L?LB3&0t(Uek7iK`*vD!7a>80?Oa
zLf3Tw7&)iCv}KX5i#eAXwRJA%1V%sNsH7}S9w0OJ%s`whi&cuQR;&BR$1Zl>`w(M@
zK}2#&!*;vdY=+$~J`LR8dFP$S$H&LVM|fkP*m?9xn~(`!tyVUWetUZhJAgGs&<CbD
zrL^5{SF4o`OO;ZFVW1+1G+-r3&^GqU#~8c58;4Pfc<*z{*;N>Db#(<(4vK?DgSwNR
zB197eGyckafl{>ZJ6a@YWguYO9ed!tU#)uDD@X?K=$z~ORZ7!lQ|ZnS!fLgm&co5A
zOA3fcqnAt0LuLe;nF5<eiv{oD72>9y_YDr!e8r-rR{fN+TCMT!!!U&4SF0Y8(NdJ$
z09kPB>@rwB2T)_oCAY(%IZq6pbY0hVF(p_q7=+-xc;DeSrKr>50H#C8IhO)n-=~E5
z#^9Wg41hj0YSAMmTXL>+MSHwbipv@Fch`4C-QAn7O3o=wIDTiuz3+RVcEA;e5*K-3
z*a;|X%g2$Qv4TZj_SLIbjJ9Bt;6H)ffF$9XfWkxz#1d($&W%Oq<2dYgyY+evu8m<S
zJYfrpA!e!6tc-IF!$5Xuv)RIdhR0?UNnHnUdE+>yl>5HNcNxtg)(;`lI|nx>BGWW>
zT@Ng7BdM#^isy<!d(u`|gwR}wQi?Zm_3F)gAG@yRHH6^Q6{-3N$~ea8eci#FQ;d~M
zg@}U?L=bn@^DX|(RG(4`Tt;Mnh<M;bkh8c&s+hjO!-d=jA8=Hdf#Y1wE@>79_^O6P
zNM}=!Dst|+G)>|qml9)0$xTy!czh%nUiW<(hjE(Rg+%0^{K=oZe*OCX{ywFIy{z7$
z(MFaS!GEFSL3=@TAi=ag*n8%C(E&oM5LLzF(k#+V#99+7*icj@;e54YIPSFm5a?-|
z`o3?zT4&x}+llRXYzQ#Gg<~U`a>&HM5CUV`46NFWF)B!Mb5&$pDhpPIq$Jr!tN=d4
zfLe`IbX{n{W>Xrfcsmmb!5I@(*LNM*@o7r1u5cES`PeLyBHly_feCPAOZcb|Vy$b1
z1h`CeK_37<JZW55gO)}SsL^TA;6bF6R4X?nhJ9jU1z-<;6@pJ`nx-k|^7)$w*pKX^
z=H$gs<H*FfGvfY(AN=6q;Q>ynb!f00@X{dk4ERw81E4e4iYN%D-|~PArVy6h+}uEj
z$>mx83wVQh#rO;<PK)ZOP<d=fsN>9l9<ec(oD!dn@CILLzjsV$fZX88aXVrjau<z$
zp6P$scOLQ>yPQ*VUcG%6tQ9^?np_Mq$}BHDE!}!-N7r@ZI1R&~r7&Lvr^wYrF3;ps
z@4Yxzn*QePY#hTTx3x!A1aWbI$#I!%wz`tBA$!D4#m`2GNmED#14arVsum~(rd!rU
zL;MU78u4Z?(=_FxkE!_BPsP3Zg<t5_SHt6DN)rrA?7A~x2CD`W$%rH4G6+vHl#NIm
z!#!}t@Byl7$|58WzabhXcR5W{?FhA65%6Gd#&M)QO({`ZLq{^{4`<-&>IxxJtk-V0
zLwE)6wcG7{3s^t|V`Pj*Ykq-qAx6Tjq9vs~juWyW2+y2z(=;N^Z(kOI!Uv8q#8`v;
zAZ@{!#u%q*#4*N`U~4Urx?Zob!RlP!b>QYe*a3SqzR%grXY3KeG>qLMcLn;IXfWrL
zQ||hnN&&hIYJ%%;6Endof)=ugJ@7seKWe*NV0}XftG-V;A<$b&0fJZ65WG{Da#k%V
zl^C2lm-9$b!GQ!N73a9SPScpCaTq5N3Df5N7q_?9EA6B2yW!?#8V4Mk^Wu&-yuQBP
zY&I#S-EMbtbAzN5rri?NLBc4b0I85dIG{8RJk6C*?|TRz?g%`Sb4KC{1dFF)y<VrL
zfDsNvJOvpnuquqL!sQ?l)O8*7A|xItndt*I7pfjG5oC%}e|zx~MKZ%QfnF3$IYTxk
zSC;Hs#N7e8aHDCOcDr5kQdGnZ!^m7tG66g@@Go&4gi#u~#3+!Xm1sxBmt4&k6e@5I
zvwT~)y6-wiBhws@1j4FiZgnl|%dr}R(N@c5!ejB?BefBElX#v9JP|xD=W51vDM*or
zKEii=^O7Ox2&*B!Hl-<q*oUB{q?&V{M5GHbjbm``&8J`NHXCts&~O^Zc)pyYWxY<*
zbaQjVEP(ZT4QXS>DI+ZOGMPI-QnIO*V6zMHc2`$d%r9V=mbQiJpBeBtMYJc|?G9@V
zjowgDjbtG^3Qt92jb}jg0ALp?u~1$`T+4riXcjFrO?cFn4&-C(=&>*k0;NkYqVGHE
zU>;zs10J}P3|oZbN8*)y0Jsj=(o!<KN$<V)5f`bHPSdQFa&@lc9K4?rJ}+I@Gns<1
zj^?#{G_jec2?;FC?Uq*kRE>#9g=)4)$|Oo;TiB3N!=-bMU?iu^stM2%jRu%2o4lzD
zV-W&%!D_XF<)6k8G2P&bIAmwkjKe9VGvZDeC4>b;fitw-Zo94nK!?e*@DxYNCQK8$
zP#_u)Rp`kPF4K#JMsZCI;5WDn)HD9|>S_(ug@}@cn^0ZW*#b_0`#%iBFzmcXW@#LU
zVH^hLlrIX4x>z%IQck_^Yg%?C!A7cWqB3SL<CqP@NWz}>&1OV<AL#Kj{y?&bjuXpJ
zNCV(Jx3vCDE+wVOIVfp|dJZkc`M?qd9B{M@p;oF=*u_??G<olxlbn+|$xB%eAxQPA
zT?j$EXmbo2vUje2%d4VL#yRJTYHSQOs{*R`tcvqLO{1x$D&v@K#tH)bHGe5*<SF!h
z4^+Z9z!`BTKZuh@mlPHUndJ;;Qg%xz$TEO0%Ww&V0}&_~aUdmVFZ>l)d{M*3;5_~d
zG|zm@#(vg7)z#G%EPy>!El%W}6J}F7=VA=2l_33S9H&wSMvjn!9vTZq&V<>^C~W}C
z)KaOu_2d}XqBSEyL`j|bL_bHMSjPZHoasg_D?wVqML^gxr3Bl;yj1T+Ra8^g&1?a{
zp7UY?e3h_A*eFetH)%pG1)y#>L;@MSjeHL*7GaQ8s)%PP61+!<6zo2Ol33L_$bAsd
zrQb3(F01Rh7{PErsi!neu2oUhDwCW)``OP3OS%}<ITnJc>KSpz=D|{Iw_6xlD5_El
zDzcFspsKswPF1h2u7KUOK`|yy+RK8P1y%vs0;dh4KphMV%fr@n-7pN4q*6-CNgTql
zk=UJa06afJMX0M_nNl;9HDkmUxoRl{>wv}13G;rjF}vLkK|vNJ!9ZDt#6|(>hy!K-
zQII5s^=Is<iN2#*XA#|QJ0bGVvJ#jJN>A5C5gg!5?**YBq!u@Z8!}oMh_G6rq!rvH
z0!$>4pg2%c<Qy|P)w$V3(;tIgOVcE(B~88sUaYAvs=@od?~7_~*m55T6$WeJMwe1h
z$?C<cxYa6XDY@j7G7fwxS#yp~r`CR8N=f$X#N%1uPDTvm00{VT99OFqd@OjA0D>rY
z!Y6Iqn`AZ}P=roFtw1`Vz^DS5B~L8DR5S6SMa4^+QdduW#rm*WB-fCG1#!WiLC!*m
zbdKru=9EfvyePyl%`ho=*=Ad(?RJMdl2XJQvG$BL9QK-&6~yq@>orR{NcGsJ2G*R4
zbK1;KZV7hOO7!!P2Zn$@)`8)0#QNz&SKBBuA|txFhWIejjaaKiG>G%4EnEn>WZ6{Z
z*@BN=a?#vYqefb4i-4JWX7noOoYDmI+ZfW?uo~_Oa9XPvtePEQs^mPWI;WlJq*9xR
zw>AWY4W6Fn-T(NH|M>0Ow?Hg}p@1AjsB|)b0VpW+B#=f%8;$Fz>UO(jIsgm)na9Ft
z1uq31h6HX#sTQT5Hl?f`*QzY>VKUsM<Q5*PoTjCv!8^GI(ikzI?%|s<8VV&BLNL|-
zwbYeminLL!_7CBn%or9C8WGW6M}|h1S#!bk3Kjsgr4tr+V-Xe&NEn<}=ejPo`VzIp
zO_Y1SK96NC1)c;8qijBAjh*`-wP-_f&Q6Qd61*S|;7kmJE6)_Rc9_BISICFf8jXE-
ziJntRD8l{X|M{ol9HR`C<8O-ljJW^dAO0bdD#@TRUzDhWX}zT6>54I-iV{Q7WY7)_
zG$;brAJKEL5(A-cnSB|{YSTQ57L-TOpB8aYW(<6E4G92Bwj?d>x~}Vbc<~J-NAVst
zihR3@Q%lK!E8ymGcJ1+i6fJOPYNw4V*nC|O{|#Nt*>Y6dEJ<Nbgsn8S)=zACnz$GO
z4iaq|jpU*m3<6fo5enA8+r%}xXVxQPfjP7ZlO1B$O(~_6k-FqV5Y6n0F^m(lGnuS6
z2a=jS;V#$n*5WNW7hl7OT1wvShLn=i^5)Zj3QmhiVWEuao_0pufAcqg6Krvq>%NqF
zmlQj;@apP{0%}u>7>k}-_5wpW(CDBn!RrH)FoBkt*(`A)sX`Z)dXDOlv;%xc#t?`W
zT0Aw_42WSERBNz|H9;t-0g6RvSsEEnI;HFTsU1-^|FDXdp$1DC5%#p4f&jJ3`maXz
zNi&iwfD_hvF-3xm4b$~zG+O-xzt`&3wA``z3xI#E509sJK{M{E9bidr6-3su%sIxT
zz(mh!2nhR!2xeAIyjCd{^R4F$4yc1_7-K9sOG9xv=W(1$(V9M=@|QpRq&PRJlw&R3
z&WQW>zyC|`zyDnnWkV3*{V*|*zBZ_9U;v{*3p4b|-!slk6-eMqEl3q>S_@?+1<)?%
zoO8+E<odEG1kwQg&11k<AlYvV9q4W{-b6Worx6WJMm~<UuspP6jjR_zww~le3+6i-
zJ$B%M+_U!+D>GoJ2+R;EV@V<649?A0o~B8W5A4)S<$0KAru)@NHs8H<h7tDC;A11j
zh4|1mT?21Z>IszK15jgCk>Ji%b5Y;I6m8iZ^OjW|Uk3^cj|iL&1+Ogv1*<qs({8sd
zs^fP1#p{3GIG@BXS|!&A#?#^+Lj35Xb(B8}001BWNkl<Zj~*T#XwInN2*I((Jj&38
zkB^TwjnQ_cV{Qpa>$x?G!HbN@T3p2ZlNl_U5_)VT*mN82CTqlsjo5yeAJ2qsdS=70
z<D1T$eT-;p0~OBveinkP*DIa@nqzh`U?rC=gtaiwG)q!j<OSN>R*Df@HvIw&J==Y1
z)*6J?(#4KR>MY$N;Is#?CV`crj^ui65&*emW)z<g=)V$EIhR~ej2F8uA|&L|KFJ+(
zA@;-&1cPfFV$M0`jKU?vxxM#AHKnN(ZB?+&Irr(OpWZ!elJj%pNvRECD|UBJ%KOiL
z_UXqT|IbtgfRC0UCAzXXHV_d2===M7Tk8O{$7+AZW#G2L6R>?$*wc&2C*&yEor_r?
z3{rMoM5Wo>`l5B|U~X+{m66~;OUceD0{A!ywJ`>iJ>t0D2W+#5Lv3Cw*kkVJG$o*9
zr1CTTVH*b8lzl^Y2wBb;W6{#mCxIoh?Uc4q%*Z^k@=6|t^bVT3V8s~FF~t^pc`rP8
z!3R3NMcKqA+w`aAQ;Ka5NZ6Kg9w)TBf^DzzNBGBAjxhjyLOt@p<7zo4Fh77>2#tZ@
zW_J4D|JVPTQ9|AbokhEiaCUG}-><&&o$pZo=zG};BjyG&j7*jQY&vLr!aHpF{rx?I
zQ*aGI@q+SWZ!mnA4je0p*+0zIRkh-7-%Lp4NwXM~cp8cSs=8XOEgQ&`7@lW)v~cxO
zN@{@;Abxh^@IJ7wh9%mlGGGuW_Y@Hv^<fx7>$V4QVgOGCY~Zj7!D%OLXH%OTY5`&6
zGZNBftcaT$c4fskw!7;(#5JwwZCu{c%wtBxEyk!u2_iWO#nh<5!U<u{NNe#2>usCG
zqR^znA!=<bQYmE`N3zLZeDTHI-J3lQUH5w<i-fb2>HhH_|M6e^i~k8Lj`yr2o#``S
zVFo0RaF%+gbtWmB%?4Y3dwWZ^9#@8r#J$FGWVHk~o>j06&9D;OQZ6(gWDg*+P51+m
zy2S>zBn7XAHzo5h_cV2mv2nHrAu-bTeT?01x3#^{Yz-G5lC}jeOSw1ww=yH}&8B(V
zyUO-^vK&8z(t<;q#l-WlTCMF}aA(u8XU3l<TS=JNZ-t&^zD%*CA!Tr&HBxn@CJ#uX
zZhdP{l3OZ#EvA8B+J>0SH_o|?<1h>(o<F7ZU;gg@JQX;i(1%L~F>W^77cX8|6HIJ_
zIt+5{G(CWoh?qjdu`w7D9?8eW7_YCdNlVupS5@cS_4PF?JP@}sr(avFbcIX_c0q#Z
z0~@dtCA)***$_ZsX}!%uhVG$H$-Yr@?`c&*cdo|4^DKefL`)~fy&M6X7YSQzn@5wp
zT4cjB!ISinYuBMVw_YQ2irAvew(_GQfS$5PR8_}u+H1<$2ou{L&^G(sPi|FmsYs`3
zMLC*tVIR1vG(*r*P^{#A0Fy&q!z#g61&hRYZT0L(r^)Czi-?<Z?!W!F|9jZ&4(Bk~
z&c<#r*z$?VTmSN3{;ThQ|NGFjXas;@s&zCi+X$m$)7J16Rzi`EWT?`&I!kN`AzjPm
zD%K^1%3-%*OJ%w_ODXn!k40gy39mB@qh;=RYa2o$^_Ej|jSXZBmFyv7PjpEwmVj*8
z_F4>2TI$9o2=RC@6A6l-nl+G{&2%t(nJMX9Vb=H}U$>di+L(^Eg-3!ZaSK~L_uq3A
zV#nktLz8FZv}mbqj}Sg%W2uzj)dVKnz^Im-Lk#S)ZHr}@v}43QvnLmo$fuuv^2sNk
za8jC@k;F3u6wL3~^Hfjul620wU-^|^{qVyNfl26Nb**y)_XZMA0uFke8*FzJMw~*-
zR<Vn&pA2i;M0sZv53<;a=85&mmYD4A+#YoXj;$|`+fV7vs@lcqTdq=z5(M_`x9r5C
z%8mw|rh~PrI9k>P->b@Nt=I6%tnDBIv2$R=@QJpLiQ%e6BW&ixw?K*_Wr!M`_`Z_7
zrtZ=9(dQ%CfeSqOgqn6H#Y`NVUdl~Dp#4rRMcHDO9w03ko)%q;P0@4t+0TCV>8GD8
zCYR_z7qK=^M~+Vom1DC`zx&+}pyL@uVk1)St}}sd(%zP%C7vb`VZ%=#YHYca%`&yb
zFz;fsRw&=>8N`fpQ?KCIT+YVooA%xF#z-{LTeSf{Te-Xdm-Y!4ZOq87*gS9BvX8-3
z{>GL9(_cpcO6)r3!CHdPHgvZkFq>*YGi#4%mF#w`(Ps~(y_SVr5CDrPuf5}<)p)CF
ztxwpHuxhCt<4Gh~$zfJGtzRkSAO7JV-n{v0IWJA-DcS6H)`mzO&Ims@#p|PwK6?4`
z6|K1`aTn{F*pMMr1v`3Lo5im$QemkB>`E<0WmtS>nP7&H7L#YV*|u#wQ&}im3}Ljk
z%W4aM(9@l7WV2k{ezEuBugn8sG9zgdThPr%wWZLuespo*?U94+vh27Wf`Sqx+f{~#
zli4!%QVbWOkQW)zi-9E;dD~~RSx;sk9jaHUWn9}eF~#Xz4s)Z*6hZFay!q;<KmF;+
zX?J>Lm|bkMaF0!~lEqZ1rOW%l2Oqrq?z?OX&h1&y1@;A4vtu=o6}yG*VSb3M=mCPV
zb2w=0scUJ47hgXtsyLVoRe<ts)y3jt6nYC`*(n=>WZSD-wyw#snhtn2Bf%{?lJZ?_
z5)ogD`A8P&5sKI*B{p#bekY`y9h+^@t!>0W1p(2gW5C2Gte92#Y;laa65^0>R!ULG
z6l>pD{%4n(;-RI8iBUSIDW&apcX#*Z^Ups&Ih%2RSgTY)cUNB5!O3Mq+nzUJ{o?tO
zUu&G?GY6-AqpPd?`tN=6n|fw;;!_Lu%qiwiux@hMEY79+Q#m=2Pfku*<VKhYmB&|D
zmgqvBD>AxweaxwUA^LvjMu%{#o}1gNb#$42{XN$UZu4#b=y8d4-|hkUCh{+*c!6*6
z8Rh9cee$k)_(wf9zxtBR*ZtYBrv+D!%gxu9Aa&jqFX|*bLwv=V8Oh&R>z-Hq->RaQ
zZ&ggc(Rbt<+(@3Z3rFAjqB+@SMC%i`(4*g3!rx0?Lk^|dJu!OQ-u>x8wNCcUmN+ji
zI2T1$-i2=>HD7<e^>S%`{t_?W-aX{!`Gsen3%MB1ou7Ozmte3{;(JnO%W2i7a`?jX
z^xc=y(z8Foxs^EQB<pj<{p7*=hARL63vvH;ZX-Yc@5nblRC+iGT+V*xq}<CzuOm;$
zfjoURS)e*Osu|_Pgm_b@cibpQac)!>+_xj{a=3!>Kbpih6(e$NuP;Yc^4x!ruLJ+(
z>-q3cnua}z*Rz6qApaMfLi5<7Pvf!U4=MJw<?KoxN5zK{TI%C`JxD8ZAp;|ayQ|-b
zbNF_|U7sV(7m+h^@ux3ywJrwd=dLVInmD=q_{j1IKKY1;EVf<(0(5!C<PyI{PZ@>7
zTZL~RJx9j09zKVh0`2nON8-<;E_u#h&p-L|U+Ni7&7P*z^El)@%Xbdk%k$pRYI^rv
z+b^ddQ7&Dq{VRlTAU*q%yIgEJFClu-2fuxpf39%KB@g(7)sU|#{c_<aRG;O1?ytFg
zo#zob`R;NC8p&CEctlk9uOZJ_FP$O&w;*A1sX~&=;m^-o;^lIb^7Ei_&f%Fej@Lz(
zlS^Ldn+pBYOwPp=nVxbe<eA*sdACf&%afL0&xwwFOOm3epZH=~IL^SxGnW4N9SB?X
zA}pZC)YH79x_|QI0*J*d5M9WpJQJ8Kz18D4)}{*8u84AJRQF$JktAonsE4Y3-v^-X
zQFh2UwEMchg5sP@xs+O<T_+}B7DnIqtMxjoZnoRcKmC;bNpPWfO4n+2laE_!TT51J
z#lIbK|N5-Np?Ju}U{NlG4f~f9vsg3fbHvuoAXL@7pd7j0VVM0%aX(2jUV_7siz0ID
zxQmf^F;IHnVVj>;<Y{cJC(!u9nH3DR+z)seH&@#~dt~F5B7!<Yms0Xtbl42{>b%7D
zRS>CN$lAAzcGo8iVO?&U@7n(8XO(b~rq^>m$Fs!!<R?v*)x|(qj@j0yY_>SD3S0KE
z^kN-rW>Qm)HuYNenYa@N(If`$<*~M9f0h$;1jy*p?9eCE!}P%eaX)rvJqZX7zPNAr
z_R)x<2i~kH&>L(@sZ9t)5614s<vxvl7sIN5-0VgPyU;mTOJ1~U_w(eKnif;u^zjKz
z^xc6ut8ccyQhFKo*UMaQ8mVt1mQD?(O{E;AypH2ZeadOulLS2hn8ohytOQSqyHQ#e
z6dCVTPdiMq1U7P*4=YbB`fQ1`owwtbR8MS%$c&6+k)f!n2N0|7vd%w6RXZ1*t7U$^
z<UW-kE{dB(hGDwu!s_OxEB@-{m2)n3v7U{m4W3QuwsV<VmTyh<JvhN~LgVT=&R7;{
z%crA%r!FN61%JZSpF9A_{C<kzJ>jwGQb8Vv;3tv_En<-wpvY0}?o^Lrn{(6{x``UH
zNsh|pLAJ>JYSucb&J`7hCq(tgJIdmw4b<(|1}x$uY(t48?YUWJF4L4$%e8keyyDOg
zBBS$H(fgF{rIXlurzMxwdL5re96oqEwik6=Y?6V_D^&Ie{_BAeJVD2tR*=o*QI)5N
zKiNM^r@0~3XVC~JNHbU!dD<h^Gi0TmbnTWZd{1=tZ(p^k)rg`e=@~VPbJp28!>`E^
z6?4QZpJAk)3S7vs4c%M0#q(+_3ut3)N^2dGFmF_)^wFbB>aMuta&mr~bkr&2T2U~j
z>8Xa--g}I=hlukBodsGKmwD`-^<Phm`>B6gio2fq{6gm~SO4h^KD522EArAr?1j|!
zRn8374bXK@`7p;Fg0l-HPlQ?I>_XM1Nk<8sSvt=;?`qs-Ry()P9|eC$;sTe1<h9uK
zZxtk%yoUPuoO8)3r7(q%#YrgBP|7;^ot9jRs&q~VapGKQ9F(5w?@sg*LWo^l_4Nl_
z4}Ku}=v_f^e<D%sa2rm*`aLCl?pvofY-v~Zgg!HZe3>U;YRPC=%HBam(b+j}arxM?
z02+H&BS#@}1PCowl0}~f(C(}Qb7(N#p=mqIirX)WbC?LCr4>!+9?fDL^dl<3)}Eus
zWiC0VoYFLo<21#TbMO+9FKOE4tRkJbYtd;KOA)u<3pSq8>valy*Y$nBTCc9J_I^Nc
zqJ@wh8gn7Dz7W19F4P37m+37%uzAPO((;a%9HFDXUzlKhvi@r!^!pF+M7-$i)9SJ4
zgdQNey~WU`FOOOY)xAs**(WGfb*Z9fcG2>fhZePX@t9q_Uah_A<L03_ah1T30|7Y3
z5jDJyxj?1%nMva~b^VBzdBKNq*p5ZLx=vhi`lvdpo5au25>+S1#NEpJYPDLg*VotA
zH@7!8*VnhVx7XL#*Vosi^keKG?dTqO&OJS<ZVyVBkIC#Q37|v20LR1tc4aRS>!%M!
zy-7LudAaDYEv~<(=8wJLzWq^g2d}87uP>IiIfD8YD%mX_Mhyo}oph|!LXC$imA?*a
z?{pkiFJC1e#QUy`Db>#UM<00qm!)6`YaQv6QcB}Aj>E9q4P7^cu=C=?$uv#dY056;
zT#BeU&vkL)QCSCAU#(VGS64STH@CO9FJ8R3y}f<$^2N=~4aEKS=Jx&f-+%YrcW-ZR
z(HPA7skX^A8ws&en`5<YgF;#^wvix}Id@RlA4$i?5-gpzt;!dTc3BT@OX%6r#rFDI
zG%v8zab0tm_HHCnT5&qR$WC``&ty0Cux_7id)X($j-EYSibIQUXV|kT+Ty(qp#ti}
zZ3}F)-NW#BUL1Wm*i0sb;8fFG8edA;YfZd=3pZcgjC&W=Tnd2=E4A5P#@en@@@}`=
zY&LJ++`akg%W)isuJ5{Tw~H9GAm007C*HeB)Frliuu)rCFe=pRRjJ?Hym;~A)vH%8
zU%q_#^5u&cFK%vc-uupbAAR)Et5>gBzP=dZ-n#KCtLWJV&CXb~Qs`Q#JpYxAQ0g{&
zaXOE`15?MIJ9blT`>R8lXpIjX+f&&)a{BI~Ll7DvEgrbFUk!`H$@b)ETR!{!?0VN4
zwD0|X=0L?x?{LmxmhVF6p}Ph@Z+nlib8UUdlyq*MIv;0BgMBH>=9{iKg3bjT^@!%$
z?7vRw<cv4&6rxHc7gw_L9xtz|QLv>h7fr2kgVgpLrSVTbrG&O*B^UA1+CmMlUw`r0
z&pzGlhV5><+io8>o85N1-EN0rHx9!zO{q;)u;LyUsvV8gFqY%$>gx9P_SLIbuU@@+
z=bd+6zI^%4JMaA3ul?G4@4bhKVi>l;#z1U(K`G~(yLpyXW?u?U-I$v`H<{)JuxAyA
zdGHMQ;DZln;AopcFUEDa)}Ce2aEqPhZTln&D_dl7N>ppm<yr&yi~&Ye_LSoog4UK&
z3DY>(J<FLH4b}{@PYLycF}8wTpF2_*Jz_7#m^WG|qqBubol)b=bXyLiY`e2J#Q@zi
z*`d;Q4zu?8D7}$W(%PIyN>STKM4ZccKr_j}j#N&aNS<dI%zD$lR33A)LQ;x)A3pr>
z!<R2#eEP{JUw!qJ8N^Up49XS7?8m)`Til}^Ti?LG?*U_8ym$dAf9IWdKKS5+-}<fJ
za?WkHTgdt}O^=U{=w!{oOdK=9#wM|i8nhi((5!SZLX0yd(fov6f9>2GHcaFWq3t!N
zU|Y1n*7EE{W4psS=Qxa#gNwN!Xo=&@uu={nAw-EWVptqITOqMzo+Y&3V@M6=VdpdM
z^fZlqzv3t_XGZXKU8h>6DLK^;=hpQWK~YuLe=Q~Dj0UcpnPY82N(m#d?PMVwlAP<*
zd723C{WOl8v4;LiEo`N^6hImd+rZ4qoC|emT}D+OJUGSLQcA=*;gEPcOTD1?TR>Z@
z(AwR25F~xS`ra>nzw3LnovF)Pw3L!d4%ONN(i|`|?4)B+JH-9v%a<Q~@WF5Y_HSd3
z<Yu#ZczC$GyL)(eKqr58K)1kv;U}H}XGS6t1hav)?a^)lG$;PG+2_7uT#&FSzj^Zp
zpMUY<g>w!zhZ9{89~j5ciECqRRK&wv@Mt;H2W#%~EG~w~Hfe3}{5(!M#(BP}9g4)h
zPN3n~tk1c5PGwyp&R{7ecHJ<HTrQf0#l()X-SXV7Rx8&gHlmSYn>b+mI%UMqO+O>Q
z=mAEvv*5=uLE5$MOSb)8gW}SfB=2w+HY&sC$8iAiM^|L;Lmo1wZ;N=RBOz4IS?2j|
zvGs2&!!Q&r9G2E~0G4%D>HF`0*E#puXP+Tstja{G!<yQo&Y7EY>qgLy*VorCU%q_x
z>eY9?^PTtJd+)vX-uv!%zx%tt`@0xH^XAQ)`}_O5yE}}`&N**38@6#IoWO;LVdU=@
zElXXSH}6cBem0@SHnWixMSrzgL4u*S*<P4GP*m&ndYYzT7`m=gr_(sqPSaj;%It}S
zE8x6wv%#xk(heJ&F@XYt)ht8jTpi<Rrhm;~iT{lu06Z=x@UTf(DFyy69fn1JU04sQ
z3@#~0c$38NZSrGv09xxg=NtyfFLXQ_*%6pwAS>rF@ukp#zKb!o>E}7;7(#B|h;P9f
z<6bK!qKb2=qA$(4h@c%>opB`M`mTpRR+9x($7vFDc#RP&gDzsuxl*)?vG4oXobpo2
z>({Tp_~MI)hx_~chsVeJ$H&dE+hH_@8G77iGg;Jrb#(=af2aL?{PD-@_4@7Gx36En
zzPr1F#A9F+$H2CM5OWj8X_|W|1G;$cr)iwh6xxI>ZYm^|9mj36^GfPVMevrvu)GJ_
zUb8JV1{AYRAzb9`Zp#@2n5=}0P~&o><2=K)A&@CeX-XXG<eVGFiS8!(1nU0Kz*{NB
zhky_Xb{GC@m6#Z7N8$!%t%J+4^cb_jgT#e3?I~fkXZEJ{e^lW00{GFyv2fxNOp|Th
zN+e@O!%pZPUQ{uV8GJ_Vp@{=zyaEh!_1@?9!04q61RWZf@!p3RLx|XjxuHHrta*=n
znTDP<M=MP!<upx+&S8vkyWNzc=x>hB<(Y5NewN}67(>YZ>eZ_cKm71}-}@f)`e&bg
z_WJefhldBua&c|?JLC*q^w~p@9$pjliO?mrpPVxkHiXa4p>=IWQk_%Kv@-G+*hARn
zX#&1;>RkmDUb@)fU)fC?UO|jCKxRii(JMIo?}GHCPr{)I9B0BoitKJqtjGqAmKBAq
z&n3&Ayb9arb5#ku@rS<engs6+k^tF^(M}~7O42k<06ZK$i*?3^A`{A{WN}CV4^o>Z
znWr{>zG@lhsd=<Arqk84kW0a>F>@y8B;uv*n)mRCJd9Yvh=33fyjLwEnC(%-IZ!lh
z`eex%v{p*r_q*M;lnh2crObg=>fCC*zJ2*BkoF?~NXQOO=Hrh)7LnJlUw`t+ClK_p
z4a8v^e~#iHba8HP9xppk)4OLZ78b{~VMFX_z8L5yA{bXjme95S?h61~v}`vO-TL5L
z4&K~4*jgm#_9i|9_tW3CBQz`#KDWBYB;me?Nd-rQ${I?!Ydz!1s8Bb<lGs-0GzidG
zP=v$=81hJzg0UHPq!A4WxDlpyh8S^3ppq~Hi!Fblu%OrNUbHUZwNr30!w#e=+hH7Y
zN_8S^j9?F92+|tIwhrTLbzM?QF$4^J%{hDV@D!#g0q>ErgD~`cAH46tq1C}BQA?5P
zHEAhYN*0lE9MA(E6Awx$;{E#i`qew{uGZ@iVzB8kq}#zizI^%ez4zYx=%bJB?(V+)
z^2@hx-#$J*0>fQhU2&`&$HP0$RyQaXnj3nWlT$ejfmX$t?$20CPDrJW>{opkJBSD-
z)zlGRCC3=yhN`L;j|n!anp5Wau|khoF_waa#<k~%J_D&6`^h??pzEZ$&a_K_A24_D
zWgE4JzWwv84$fL~`vbD^T>wI83hohcyoh9D-0Wx_G6&Uy$Dl<y6aa{#jYD(T9qYn5
zH%~TQ^*u8NINN8@j$EB9c^*FjSqJ0X#&5KPS6AETn!{Uy4<Oo8&KOOjs%e@qgcVwe
z8k&PdXGal^Qz^ORX`K4L-|dEJ8gtIE?{8kdyt=-Lv4h{L05hD>fRq7C$b9d6-&?QO
z@J+!vV&kEcVvKY>Nu_gO7-NbYFa$4?<9Vs%fSzsQ4bZa<!4P3%vQppoJV;)=9g^ef
z5DSmtZ?J}&&Bn%(LI{Z6fG%i7F?;RO7md6bCB;$a$8h!-&?S$?QBxv<k-0e+A3Tvs
z&bfhnZ<Qa3ky5mgAsK(>Y|08jO+#wBr2K*Fg4n0{O5;}1DJ7R&a*6FAS;ownrruLs
z<T?Tplf&kO5#WLl1S2?0NtjEgN-EY&Y!yz!j4{-?4~@a~b=sZ_KB$B7uS4%_P=Ybi
zR$2sz=(XrHO(8@TSzTRS-Q4!8l{z;Lg8)FLG)>d2-L0ScqWygDd*5@;y?*`rtFOL-
zgTn2!;~kM5Ny-I205$Ytk`e3!%q4sE6W!uKlQ_4!-wv4|%2(B*MT>TQ$Key;RX8R9
z)be7ggpIv(e2pEkgxDcJ$uV67-*A69%1=#H!A=|*$AK?GF$!%|C21B$2HI$O<&-iT
zrLME68Xb-euhHMeFme_H<Pnr#2mzcR&BfGa9q$)p?iFd0y=mhin$MlHX9%gO%bFW&
zq}+QkL*=ujI6(o;+@*5>Bp_r(Du;%(g*!AKNS((ohY;YL0KUhrQzTBRs&l!NX&kqY
zkK4z`VYl1k-R<~U>ifsX$H&LV-EId-<?+A`7@dK}0u9VC(CzIlr!R4?IY%z;b~~UB
z?j>LV6*+d7Ben35WUOP1{i+{_Q3~RDxk&x})zuXoBTyWN<1y@5bZjK69vR6hMM3e}
ziBXKvg3E8S1bY$D79t?}Z|+2o`B|KX3DabQO=MB&H{-DpTi{_JSlaG(6<34+udq^B
zZH|&it)xb>p%%@h^nKl(aU4VN&^UzS7V<GW%}$sqrL>`J;$uWYft>?Iq-xhNXP7?m
z9@B^FoOfP7=d9}Bs9-7+v}8N>dx=0fXBN<@iEKhtw%cvr_h}l_Fy`HEc-(yT&;LB^
zcH8X+bV59obz7n5y?=aseEaq-2NQEw76W%MV$QXhd(KQlCOKs$NM#Xu`SK-a^K#}M
zzQUoFxF(O8ok)Q)7HV-b)pZ<(VZB~s>@ver_%1F5O_THNq}B_jgki4OhV6F8X}y^A
zx#0L?jH(z7oBO`US!Fb*@p?Ig$WdfKJ|dFR1du~Hwt-&j>DpjiwUgdwX2|$jU5|mA
z9N1KcMT&RoidQ$YgrL1$8&H~J?9AA`nq1(Vj^kvD@$vkW>{6=BSf8P;s&I56!AzC!
z%1?+FUs_obEC4dVoZB1ujY*UX&)$wsiCveb$%&MbV+<+Db~~tZDd)${<955v<Cw;A
zx7*T<i3c^}d!j!K!^6V^6zVVxZ2;$J$7j>6A#V-sg)zL~=CBixInd`&0CobF_x|SQ
zhG>{p49mylM=T--l|ceH0|!7^#Mv}2GUd}WVF*2w8XA*b$=TJaCkF@P2I$P;=%iW9
zq`TO4zVGYd$0Ar~ojxwZlWjm%*J#SA>w1pXp&eVTR$!v=<alMSd@v*2crSbfD-V#+
z!`D<@tyW;c+u}3Dqa##9PC+w$VYTu$DTwSai!Bft^YauSW$>FK)b|~xxjV$yLnY$@
z7oBq{Wfo~-6f4a|o6}w0TqcH4ihyjq_uyevHS}xs8|Pz^Ficb5_m7VcEO(8kWZgn@
z=vSK(gMY<vU3!_#l?nJ*N(r(>7(>v)QKNQL7->i6+{>3QaloJp8{WuvNKiHR`>8Ut
z6(P*ADlkZtRKhJzQ%Whv7;;YIIB6-)cnHYWV2}v;iLgbMv&XVP9WWJwge>#|WD(E9
z0oJP8_bZG(wc&kS6yH-8000*7Nkl<Z2Oa|5_nJZE+^&HcFVX^$Y6n$gg=384IOUwZ
zh-=rurnFou&maJ<Y^UDRY}!0O%hp2$fMG>v3b#Mt$<9d@5v_BT3o?01QA}DTA25z1
zau7i2fYDmargYAgoSFZLv|bwg)%DG482!Wc_V$HeuOA*a&U+XZVPQ30Rh##Fb93|l
z`|sc1-@ktS8tYCt0Zj(t5~l(|9Hvi^2f$x}Sr?<IiA0$eMtlYdrU6qHtm(_^%)DtE
zqrpv?9%GpepfAWDN9^LVv<di(GlO&MSm(wF!Q#UGuW(!(Mq{s5EAA6b1P`I{lD!qF
z7z3th<E>k?E5_J?>9YxvTxa}-*;>d-C^gEGodZd#hok?<`C>XO(0052T#nNOx}=o+
zevr<N<Fx#vN$y?zmHAgSCa}*3c<+7Rb<)y(ix$Q=Lh!LoNUn3sbUt+ziQ)QKjmjyz
zX-dOx*bSqq-dwFZFV6eyia6PBhiUt$PDAWs*PpCab!Ir=_4V~;vw?F7ywP<X({7<P
znL&l*VJK9lhz!F(QqoQXW-t<|UCsQ}7zci{Ua!gCKu4OPzHztPK_4K41=~YrkS+(V
ziGjmpPM`uA%!mBpWO8b$8bupI@ZvQnEE@!cT0pf9Mz$u4fC}oLY$;g<cnj^VRm6?s
z<l3-UB4Crk*|0(7)MIm^=BhD>DU-3m)4|Pvz#znq#02h0j2*^a+sFXuT06IyS?|7O
zpOYlvVQ4rTC#N*!UB)rjtg?2fyqgD=GWWBz_&>em$}4-1)CdttIe8xdBJF&_;KPsy
zU()8EKmYRe7iqg8`WnYEUeH@h$hF_x+z^zn*K0@{ku)9}2nX5-8_WUOTrx!UY^o<h
zSIjSfj)y+mZnv~QCe&9$gH-w3ZME;~_1a`F)@T5-qsWR7<ls8%^%{>|)2t}3ZE^9;
z(=-*GQwuq*9Mo?Jbtrb5NnI2Az;8l)aV7~K=zuccxReatfg!Kfw`m+`m7djVRZ5<w
zWO+=B@fs@dsE95(2k)m!_zEC9voPoxGi2egh8}vlw1Se-k}hlFnWbFqI8F*NxzwCf
zE6tHcSO;>-e0X?<4B*Uzvs(tWRm9Z#*w$efa!MtoJWau=G-?jpG@KQ8$|%E%%(WPX
zVY}TTWdc{^G;QXJkzcnNRLJ`wE@U*$)`-|#15QyTN(SUWz74VF)zunjbF<mlh*cZK
z+7ml57ZRFm9LI4SyboO$W9-IZ7>6NL)@5(%s&lSuBt|Z|_dWMD=Tb^FDJ<Y%5*v(O
zN@*BIThe76)3!>%*1O<+k=jX3OqYP6J)AoclJ}WhO3ul-ntn|F&iP<HrK<H+r6$!>
zcoSP{B*;YYCq&6`7_49Jy_f1@p=*mcQ)A_Q@LpPcS5z?!zJim`^dmOKhR+&y7{T6z
z;7cjflu7zwH6WJLG$BE~<kI*3{r&wg49I*brNk#nm4Oy=&PaN;dESsS<N(t^+X>o0
zAV?sySq6|%z<t1>>+5R>_ineNDA3R2e=W*q5Z8VH@pkY1dc7`<f?%wQjD8b>+%koN
zNIy*p5qkg$JZ;X|srn|b4Bt@wC>mvC*OJl{p~uCLWLyx|3@44yXu;+4#T)qXaFJm%
zD<Km+E2j|;Zh4)Ntc9t;t0ku*9q4WEz4JnWMpmu&zT^}_$SL_2;()cmThPUC0g75@
zhX=6T5q)ziREwwt?-3%E>hn}?DoA9k#^W5c!%vt;x88L1ip|TCQW99m`R=MYr8jTh
zyuG`7dv|yD_U*9SLCgqs;<8uMegPFW55D|%yJZF(bi>Wf4YnUr2zkA_y6U=)1qamq
zG!(cjsoCweCgsBUgDBJCgUIr*A-F@yD5a#F#MP{SXm_d;(wCs#R;wO+LmcJ3Pbo9t
z%6PuE%&jWZT2M?tiw`)5pdcZZh$jJOD^zSYTVpnakIFYC=SW`}K^&$l*Y};K*mZaq
zPMXKzY>8z-M36to{0^+PsRS3ewy0OC0q>l(ruPJvUr}W;ztuTyKAYHC@ASt=&x>lw
z$v2<P7A~rDUSz#qm+EcLPL7G^%uYXY8*9#hh&t(OT3$v@7`zcJB?ixY-;{G|C25sK
zJ=(>_Rx#6F1LFcAftuLucGwswRJf;`&4!FhP2p-079bEfp!5{++)!XPUyX7}o`exr
z@uEdhtI;*@iu@+m4zVGK8nd)v({X*MOva4zK2>k(lB&0gCn4Lo-R^*}nQg{`6<dU1
zNfJ}VT%kXia?+-@W{;6hTv?pc7+d2wCGKY;KqS-WT#SK};H?E_P5RuOL1an^l4S}$
zJZNv$YLPh=@gg-<4OplhzXWsSy^mgk_eEW4^*S|f(0UONoic=iLL?APjg`u+|0LCa
z;~#4}etjs9E~{EF2)-DVT`O1*!%Pc_FQovkKn+8f@EliHS4?%XE*E`PX6u(yh%Rt(
z@HVBChlhujHW+P`iYzCQw}rp(0myPPz|=&!lJv|wu(|0V<3z;!j1UaGNf;2&Xs`)5
zd$H+zn>T`g!edtsVNN^COt-Rh9`PlcY_g_tnkEqkcG>3n@Zgb?M(!bxP;A=Q_7q`Y
zB&CG6M_>T~AWH3FMs(kIO-0m<;^xQ-lQ>8cP)9Tu%QbKqNF}GDDQk?KcnMB`G`u)u
zWfv_@yfZEtS3adQO=C+d2#q{VwK)f5-v>xBPzG{%g198&O9?uc2qX3^s^~xz7chp>
zhFydRq6(Wi=y00=${Ds`j1wQQm)&l+$c^ENLbQ_FZnN3cwB|;Kvk;rLd@QBI?<rV>
zm8BF^qhjGKaJND)(zOifD$}U1X?{|krUZM1r$rJdEdE?i)GBnvxxVkGanw?#Qb^0#
zGK*$*>#8z`n~^LVnxPnxbSDcj3<GTDoEW&@PRAy6h=>y$32m|{-dD)P0Ii8s)Lev8
zik(k^1{=(nu<ijaLZ1ZDI@Wr#riXcKJ_J`16qxhDf?EJ^ZQ%v36^>9!38+1#RGbUm
zr!?U_bX}LyR7##wno<g0yp~RLaI?SbWN!Tw4}m*#vrwFhSG~Hr!b+I}$C_daS0Lwn
zb8}-z+*nM=y46ZfL`PIDbOzwp(y67~Aee=fWy=?)5L46VoMH5Ui&*YuE1oRf#=pWq
z!lwbmvT0AZx3{d++&i~ytCCXJ_ryi4Mz_xBVd?iGoYbs=f?a8XtG0}Ny<RcT1e%Z4
z1uaYy_JEMOA}U^jBad0Eh%*|npW#ed_s%H}zb(6gd)kz}GCQNCdCqhpBSbZW2!v<|
zs;;DLd+ET@Or?a@4#fOZX&#h<wnXAI&B!#RoC}h6x)}3rgTR1OGpMH7`Cv1{kjIOx
zcl3fp@fGAAL^!oCZ81a^)HODsYrYiXJEqo9Y-E&#D;5-o$1bjI$pW26#KM$;TypC#
zFf*rE8u<0LkQvWrLog5>a-AgpZS6g-hMJnv)Bz=!&I##JMr|qCWO6O}uxN3E2xU71
ztaT0yim~|wqsYkWp}v=z|0vcfR$9ra7mw4z=(WucwOz|AHdYlU?4`lS=H793%L2KT
z#xkK=H#hRP<m_W8s=25S9<6LFkJO%OE-qo)roGmxco<=?Nlhv3cH3Oay6?wfcemX=
z?1o_+;bdm=ofLQ5b%Q;X*6VfO_tP|?3L76l&<DH4I0AiHI-r})hOVdexS8rr%gAy}
z+7=!^7VT5)3|?%>jD9aoyupABv`U1^LV;R^)P9&B-(Ek`GaH5--_$mX1A~OOw#O1C
zYqjb@(U47|w+-Wl^R_5~wFygXP;Gk>c|+A%o*SzNf)X;ymtxUl&KWfUUF@tbx0Ly!
z<6i~q2-Vh4LSHHu)`kn<8PD}i$h6E^TE$PUB_?whJ7A}#$oB>&(86GOTU&}sXH{KR
zO3t-_W*kS9D}~@6HoJ%IZnxVF!!V6w%84H4T$)XBXA3d>Twh<aLJxEVb&}mnLQiJW
zLfe&6?(gqyFGyk%D1KTM)S)u{)yAGI#bH4Lsab2sAr@U1nb>3#Y`CxXi+JTyGE!by
z*jANUN;#!s`U_CCbX^x(PBv>_QbG}0tyF}qz>`4z+u|=9F5QcShp0}qsO^ts@y{Z0
zAEnu@jfJxon_Rw^>V4W2?t^hSy67fpD&-MJ#X&7O@73&v5Nx*(-n~UMk;4arhS@eU
zwWM4UP^S73Xi+In?O^BJ%Ds`q3IGf$JcKZ%^tjz^Hk&H$(=?4!w#}`qUZKQO#{*zM
zyVDc0`ib!xTf<M~hZGkG1e%SJLV?jBp}`7n*21AT7_JGS;ZoXQmPvD;XhRUvZvz*?
zVQhhsBjzluR%^=!G9?Bs9vM|zOI1oq&8%Ue0H0I~oI>^{VAzi;gJ}p>?wOay>;@)y
z+PrfvqqPt82Xy8K)pp2X@(HV`(9`d*Noc!*GC05#NN{>gFSBjokcbQHPA9}F!BWsQ
zK#}^==8w$mibzd_wEb}jb(rT@r*@=_D8zQR!$sEX^>(}2HgO-vafDY1Jc+6+tKFHJ
zV#jsd-rhbu+z-P@X`_e4J`)yZ5dK1IQV*?GD=aj!&0>r<H#bOrq*excp@)D<3syfd
zvw{Uswh0Lx28LZpM=~sjE>=Y9Hl}7fDUdlqmjtSxna61Q4W|^hW2R7yaU2I~JGuva
zi$&WuR~t%ZTUA37$0w{snd)plmMwKPfTt{4z%r-ONVDtv6)7V?2tsgTOQzxxtr7b3
zzToNEXtI_3is`5dip<w+$2|APs~K$7(_o^Q%_^1s{>j=hLCZJ}yJ3Lr_kEwHX}jBQ
zHjkUl7TSHBrrcCOJ$UmVU}J_L@dUW9UcLJ1Pk&0e<XW%}!}eRV3ABiqVz>>n8c^Uz
zbPKs>U)5rs@A~?hzyq1amPx>w0aXI3lz4!|HdB0Uj1aM21V{L&Y$AdvJ<)CJ2sg}q
z!c+y_xm4%!`xs*kTodH{5%Ke-n8s{7s#3~r`!uqs*5T$n1;zm^q98Rytip!1HVFzl
z!Vm{(R&<G)>m*x(Wec(zXVi4eY{#j!4XeK-EmL#a;D|#`&@}|CENThLewlA>wWr7m
zW)&|7c%+1uSP7nSQC+S2?Pj~(Y@phq-CMM^us~!_+^y}i#kPIlf9E^j`Q(#NAV9Ux
zQO-c;SV7`WN*Gi%#2V!YY-z-lFYGS5)>tv6-KPm=`XN0|*b3zH0Cz#(<AMlPv`(TE
zI!-kZoVTsbBBh+6u3;G2%7R67v?o;LrM)uY2kL_?+sHI5dxm+cNuKaGHpznBA$dV2
zGouA5;Xa!>(VE$kNbo##7#JaDc@3MR*s9rNfOX4XSW*SHt+7px&#lv{t;d7pq%;8z
zg&3f_Qc5Lf(8sn*y^=xHx%Up11qx*xM`SwXQm(JBHk<8cyV*WIqF?VYj_^4PXjwDv
zu>P4|Bl;2C-rk~-*t_q(`|i8%zVpsI<2e59-~KK1J<TF)H0hVv+V|L0_?m>&8dBS)
zq3laKkAiBMpX86Jvk$vdFi8`8$sUwM<krolO=sVM7U}fh`{-m-I??KlS7RyuVG^6^
z#Xi@q*_KQUPTO2M+t1#1OD1Z!5n}pIRkD%%y_g!%+yK%V`+Y=ezI)4-Q{?>E?h@o!
zO`vD)mu8E&7exyu#XwBxz*z!^p&#3MlkIoF`nSmmdzCTl3Yb#DK(ZRUP5Juzdbitc
zHjfYY_m7X8&E|2t-EOv3yU+cC7GWk9gW@1=w_9p=eE8+dm%sBnzw`Hh|M#DL_8F^w
zShz&sZi!fE5yDF2#cPY(?`;fit4Wg1%sN^O`JkO;){2OH_0?Cn9Xs+c<Giu&VB9=|
zWrRAc2yU53vN!{zi7W+SS?76jsbPdj8d~0iMP4*_^zUtpr9}fXo>=}>oQo|J&Zfbz
zcFaa$ZB;u16s%9M{s}Y28VBIm@qOPtb=GHHyz+uSwSDMq0JsU*JVDK-pz;<pCUnZL
zuCDrCw%ct5G288Kw*_Q}b)0$IcCBb9Sy_E!1_jS8^k2Mq@$ttWfB4}CKl#c3_2$hR
zMsv(mj6Ha52TRy&L0~wWvthY;+oEVu7-lm)4#&7IB2aADU`r<#f3Ptc+mX24)U^r;
zwY(lmOX*=-(O<Y!cGQQ-psot+Fur36bxU*EQvAh%*WJ$Enp0mE@d`Fa$3L;F4ABC6
z=-Ep#^>Uh0&Y68xnHsc6hO=+M^k&QXAlJa=bvpt!n*lLTFcjS%7mh5ItE=@irQL3~
z+ikbo?c?L)c6LywG$n1dmwcJvbRO?5`*X+`Y}%X6^kSaPbbo*U)mLAA`Q?{rFlPHL
zlC)-OC^v~3-5z~gqh-aBXUR7Ihf=g16|EVy;D?@~ZFWHWYbA_K8B76<`{Cd!+tk}?
zn%2@WK#**E^#zl?;OW@^h+yVW?kx*$v7EJRlqw4atva_G1fjQc9BgF}x5nynR>a#*
zY#3p%AWyIY6%lC>hi=-XmY*8hM;7wEUqGj&h%t$%@x^JZbo(&uhAQv7-B4Bg&`^6R
zDQ9AsE3fO|BF7aHiotNO8j`vheExR3<>b8&Km70)fAJTCNgKRmoa)yZcK7u@e=x-M
z*%vSBPEKZg?!(J+s;2JEQL$qj?eMx|Q~17}#gYAIJF_^LL#u_O>-G2u^5bLu_UF;c
z*@x#%rAlBeTpY&<oD6vC-EKDw<1p-yzyc>W*OrjAHLN&yCHj7JV+Aw1SG&$|=0e<Y
zqyUu6T-A8-T-B%Mm42;3+fQA`Zh?I5xvE8vZ~WQ0sz;v99UZKBbolHkaX&i^+|KxI
z7m?>a>V-RUFyvkqLq(4q2Q>qcDfzk_(7E_xX~UM<T=<l78tV_^s#U4itdu#$-8rYu
zyW&O5!JtX2<1G;m?T-4?{i)gETbi2v^}lt=bq<EkUv^sb=!Kqt>W)neKK>&5lqIn<
zzMq&0Ze~-uvomE+&nP||w5=CT-#)uXBFho5%h9q&re<fhr%X8`Hvu9J_L(fZm30y4
zoy+Qm;?};3`;o<>14``<+IzR#A#B7Dy=^*oba>~5EAreGeBSTn%oZKL&Qn`*`76jV
zgMRw%`h>W<a|h{gbnqdjdt&PE6W7-#0M(wRUmgdm_2r4vb$Bnv`%p1xik^^>P$y!{
zf^O1doXKO3&P~M?JuL$^E^TUKYcUT0Y%{hGsgmc)^fx&hXFl`vo?mVf<ZC>SoVxDG
z>zr4hCw+tC_;?<%efl#e<^H%yxdb=v;TX5n+<b<^(e?YQr_4#_Kn@6f&bgr7tCmHm
z&fYsZ73XgJlZ(2@9$R!vJtgah<lxuV=-=icX<H_8Y_Ii%+CGa)_ILCG7;^$4JzwIV
zyw|ZWSNryau08U|veeLWMyIro$jLiADRNF{|74k}30^BRCe2)fI!;wBHMIQkPFGJr
z?V}yVEJbAn{S<0H<j8-H0(}Xf(25$*LW5^Yytqq0eX*>cmkoF9$S$5=jw;{_ufG4$
zD$56WOis$ZmAv_J<muODUeEymt=VsEmqDz3^kt9U>LQc5D7sm;ynj-o|M&Vij=9NI
z=w%{wC`j`3cfJjA*UR@u4t;2O`eo#Dcjkz9F2~<go_;{lBcAAqP1A)vlH+92oK$n>
zG4@~bc>=l!9UuCp|KtCh5qEh~1m&zDzvQBNskqBUZ*$ZmkqdA(zA4XJyV*5;0-B#%
z7&#*G`jpRe@EV7d&$-RhIXmpR;(jRH|NoNpJ#6<3T>dQ_vFC}qJmVkrsawj$;(M{d
slymCz++v8kjGj3#RF`3|r`)pt7bWcDW@p12kN^Mx07*qoM6N<$f(c_Ef&c&j

diff --git a/legacyworlds-web-main/Content/Raw/img/button-5.png b/legacyworlds-web-main/Content/Raw/img/button-5.png
deleted file mode 100644
index 92560eaed3248f6d2eae0ed2cd95119e341f5566..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 22034
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
BjEevO

diff --git a/legacyworlds-web-main/Content/Raw/img/button-6.png b/legacyworlds-web-main/Content/Raw/img/button-6.png
deleted file mode 100644
index 81a8a9271cbb10bc296f27a47e55b4a9d6607893..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 19169
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
KMNUMnLSTYrSIlt$

diff --git a/legacyworlds-web-main/Content/Raw/img/logo.png b/legacyworlds-web-main/Content/Raw/img/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d082bc4fbbec409030e24035276c1584d061e90
GIT binary patch
literal 19320
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

literal 0
HcmV?d00001

diff --git a/legacyworlds-web-main/Content/Raw/img/rust.png b/legacyworlds-web-main/Content/Raw/img/rust.png
new file mode 100644
index 0000000000000000000000000000000000000000..223ba4618a376dde8ad0dbd454bee87af12184d4
GIT binary patch
literal 80507
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

literal 0
HcmV?d00001

diff --git a/legacyworlds-web-main/Content/Raw/js/jquery-1.4.2.min.js b/legacyworlds-web-main/Content/Raw/js/jquery-1.4.2.min.js
deleted file mode 100644
index 7c24308..0000000
--- a/legacyworlds-web-main/Content/Raw/js/jquery-1.4.2.min.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/*!
- * jQuery JavaScript Library v1.4.2
- * http://jquery.com/
- *
- * Copyright 2010, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- * Copyright 2010, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- *
- * Date: Sat Feb 13 22:33:48 2010 -0500
- */
-(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
-e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
-j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
-"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
-true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
-Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
-(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
-a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
-"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
-function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
-c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
-L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
-"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
-a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
-d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
-a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
-!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
-true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
-var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
-parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
-false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
-s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
-applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
-else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
-a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
-w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
-cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
-i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
-" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
-this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
-e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
-c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
-a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
-function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
-k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
-C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
-null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
-e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
-f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
-if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
-fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
-d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
-"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
-a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
-isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
-{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
-if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
-e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
-"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
-d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
-!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
-toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
-u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
-function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
-if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
-e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
-t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
-g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
-for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
-1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
-CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
-relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
-l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
-h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
-CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
-g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
-text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
-setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
-h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
-m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
-"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
-h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
-!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
-h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
-q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
-if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
-(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
-function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
-gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
-c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
-{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
-"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
-d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
-a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
-1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
-a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
-c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
-wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
-prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
-this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
-return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
-""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
-this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
-u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
-1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
-return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
-""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
-c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
-c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
-function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
-Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
-"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
-a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
-a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
-"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
-serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
-function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
-global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
-e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
-"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
-false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
-false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
-c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
-d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
-g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
-1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
-"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
-if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
-this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
-"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
-animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
-j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
-this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
-"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
-c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
-this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
-this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
-e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
-c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
-function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
-this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
-k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
-f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
-a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
-c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
-d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
-f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
-"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
-e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
diff --git a/legacyworlds-web-main/Content/Raw/js/jquery-1.7.1.min.js b/legacyworlds-web-main/Content/Raw/js/jquery-1.7.1.min.js
new file mode 100644
index 0000000..198b3ff
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/js/jquery-1.7.1.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.1 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};
+f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function()
+{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/js/main.js b/legacyworlds-web-main/Content/Raw/js/main.js
index 545e799..85cc697 100644
--- a/legacyworlds-web-main/Content/Raw/js/main.js
+++ b/legacyworlds-web-main/Content/Raw/js/main.js
@@ -1,37 +1,4 @@
 $(function() {
-	/*
-	 * Main layer location
-	 */
-	var _viewportHeight = function() {
-		return self.innerHeight || jQuery.boxModel
-				&& document.documentElement.clientHeight
-				|| document.body.clientHeight;
-	};
-	var _viewportWidth = function() {
-		return self.innerWidth || jQuery.boxModel
-				&& document.documentElement.clientWidth
-				|| document.body.clientWidth;
-	};
-
-	var _handleSize = function() {
-		var _h = _viewportHeight();
-		if (_h < 560) {
-			$("#extframe").css('top', '280px');
-		} else {
-			$("#extframe").css('top', '50%');
-		}
-
-		var _w = _viewportWidth();
-		if (_w < 950) {
-			$(".internal").css('left', '475px');
-		} else {
-			$(".internal").css('left', '50%');
-		}
-	};
-
-	$(window).resize(_handleSize);
-	_handleSize();
-
 	/*
 	 * Tabs
 	 */
@@ -58,20 +25,20 @@ $(function() {
 	};
 
 	var _hideTab = function(_id) {
-		$('#tabb-' + _id).removeClass('selected-tab');
+		$('#tabl-' + _id).removeClass('selected');
 		$('#tabc-' + _id).css('display', 'none');
 	};
 
 	var _showTab = function(_id) {
-		$('#tabb-' + _id).addClass('selected-tab');
+		$('#tabl-' + _id).addClass('selected');
 		$('#tabc-' + _id).css('display', 'block');
 	};
 
 	var _prepareTabContainer = function(_root) {
 		_root.container.empty();
 
-		var _titles = $('<div/>').addClass('tab-buttons');
-		_titles.appendTo(_root.container);
+		var _titles = $('<ul/>').addClass('tab-buttons');
+		_titles.appendTo( $('#hd-page') );
 
 		for ( var j in _root.tabs) {
 			var _theTab = _root.tabs[j];
@@ -79,15 +46,17 @@ $(function() {
 				_root.selected = _theTab.id;
 			}
 
-			$('<a/>').addClass('tab-button').attr('id', 'tabb-' + _theTab.id)
-					.attr('href', '#' + _theTab.id).text(_theTab.title)
-					.appendTo(_titles).click(
-							function() {
-								_hideTab(_root.selected);
-								_root.selected = $(this).attr('id').replace(
-										/^tabb-/, '');
-								_showTab(_root.selected);
-							});
+			var _link = $('<a/>').attr('id', 'tabb-' + _theTab.id)
+					.attr('href', '#' + _theTab.id).text(_theTab.title);
+			$('<li/>').attr('id' , 'tabl-' + _theTab.id )
+				.appendTo(_titles).append(_link);
+			_link.click(
+					function() {
+						_hideTab(_root.selected);
+						_root.selected = $(this).attr('id').replace(
+								/^tabb-/, '');
+						_showTab(_root.selected);
+					});
 			_theTab.contents.css('display', 'none').attr('id',
 					'tabc-' + _theTab.id).appendTo(_root.container);
 		}
@@ -99,9 +68,37 @@ $(function() {
 			_prepareTabContainer(_tabs[i]);
 		}
 	};
+	
+	var _handleFakeTabs = function( ) {
+		var _buttons = [];
+		$('.tab-buttons a').each( function( _button ) {
+			$(this).remove( );
+			_buttons.push( $(this) );
+		});
+		if ( _buttons.length == 0 ) {
+			return false;
+		}
+
+		var _titles = $('<ul/>').addClass('tab-buttons');
+		_titles.appendTo( $('#hd-page') );
+		for ( var i in _buttons ) {
+			var _button = _buttons[ i ];
+			var _content = $('<li/>').appendTo(_titles).append(_button);
+			if ( _button.hasClass( 'selected-tab' ) ) {
+				_content.addClass( 'selected' );
+			}
+		}
+		
+		return true;
+	};
 
 	var _tabs = _findTabs();
-	_prepareTabs(_tabs);
+	if ( _handleFakeTabs() && _tabs.length != 0 ) {
+		// FIXME: we should display "sub-tabs" somehow
+		// For now we simply ignore the JS tabs
+	} else if ( _tabs.length != 0 ) {
+		_prepareTabs(_tabs);
+	}
 
 	/*
 	 * Hidden descriptions

From 66c72ef718440047835546b896748d722a53893e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 2 Jan 2012 15:10:04 +0100
Subject: [PATCH 09/94] Resource provider regeneration

Implemented resource regeneration computation in the
verse.compute_provider_regeneration() function.

Created the PLANET_RES_REGEN update type, and added the corresponding
implementation.

Added resource regeneration constants registration.
---
 .../lw/beans/sys/ConstantsRegistrarBean.java  |   8 +
 .../db-structure/parts/020-functions.sql      |   1 +
 .../db-structure/parts/030-updates.sql        |   1 +
 .../db-structure/parts/data/000-typedefs.sql  |  35 ++-
 .../145-resource-providers-functions.sql      |  61 ++++++
 .../105-planet-resource-regeneration.sql      |  47 ++++
 .../14500-compute-provider-regeneration.sql   | 204 ++++++++++++++++++
 ...10500-process-planet-res-regen-updates.sql |  87 ++++++++
 8 files changed, 443 insertions(+), 1 deletion(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/functions/145-resource-providers-functions.sql
 create mode 100644 legacyworlds-server-data/db-structure/parts/updates/105-planet-resource-regeneration.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/verse/14500-compute-provider-regeneration.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/updates/10500-process-planet-res-regen-updates.sql

diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 59743d9..38960a0 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -53,6 +53,14 @@ public class ConstantsRegistrarBean
 				true ) );
 		cDesc = "Initial universe size (offset relative to the centre).";
 		defs.add( new ConstantDefinition( "game.universe.initialSize" , "Universe" , cDesc , 1.0 , 1.0 , true ) );
+		
+		// Natural resources
+		cDesc = "Global recovery rate multiplier for natural resources. This should be kept close to 0.";
+		defs.add( new ConstantDefinition( "game.resources.recovery" , "Natural resources" , cDesc , 0.01 , 0.00001 ,
+				1.0 ) );
+		cDesc = "Resource recovery dampener. Lower means less dampening";
+		defs.add( new ConstantDefinition( "game.resources.recoveryDampening" , "Natural resources" , cDesc , 1.5 , 1.0 ,
+				true ) );
 
 		// Happiness
 		String[] hcNames = {
diff --git a/legacyworlds-server-data/db-structure/parts/020-functions.sql b/legacyworlds-server-data/db-structure/parts/020-functions.sql
index d97d3cd..18c48c0 100644
--- a/legacyworlds-server-data/db-structure/parts/020-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/020-functions.sql
@@ -24,6 +24,7 @@
 \i parts/functions/110-prefs-functions.sql
 \i parts/functions/120-map-functions.sql
 \i parts/functions/140-planets-functions.sql
+\i parts/functions/145-resource-providers-functions.sql
 \i parts/functions/150-battle-functions.sql
 \i parts/functions/160-battle-views.sql
 \i parts/functions/163-alliance-functions.sql
diff --git a/legacyworlds-server-data/db-structure/parts/030-updates.sql b/legacyworlds-server-data/db-structure/parts/030-updates.sql
index 47862f5..12efd35 100644
--- a/legacyworlds-server-data/db-structure/parts/030-updates.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-updates.sql
@@ -19,4 +19,5 @@
 \i parts/updates/080-planet-construction.sql
 \i parts/updates/090-planet-military.sql
 \i parts/updates/100-planet-population.sql
+\i parts/updates/105-planet-resource-regeneration.sql
 \i parts/updates/110-planet-money.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql b/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
index f9fe790..edcef12 100644
--- a/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
+++ b/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
@@ -28,20 +28,53 @@ CREATE TYPE log_type
 
 -- Update types
 CREATE TYPE update_type	AS ENUM (
+
+	/* Empires' money is being updated using the previous update's results */
 	'EMPIRE_MONEY' ,
+	
+	/* Empire research points are being attributed to technologies */
 	'EMPIRE_RESEARCH' ,
+	
+	/* The effects of empires' debts are being computed */ 
 	'EMPIRE_DEBT' ,
+	
+	/* Fleets which were 1 update away are arriving at their destination */
 	'PLANET_FLEET_ARRIVALS' ,
+	
+	/* Other fleets are moving */
 	'PLANET_FLEET_MOVEMENTS' ,
+	
+	/* Fleet states (e.g. "deploying", "unavailable", etc...) are being
+	 * updated.
+	 */
 	'PLANET_FLEET_STATUS' ,
+	
+	/* Start new battles where necessary */
 	'PLANET_BATTLE_START' ,
+	
+	/* Process all in-progress battles */
 	'PLANET_BATTLE_MAIN' ,
+	
+	/* Finalise battles that need to be ended */
 	'PLANET_BATTLE_END' ,
+	
+	/* Abandon planets */
 	'PLANET_ABANDON' ,
+	
+	/* Handle civilian build queues */
 	'PLANET_CONSTRUCTION' ,
+	
+	/* Handle military build queues */
 	'PLANET_MILITARY' ,
+	
+	/* Update planets' population */
 	'PLANET_POPULATION' ,
-	'PLANET_MONEY' 
+	
+	/* Regenerate resources in resource providers */
+	'PLANET_RES_REGEN' ,
+
+	/* Compute income and upkeep of planets */
+	'PLANET_MONEY'
 );
 
 -- Types of recapitulative e-mail messages
diff --git a/legacyworlds-server-data/db-structure/parts/functions/145-resource-providers-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/145-resource-providers-functions.sql
new file mode 100644
index 0000000..b79d4fd
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/functions/145-resource-providers-functions.sql
@@ -0,0 +1,61 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Resource providers functions
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Compute resource provider regeneration
+ * 
+ * This function computes the quantity in a resource provider after it has
+ * been regenerated.
+ * 
+ * Parameters:
+ *		_quantity		The current quantity of resources in the provider
+ *		_max			The maximal amount of resources supported by the
+ *							provider
+ *		_recovery_rate	The provider's recovery rate
+ *
+ * Returns:
+ *		?				The new quantity of resources.
+ */
+CREATE OR REPLACE FUNCTION verse.compute_provider_regeneration(
+				_quantity		DOUBLE PRECISION ,
+				_max			DOUBLE PRECISION ,
+				_recovery_rate	DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+		STRICT IMMUTABLE
+		SECURITY INVOKER
+	AS $compute_provider_regeneration$
+
+DECLARE
+	_uc_recovery	DOUBLE PRECISION;
+	_uc_dampening	DOUBLE PRECISION;
+	_uc_ticks		DOUBLE PRECISION;
+	_result			DOUBLE PRECISION;
+
+BEGIN
+	_uc_recovery := sys.get_constant( 'game.resources.recovery' );
+	_uc_dampening := sys.get_constant( 'game.resources.recoveryDampening' );
+	_uc_ticks := 1440; -- FIXME: this should be a constant
+	
+	_result := ( 1 - _quantity / _max ) ^ _uc_dampening;
+	_result := _quantity + _result * _recovery_rate * _uc_recovery / _uc_ticks;
+
+	IF _result > _max THEN
+		_result := _max;
+	END IF;
+
+	RETURN _result;
+END;
+$compute_provider_regeneration$ LANGUAGE PLPGSQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION verse.compute_provider_regeneration(
+		DOUBLE PRECISION , DOUBLE PRECISION ,
+		DOUBLE PRECISION )
+	FROM PUBLIC;
diff --git a/legacyworlds-server-data/db-structure/parts/updates/105-planet-resource-regeneration.sql b/legacyworlds-server-data/db-structure/parts/updates/105-planet-resource-regeneration.sql
new file mode 100644
index 0000000..9e59a4b
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/updates/105-planet-resource-regeneration.sql
@@ -0,0 +1,47 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates - resource regeneration
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Resource provider regeneration update
+ * 
+ * This function computes the regeneration of resource providers for the current batch
+ * of planets.
+ * 
+ * Parameters:
+ *		_tick		The identifier of the game update
+ */
+CREATE OR REPLACE FUNCTION sys.process_planet_res_regen_updates( _tick BIGINT )
+	RETURNS VOID
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $process_planet_res_regen_updates$
+
+	UPDATE verse.resource_providers _provider
+
+		SET resprov_quantity = verse.compute_provider_regeneration(
+				_provider.resprov_quantity ,
+				_provider.resprov_quantity_max ,
+				_provider.resprov_recovery
+			)
+
+		FROM sys.updates _upd_sys
+			INNER JOIN verse.updates _upd_verse
+				ON _upd_verse.update_id = _upd_sys.id
+
+		WHERE _upd_sys.last_tick = $1
+			AND _upd_sys.status = 'PROCESSING'
+			AND _upd_sys.gu_type = 'PLANET_RES_REGEN'
+			AND _provider.planet_id = _upd_verse.planet_id;
+
+$process_planet_res_regen_updates$ LANGUAGE SQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION sys.process_planet_res_regen_updates( BIGINT )
+	FROM PUBLIC;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/verse/14500-compute-provider-regeneration.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/verse/14500-compute-provider-regeneration.sql
new file mode 100644
index 0000000..f58a6c7
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/verse/14500-compute-provider-regeneration.sql
@@ -0,0 +1,204 @@
+/*
+ * Test the verse.compute_provider_regeneration() function
+ */
+BEGIN;
+
+	/* Define the necessary constants using default values */
+	SELECT sys.uoc_constant( 'game.resources.recovery' , '(test)' , 'Resources' , 0.01 );
+	SELECT sys.uoc_constant( 'game.resources.recoveryDampening' , '(test)' , 'Resources' , 1.5 );
+	
+	/* Make sure the functions are not immutable during the tests */
+	ALTER FUNCTION sys.get_constant( TEXT ) VOLATILE;
+	ALTER FUNCTION verse.compute_provider_regeneration( DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ) VOLATILE;
+	
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 6 );
+
+
+	SELECT diag_test_name( 'verse.compute_provider_regeneration() - no regeneration at maximal quantity' );
+	CREATE FUNCTION _run_tests( ) RETURNS BOOLEAN AS $$
+	DECLARE
+		rr	DOUBLE PRECISION;
+	BEGIN
+		rr := 0.05;
+		WHILE rr < 1
+		LOOP
+			IF verse.compute_provider_regeneration( 100.0 , 100.0 , rr ) > 100.0
+			THEN
+				RETURN FALSE;
+			END IF;
+			rr := rr + 0.05;
+		END LOOP;
+		RETURN TRUE;
+	END;
+	$$ LANGUAGE PLPGSQL;
+	SELECT ok( _run_tests() );
+	DROP FUNCTION _run_tests( );
+
+
+	SELECT diag_test_name( 'verse.compute_provider_regeneration() - regeneration >= 0' );
+	CREATE FUNCTION _run_tests( ) RETURNS BOOLEAN AS $$
+	DECLARE
+		qt	DOUBLE PRECISION;
+		rr	DOUBLE PRECISION;
+	BEGIN
+		qt := 0;
+		WHILE qt < 100.0
+		LOOP
+			rr := 0.05;
+			WHILE rr < 1
+			LOOP
+				IF verse.compute_provider_regeneration( qt , 100.0 , rr ) <= qt
+				THEN
+					RETURN FALSE;
+				END IF;
+				rr := rr + 0.05;
+			END LOOP;
+			qt := qt + 5;
+		END LOOP;
+		RETURN TRUE;
+	END;
+	$$ LANGUAGE PLPGSQL;
+	SELECT ok( _run_tests() );
+	DROP FUNCTION _run_tests( );
+
+
+	SELECT diag_test_name( 'verse.compute_provider_regeneration() - higher quantity => slower regeneration' );
+	CREATE FUNCTION _run_tests( ) RETURNS BOOLEAN AS $$
+	DECLARE
+		pdiff	DOUBLE PRECISION;
+		diff	DOUBLE PRECISION;
+		qt		DOUBLE PRECISION;
+		rr		DOUBLE PRECISION;
+	BEGIN
+		rr := 0.05;
+		WHILE rr < 1
+		LOOP
+			qt := 0;
+			WHILE qt < 100.0
+			LOOP
+				diff := verse.compute_provider_regeneration( qt , 100.0 , rr ) - qt;
+				IF qt <> 0 AND diff >= pdiff
+				THEN
+					RETURN FALSE;
+				END IF;
+				pdiff := diff;
+				qt := qt + 5;
+			END LOOP;
+			rr := rr + 0.05;
+		END LOOP;
+		RETURN TRUE;
+	END;
+	$$ LANGUAGE PLPGSQL;
+	SELECT ok( _run_tests() );
+	DROP FUNCTION _run_tests( );
+
+
+	SELECT diag_test_name( 'verse.compute_provider_regeneration() - higher recovery rate => faster regeneration' );
+	CREATE FUNCTION _run_tests( ) RETURNS BOOLEAN AS $$
+	DECLARE
+		pdiff	DOUBLE PRECISION;
+		diff	DOUBLE PRECISION;
+		qt		DOUBLE PRECISION;
+		rr		DOUBLE PRECISION;
+	BEGIN
+		qt := 0;
+		WHILE qt < 100.0
+		LOOP
+			rr := 0.05;
+			WHILE rr < 1
+			LOOP
+				diff := verse.compute_provider_regeneration( qt , 100.0 , rr ) - qt;
+				IF rr > 0.06 AND diff <= pdiff
+				THEN
+					RETURN FALSE;
+				END IF;
+				pdiff := diff;
+				rr := rr + 0.05;
+			END LOOP;
+			qt := qt + 5;
+		END LOOP;
+		RETURN TRUE;
+	END;
+	$$ LANGUAGE PLPGSQL;
+	SELECT ok( _run_tests() );
+	DROP FUNCTION _run_tests( );
+
+
+	SELECT diag_test_name( 'verse.compute_provider_regeneration() - effect of game.resources.recovery' );
+	CREATE FUNCTION _run_tests( ) RETURNS BOOLEAN AS $$
+	DECLARE
+		pdiff	DOUBLE PRECISION;
+		diff	DOUBLE PRECISION;
+		qt		DOUBLE PRECISION;
+		rr		DOUBLE PRECISION;
+	BEGIN
+		qt := 0;
+		WHILE qt < 100.0
+		LOOP
+			rr := 0.01;
+			WHILE rr < 1
+			LOOP
+				UPDATE sys.constant_definitions
+					SET c_value = rr
+					WHERE name = 'game.resources.recovery';
+				diff := verse.compute_provider_regeneration( qt , 100.0 , 0.5 ) - qt;
+				IF rr > 0.011 AND diff <= pdiff
+				THEN
+					RETURN FALSE;
+				END IF;
+				pdiff := diff;
+				rr := rr + 0.01;
+			END LOOP;
+			qt := qt + 5;
+		END LOOP;
+		RETURN TRUE;
+	END;
+	$$ LANGUAGE PLPGSQL;
+	SELECT ok( _run_tests() );
+	DROP FUNCTION _run_tests( );
+	UPDATE sys.constant_definitions
+		SET c_value = 0.01
+		WHERE name = 'game.resources.recovery';
+
+
+
+	SELECT diag_test_name( 'verse.compute_provider_regeneration() - effect of game.resources.recoveryDampening' );
+	CREATE FUNCTION _run_tests( ) RETURNS BOOLEAN AS $$
+	DECLARE
+		pdiff	DOUBLE PRECISION;
+		diff	DOUBLE PRECISION;
+		qt		DOUBLE PRECISION;
+		rrd		DOUBLE PRECISION;
+	BEGIN
+		qt := 5;
+		WHILE qt < 100.0
+		LOOP
+			rrd := 1;
+			WHILE rrd < 3
+			LOOP
+				UPDATE sys.constant_definitions
+					SET c_value = rrd
+					WHERE name = 'game.resources.recoveryDampening';
+				diff := verse.compute_provider_regeneration( qt , 100.0 , 0.5 ) - qt;
+				IF rrd > 1.01 AND diff >= pdiff
+				THEN
+					RETURN FALSE;
+				END IF;
+				pdiff := diff;
+				rrd := rrd + 0.25;
+			END LOOP;
+			qt := qt + 5;
+		END LOOP;
+		RETURN TRUE;
+	END;
+	$$ LANGUAGE PLPGSQL;
+	SELECT ok( _run_tests() );
+	DROP FUNCTION _run_tests( );
+	UPDATE sys.constant_definitions
+		SET c_value = 1.5
+		WHERE name = 'game.resources.recoveryDampening';
+
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/updates/10500-process-planet-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/updates/10500-process-planet-res-regen-updates.sql
new file mode 100644
index 0000000..54385b4
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/updates/10500-process-planet-res-regen-updates.sql
@@ -0,0 +1,87 @@
+/*
+ * Test for the sys.process_planet_res_regen_updates() function
+ */
+BEGIN;
+
+	/* We need to create a natural resource and a pair of planets */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 1 , 'testResource' );
+	SELECT _create_raw_planets( 3 , 'testPlanet' );
+
+	/* Define the necessary constants using default values */
+	SELECT sys.uoc_constant( 'game.resources.recovery' , '(test)' , 'Resources' , 0.01 );
+	SELECT sys.uoc_constant( 'game.resources.recoveryDampening' , '(test)' , 'Resources' , 1.5 );
+	
+	/* Create resource providers */
+	INSERT INTO verse.resource_providers ( planet_id , resource_name_id ,
+			resprov_quantity_max , resprov_quantity ,
+			resprov_difficulty , resprov_recovery )
+		VALUES (
+			 _get_map_name( 'testPlanet1' ) ,  _get_string( 'testResource1' ) ,
+			 100 , 50 ,
+			 0.5 , 0.5
+		) , (
+			 _get_map_name( 'testPlanet2' ) ,  _get_string( 'testResource1' ) ,
+			 100 , 100 ,
+			 0.5 , 0.5
+		) , (
+			 _get_map_name( 'testPlanet3' ) ,  _get_string( 'testResource1' ) ,
+			 100 , 50 ,
+			 0.5 , 0.5
+		);
+	
+	/* Insert update records for the planets. Planets 1 and 2 will be processed,
+	 * planet testPlanet3 will not.
+	 */
+	INSERT INTO sys.updates ( id , gu_type , status , last_tick )
+		VALUES ( 1 , 'PLANET_RES_REGEN' , 'PROCESSING' , 0 );
+	INSERT INTO verse.updates ( update_id , planet_id )
+		VALUES ( 1 , _get_map_name( 'testPlanet1' ) );
+
+	INSERT INTO sys.updates ( id , gu_type , status , last_tick )
+		VALUES ( 2 , 'PLANET_RES_REGEN' , 'PROCESSING' , 0 );
+	INSERT INTO verse.updates ( update_id , planet_id )
+		VALUES ( 2 , _get_map_name( 'testPlanet2' ) );
+
+	INSERT INTO sys.updates ( id , gu_type , status , last_tick )
+		VALUES ( 3 , 'PLANET_RES_REGEN' , 'PROCESSED' , 0 );
+	INSERT INTO verse.updates ( update_id , planet_id )
+		VALUES ( 3 , _get_map_name( 'testPlanet3' ) );
+
+	/* Helper function that gets a provider's current quantity */
+	CREATE FUNCTION _get_quantity( _planet TEXT )
+		RETURNS DOUBLE PRECISION
+	AS $$
+		SELECT resprov_quantity FROM verse.resource_providers
+			WHERE planet_id = _get_map_name( $1 )
+				AND resource_name_id = _get_string( 'testResource1' );
+	$$ LANGUAGE SQL;
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 6 );
+
+
+	SELECT sys.process_planet_res_regen_updates( 10 );
+	SELECT diag_test_name( 'PLANET_RES_REGEN update - Wrong update identifier (1/3)' );
+	SELECT ok( _get_quantity( 'testPlanet1' ) = 50 );
+	SELECT diag_test_name( 'PLANET_RES_REGEN update - Wrong update identifier (2/3)' );
+	SELECT ok( _get_quantity( 'testPlanet2' ) = 100 );
+	SELECT diag_test_name( 'PLANET_RES_REGEN update - Wrong update identifier (3/3)' );
+	SELECT ok( _get_quantity( 'testPlanet3' ) = 50 );
+
+
+	SELECT sys.process_planet_res_regen_updates( 0 );
+	SELECT diag_test_name( 'PLANET_RES_REGEN update - Processed planet with quantity < max.' );
+	SELECT ok( _get_quantity( 'testPlanet1' ) > 50 );
+	SELECT diag_test_name( 'PLANET_RES_REGEN update - Processed planet with quantity = max.' );
+	SELECT ok( _get_quantity( 'testPlanet2' ) = 100 );
+	SELECT diag_test_name( 'PLANET_RES_REGEN update - Already processed planet' );
+	SELECT ok( _get_quantity( 'testPlanet3' ) = 50 );
+
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From d4945d8e219dd250ff56ede5263934cdd71c7930 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 2 Jan 2012 15:11:17 +0100
Subject: [PATCH 10/94] Maven project for resources-related component

Added a Maven project that will contain all resources-related
components. Updated list of children in the top-level components
project. Added dependency to the main project.
---
 legacyworlds-server-beans-resources/pom.xml     | 17 +++++++++++++++++
 .../src/main/java/.empty                        |  0
 .../src/main/resources/.empty                   |  0
 .../src/test/java/.empty                        |  0
 .../src/test/resources/.empty                   |  0
 legacyworlds-server-beans/pom.xml               |  1 +
 legacyworlds-server-main/pom.xml                |  4 ++++
 legacyworlds/pom.xml                            |  5 +++++
 8 files changed, 27 insertions(+)
 create mode 100644 legacyworlds-server-beans-resources/pom.xml
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/.empty
 create mode 100644 legacyworlds-server-beans-resources/src/main/resources/.empty
 create mode 100644 legacyworlds-server-beans-resources/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-resources/src/test/resources/.empty

diff --git a/legacyworlds-server-beans-resources/pom.xml b/legacyworlds-server-beans-resources/pom.xml
new file mode 100644
index 0000000..404345a
--- /dev/null
+++ b/legacyworlds-server-beans-resources/pom.xml
@@ -0,0 +1,17 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<parent>
+		<artifactId>legacyworlds-server-beans</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-server-beans-resources</artifactId>
+	<name>Legacy Worlds - Server - Components - Resources</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<description>This module contains the components which manage and access in-universe resources, including money and natural resources.</description>
+
+</project>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-resources/src/main/java/.empty b/legacyworlds-server-beans-resources/src/main/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-resources/src/main/resources/.empty b/legacyworlds-server-beans-resources/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-resources/src/test/java/.empty b/legacyworlds-server-beans-resources/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-resources/src/test/resources/.empty b/legacyworlds-server-beans-resources/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans/pom.xml b/legacyworlds-server-beans/pom.xml
index 1c27874..6e138de 100644
--- a/legacyworlds-server-beans/pom.xml
+++ b/legacyworlds-server-beans/pom.xml
@@ -27,6 +27,7 @@
 		<module>../legacyworlds-server-beans-mailer</module>
 		<module>../legacyworlds-server-beans-system</module>
 		<module>../legacyworlds-server-beans-naming</module>
+		<module>../legacyworlds-server-beans-resources</module>
 		<module>../legacyworlds-server-beans-bt</module>
 		<module>../legacyworlds-server-beans-user</module>
 		<module>../legacyworlds-server-beans-simple</module>
diff --git a/legacyworlds-server-main/pom.xml b/legacyworlds-server-main/pom.xml
index 5f28b4b..e947132 100644
--- a/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server-main/pom.xml
@@ -38,6 +38,10 @@
 			<artifactId>legacyworlds-server-beans-naming</artifactId>
 			<groupId>com.deepclone.lw</groupId>
 		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-beans-resources</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-simple</artifactId>
 			<groupId>com.deepclone.lw</groupId>
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index 773e818..6cd929e 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -152,6 +152,11 @@
 				<groupId>com.deepclone.lw</groupId>
 				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-resources</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
 			<dependency>
 				<artifactId>legacyworlds-server-beans-simple</artifactId>
 				<groupId>com.deepclone.lw</groupId>

From e7d2072813659ab425a189bfe5c7c11a8e9e97b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 3 Jan 2012 09:35:57 +0100
Subject: [PATCH 11/94] SQL privileges fix and unit tests

* Fixed privileges on both variants of defs.uoc_natural_resource()

* Added user unit tests which check execution privileges on all (new)
stored procedures and INSERT/UPDATE/SELECT/DELETE privileges on all
(new) tables
---
 .../functions/025-resources-functions.sql     |  4 +--
 .../defs => data}/07500-resources.sql         |  0
 .../defs => data}/07501-natural-resources.sql |  0
 .../10003-resource-providers.sql              |  0
 .../defs => data}/11001-empire-resources.sql  |  0
 .../11002-empire-mining-settings.sql          |  0
 .../11003-empire-planet-mining-settings.sql   |  0
 .../user/priv/data/07500-defs-resources.sql   | 32 +++++++++++++++++++
 .../data/07501-defs-natural-resources.sql     | 28 ++++++++++++++++
 .../data/10003-verse-resource-providers.sql   | 28 ++++++++++++++++
 .../user/priv/data/11001-empire-resources.sql | 28 ++++++++++++++++
 .../data/11002-empire-mining-settings.sql     | 28 ++++++++++++++++
 .../11003-empire-planet-mining-settings.sql   | 28 ++++++++++++++++
 .../functions/02500-uoc-resource-internal.sql | 14 ++++++++
 .../priv/functions/02501-uoc-resource.sql     | 14 ++++++++
 .../functions/02502-uoc-natres-internal.sql   | 16 ++++++++++
 .../functions/02503-uoc-natural-resource.sql  | 18 +++++++++++
 .../14500-compute-provider-regeneration.sql   | 11 +++++++
 ...10500-process-planet-res-regen-updates.sql | 11 +++++++
 19 files changed, 258 insertions(+), 2 deletions(-)
 rename legacyworlds-server-data/db-structure/tests/admin/{constraints/defs => data}/07500-resources.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{constraints/defs => data}/07501-natural-resources.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{constraints/defs => data}/10003-resource-providers.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{constraints/defs => data}/11001-empire-resources.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{constraints/defs => data}/11002-empire-mining-settings.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{constraints/defs => data}/11003-empire-planet-mining-settings.sql (100%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/data/07500-defs-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/data/07501-defs-natural-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/data/10003-verse-resource-providers.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/data/11001-empire-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/data/11002-empire-mining-settings.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/data/11003-empire-planet-mining-settings.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/02500-uoc-resource-internal.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/02501-uoc-resource.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/02502-uoc-natres-internal.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/02503-uoc-natural-resource.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/14500-compute-provider-regeneration.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/updates/10500-process-planet-res-regen-updates.sql

diff --git a/legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql
index b401eb0..053f1f1 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql
@@ -393,7 +393,7 @@ CREATE OR REPLACE FUNCTION defs.uoc_natural_resource(
 			_recovery_dev DOUBLE PRECISION )
 	RETURNS defs.resource_update_result
 	STRICT VOLATILE
-	SECURITY INVOKER
+	SECURITY DEFINER
 AS $$
 	SELECT defs.uoc_natres_internal( $1 , $2 , NULL , $3 , $4 , $5 , $6 , $7 ,
 		$8 , $9 , $10 );
@@ -447,7 +447,7 @@ CREATE OR REPLACE FUNCTION defs.uoc_natural_resource(
 			_recovery_dev DOUBLE PRECISION )
 	RETURNS defs.resource_update_result
 	STRICT VOLATILE
-	SECURITY INVOKER
+	SECURITY DEFINER
 AS $$
 	SELECT defs.uoc_natres_internal( $1 , $2 , $3 , $4 , $5 , $6 , $7 , $8 ,
 		$9 , $10 , $11 );
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07500-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/data/07500-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07500-resources.sql
rename to legacyworlds-server-data/db-structure/tests/admin/data/07500-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07501-natural-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/data/07501-natural-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/constraints/defs/07501-natural-resources.sql
rename to legacyworlds-server-data/db-structure/tests/admin/data/07501-natural-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/10003-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/admin/data/10003-resource-providers.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/constraints/defs/10003-resource-providers.sql
rename to legacyworlds-server-data/db-structure/tests/admin/data/10003-resource-providers.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11001-empire-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/data/11001-empire-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11001-empire-resources.sql
rename to legacyworlds-server-data/db-structure/tests/admin/data/11001-empire-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11002-empire-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/admin/data/11002-empire-mining-settings.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11002-empire-mining-settings.sql
rename to legacyworlds-server-data/db-structure/tests/admin/data/11002-empire-mining-settings.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11003-empire-planet-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/admin/data/11003-empire-planet-mining-settings.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/constraints/defs/11003-empire-planet-mining-settings.sql
rename to legacyworlds-server-data/db-structure/tests/admin/data/11003-empire-planet-mining-settings.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/07500-defs-resources.sql b/legacyworlds-server-data/db-structure/tests/user/priv/data/07500-defs-resources.sql
new file mode 100644
index 0000000..c04c7eb
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/data/07500-defs-resources.sql
@@ -0,0 +1,32 @@
+/*
+ * Test privileges on defs.resources
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'defs.resources - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO defs.resources(
+			resource_name_id , resource_description_id , resource_weight
+		) VALUES (
+			1 , 2 , 1
+		); $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'defs.resources - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE defs.resources SET resource_weight = 10; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'defs.resources - SELECT privileges' );
+	SELECT throws_ok(
+		$$ SELECT * FROM defs.resources; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'defs.resources - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM defs.resources; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/07501-defs-natural-resources.sql b/legacyworlds-server-data/db-structure/tests/user/priv/data/07501-defs-natural-resources.sql
new file mode 100644
index 0000000..40894ba
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/data/07501-defs-natural-resources.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on defs.natural_resources
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'defs.natural_resources - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO defs.natural_resources( resource_name_id ) VALUES ( 1 ); $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'defs.natural_resources - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE defs.natural_resources SET natres_p_presence = 0.5; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'defs.natural_resources - SELECT privileges' );
+	SELECT throws_ok(
+		$$ SELECT * FROM defs.natural_resources; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'defs.natural_resources - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM defs.natural_resources; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/10003-verse-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/user/priv/data/10003-verse-resource-providers.sql
new file mode 100644
index 0000000..227789b
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/data/10003-verse-resource-providers.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on verse.resource_providers
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'verse.resource_providers - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO verse.resource_providers( resource_name_id ) VALUES ( 1 ); $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.resource_providers - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE verse.resource_providers SET resprov_quantity = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.resource_providers - SELECT privileges' );
+	SELECT throws_ok(
+		$$ SELECT * FROM verse.resource_providers; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.resource_providers - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM verse.resource_providers; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/11001-empire-resources.sql b/legacyworlds-server-data/db-structure/tests/user/priv/data/11001-empire-resources.sql
new file mode 100644
index 0000000..b565b91
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/data/11001-empire-resources.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on emp.resources
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'emp.resources - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO emp.resources( resource_name_id ) VALUES ( 1 ); $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.resources - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE emp.resources SET empres_possessed = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.resources - SELECT privileges' );
+	SELECT throws_ok(
+		$$ SELECT * FROM emp.resources; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.resources - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM emp.resources; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/11002-empire-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/user/priv/data/11002-empire-mining-settings.sql
new file mode 100644
index 0000000..040d66b
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/data/11002-empire-mining-settings.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on emp.mining_settings
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'emp.mining_settings - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO emp.mining_settings( resource_name_id ) VALUES ( 1 ); $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.mining_settings - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE emp.mining_settings SET empmset_weight = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.mining_settings - SELECT privileges' );
+	SELECT throws_ok(
+		$$ SELECT * FROM emp.mining_settings; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.mining_settings - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM emp.mining_settings; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/11003-empire-planet-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/user/priv/data/11003-empire-planet-mining-settings.sql
new file mode 100644
index 0000000..34d1e27
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/data/11003-empire-planet-mining-settings.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on emp.planet_mining_settings
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'emp.planet_mining_settings - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO emp.planet_mining_settings( resource_name_id ) VALUES ( 1 ); $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.planet_mining_settings - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE emp.planet_mining_settings SET emppmset_weight = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.planet_mining_settings - SELECT privileges' );
+	SELECT throws_ok(
+		$$ SELECT * FROM emp.planet_mining_settings; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'emp.planet_mining_settings - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM emp.planet_mining_settings; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/02500-uoc-resource-internal.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/02500-uoc-resource-internal.sql
new file mode 100644
index 0000000..c66b9dc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/02500-uoc-resource-internal.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.uoc_resource_internal()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - Privileges' );
+	PREPARE _test_this AS
+		SELECT defs.uoc_resource_internal( 'test1' , 'test2' , NULL , 1 );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/02501-uoc-resource.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/02501-uoc-resource.sql
new file mode 100644
index 0000000..56053d4
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/02501-uoc-resource.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.uoc_resource()
+ */
+BEGIN;
+	SELECT plan( 2 );
+	
+	SELECT diag_test_name( 'defs.uoc_resource() - Privileges (without category)' );
+	SELECT is( defs.uoc_resource( 'test1' , 'test2' , 1 ) , 'BAD_STRINGS' );
+	
+	SELECT diag_test_name( 'defs.uoc_resource() - Privileges (with category)' );
+	SELECT is( defs.uoc_resource( 'test1' , 'test2' , 'test3' , 1 ) , 'BAD_STRINGS' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/02502-uoc-natres-internal.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/02502-uoc-natres-internal.sql
new file mode 100644
index 0000000..1a321e5
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/02502-uoc-natres-internal.sql
@@ -0,0 +1,16 @@
+/*
+ * Test privileges on defs.uoc_natres_internal()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - Privileges' );
+	PREPARE _test_this AS
+		SELECT defs.uoc_natres_internal( 'test1' , 'test2' , NULL , 1 ,
+				0.5 , 100 , 50 , 0.5 , 0.1 , 0.5 , 0.1
+			);
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/02503-uoc-natural-resource.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/02503-uoc-natural-resource.sql
new file mode 100644
index 0000000..dee7b74
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/02503-uoc-natural-resource.sql
@@ -0,0 +1,18 @@
+/*
+ * Test privileges on defs.uoc_natural_resource()
+ */
+BEGIN;
+	SELECT plan( 2 );
+	
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - Privileges (without category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test2' , 1 ,
+			0.5 , 100 , 50 , 0.5 , 0.1 , 0.5 , 0.1
+		) , 'BAD_STRINGS' );
+	
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - Privileges (with category)' );
+	SELECT is( defs.uoc_natural_resource( 'test1' , 'test2' , 'test3' , 1 ,
+			0.5 , 100 , 50 , 0.5 , 0.1 , 0.5 , 0.1
+		) , 'BAD_STRINGS' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/14500-compute-provider-regeneration.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/14500-compute-provider-regeneration.sql
new file mode 100644
index 0000000..cce7c93
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/14500-compute-provider-regeneration.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on verse.compute_provider_regeneration()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'verse.compute_provider_regeneration() - Privileges' );
+	SELECT throws_ok( 'SELECT verse.compute_provider_regeneration( 100.0 , 100.0 , 0.5 )' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/updates/10500-process-planet-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/user/priv/updates/10500-process-planet-res-regen-updates.sql
new file mode 100644
index 0000000..b08eb50
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/updates/10500-process-planet-res-regen-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.process_planet_res_regen_updates()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.process_planet_res_regen_updates() - Privileges' );
+	SELECT throws_ok( 'SELECT sys.process_planet_res_regen_updates( 1 )' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From b054a379a997223e3882fc619b32429e0438d3fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Fri, 6 Jan 2012 10:05:47 +0100
Subject: [PATCH 12/94] Universe generator: resource providers

 * The universe generator has been modified to generate resource
providers. The code attempts to keep the universe balanced according to
the natural resources definitions.
---
 .../db-structure/parts/020-functions.sql      |   2 +
 .../functions/050-computation-functions.sql   | 109 +++++
 .../parts/functions/053-generator-basics.sql  |  51 +++
 .../functions/055-generator-resources.sql     | 384 ++++++++++++++++++
 .../functions/060-universe-functions.sql      |  22 +-
 .../010-uoc-resource-internal.sql}            |   0
 .../020-uoc-resource.sql}                     |   0
 .../030-uoc-natres-internal.sql}              |   0
 .../040-uoc-natural-resource.sql}             |   0
 .../050-computation/010-get-random-part.sql   |  44 ++
 .../010-list-random-planets-in.sql            |  41 ++
 .../010-collect-resprov-statistics.sql        |  72 ++++
 .../020-compute-rpp-delta.sql                 |  56 +++
 .../030-create-resource-provider.sql          |  62 +++
 ...40-create-resource-providers-with-type.sql | 129 ++++++
 .../050-create-resource-providers.sql         |  35 ++
 .../010-compute-provider-regeneration.sql}    |   0
 .../050-computation/010-get-random-part.sql   |  14 +
 .../010-list-random-planets-in.sql            |  14 +
 .../010-collect-resprov-statistics.sql        |  14 +
 .../020-compute-rpp-delta.sql                 |  14 +
 .../030-create-resource-provider.sql          |  16 +
 ...40-create-resource-providers-with-type.sql |  18 +
 .../050-create-resource-providers.sql         |  14 +
 24 files changed, 1105 insertions(+), 6 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/functions/053-generator-basics.sql
 create mode 100644 legacyworlds-server-data/db-structure/parts/functions/055-generator-resources.sql
 rename legacyworlds-server-data/db-structure/tests/admin/functions/{defs/02500-uoc-resource-internal.sql => 025-resources/010-uoc-resource-internal.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/functions/{defs/02501-uoc-resource.sql => 025-resources/020-uoc-resource.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/functions/{defs/02502-uoc-natres-internal.sql => 025-resources/030-uoc-natres-internal.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/functions/{defs/02503-uoc-natural-resource.sql => 025-resources/040-uoc-natural-resource.sql} (100%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/050-computation/010-get-random-part.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/053-generator-basics/010-list-random-planets-in.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/010-collect-resprov-statistics.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/020-compute-rpp-delta.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/030-create-resource-provider.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/040-create-resource-providers-with-type.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/050-create-resource-providers.sql
 rename legacyworlds-server-data/db-structure/tests/admin/functions/{verse/14500-compute-provider-regeneration.sql => 145-resource-providers/010-compute-provider-regeneration.sql} (100%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/050-computation/010-get-random-part.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/053-generator-basics/010-list-random-planets-in.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/010-collect-resprov-statistics.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/020-compute-rpp-delta.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/030-create-resource-provider.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/040-create-resource-providers-with-type.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/050-create-resource-providers.sql

diff --git a/legacyworlds-server-data/db-structure/parts/020-functions.sql b/legacyworlds-server-data/db-structure/parts/020-functions.sql
index 18c48c0..027e727 100644
--- a/legacyworlds-server-data/db-structure/parts/020-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/020-functions.sql
@@ -16,6 +16,8 @@
 \i parts/functions/035-users-view.sql
 \i parts/functions/040-empire-functions.sql
 \i parts/functions/050-computation-functions.sql
+\i parts/functions/053-generator-basics.sql
+\i parts/functions/055-generator-resources.sql
 \i parts/functions/060-universe-functions.sql
 \i parts/functions/070-users-functions.sql
 \i parts/functions/075-session-functions.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
index e5304c2..ebc8ed6 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
@@ -7,6 +7,115 @@
 -- --------------------------------------------------------
 
 
+/*
+ * Random value with deviation
+ * 
+ * Parameters:
+ *		_mean		the mean value
+ *		_deviation	the deviation
+ *
+ * Returns:
+ *		?			a random value between _mean - _deviation and
+ *						_mean + _deviation, with a higher probability
+ *						of a value that is close to _mean
+ */
+DROP FUNCTION IF EXISTS verse.random_deviation( DOUBLE PRECISION , DOUBLE PRECISION );
+CREATE FUNCTION verse.random_deviation( _mean DOUBLE PRECISION , _deviation DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $random_deviation$
+
+DECLARE
+	_result	DOUBLE PRECISION;
+
+BEGIN
+	_result := _deviation * RANDOM( ) ^ 2.5;
+	IF RANDOM() < 0.5 THEN
+		_result := -_result;
+	END IF;
+	RETURN _result + _mean;
+END;
+$random_deviation$ LANGUAGE PLPGSQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION verse.random_deviation( DOUBLE PRECISION , DOUBLE PRECISION )
+	FROM PUBLIC;
+
+
+
+/*
+ * Randomly distribute some part of a total value
+ * 
+ * This function can be used when a total value must be distributed between
+ * various items. It will compute the minimal and maximal values that may be
+ * attributed, enforcing the fact that the whole value needs to be consumed
+ * in the end, and that values must conform to a specific range expressed as
+ * a mean value and a deviation.
+ * 
+ * The total value is assumed to be valid with regards to the mean and
+ * deviation. That is:
+ * 
+ *		_parts * ( _mean - _deviation ) <= _quantity
+ *		_parts * ( _mean + _deviation ) >= _quantity
+ *
+ * Parameters:
+ *		_quantity		the total quantity left to distribute
+ *		_parts			the amount of items left
+ *		_mean			the result's mean value
+ *		_deviation		the result's deviation
+ *
+ * Returns:
+ * 		?				the value to attribute to the nex item
+ */
+DROP FUNCTION IF EXISTS verse.get_random_part( DOUBLE PRECISION , INT , DOUBLE PRECISION , DOUBLE PRECISION );
+CREATE FUNCTION verse.get_random_part(
+			_quantity DOUBLE PRECISION ,
+			_parts INT ,
+			_mean DOUBLE PRECISION ,
+			_deviation DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $get_random_part$
+
+DECLARE
+	_min	DOUBLE PRECISION;
+	_max	DOUBLE PRECISION;
+	_n_mean	DOUBLE PRECISION;
+
+BEGIN
+	IF _parts = 1 THEN
+		RETURN _quantity;
+	END IF;
+
+	_min := _quantity - ( _mean + _deviation ) * ( _parts - 1 );
+	IF _min < _mean - _deviation THEN
+		_min := _mean - _deviation;
+	END IF;
+
+	_max := _quantity - ( _mean - _deviation ) * ( _parts - 1 );
+	IF _max > _mean + _deviation THEN
+		_max := _mean + _deviation;
+	END IF;
+	
+	IF _min = _max THEN
+		RETURN _min;
+	END IF;
+	
+	_n_mean := ( _min + _max ) * 0.5;
+	RETURN verse.random_deviation( _n_mean , _n_mean - _min );
+END;
+$get_random_part$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.get_random_part( DOUBLE PRECISION , INT , DOUBLE PRECISION , DOUBLE PRECISION )
+	FROM PUBLIC;
+
+
+
+
 --
 -- sigma( x ) = exp( x ) / ( 1 + exp( x ) )
 --
diff --git a/legacyworlds-server-data/db-structure/parts/functions/053-generator-basics.sql b/legacyworlds-server-data/db-structure/parts/functions/053-generator-basics.sql
new file mode 100644
index 0000000..a7fb6f0
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/functions/053-generator-basics.sql
@@ -0,0 +1,51 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- A few types and internal functions used in most parts
+-- of the universe generator.
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/* The coordinates of the area being generated. */
+DROP TYPE IF EXISTS verse.generator_area_type CASCADE;
+CREATE TYPE verse.generator_area_type AS (
+	x0 INT , y0 INT ,
+	x1 INT , y1 INT
+);
+
+
+
+/*
+ * List some quantity of random planets from an area of the universe
+ * 
+ * This function returns a set of planet identifiers chosen at random from the
+ * specified area of the universe.
+ * 
+ * Parameters:
+ *		_area		The area to select planets from
+ *		_count		The maximal amount of planets to return
+ */
+DROP FUNCTION IF EXISTS verse.list_random_planets_in( verse.generator_area_type , INT );
+CREATE FUNCTION verse.list_random_planets_in( _area verse.generator_area_type , _count INT )
+	RETURNS SETOF INT
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $list_random_planets_in$
+
+	SELECT _planets.name_id
+		FROM verse.planets _planets
+			INNER JOIN verse.systems _systems
+				ON _planets.system_id = _systems.id
+		WHERE _systems.x BETWEEN $1.x0 AND $1.x1
+			AND _systems.y BETWEEN $1.y0 AND $1.y1
+		ORDER BY RANDOM( )
+		LIMIT $2;
+
+$list_random_planets_in$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.list_random_planets_in( verse.generator_area_type ,
+			INT )
+	FROM PUBLIC;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/functions/055-generator-resources.sql b/legacyworlds-server-data/db-structure/parts/functions/055-generator-resources.sql
new file mode 100644
index 0000000..a2418ac
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/functions/055-generator-resources.sql
@@ -0,0 +1,384 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Functions for the resource provider generator
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+
+/*
+ * Resource provider generator data
+ * 
+ * This data type is used to store statistics about the existing resource
+ * providers. A single item of the type represents both statistics and
+ * parameters for a given resource type.
+ */
+DROP TYPE IF EXISTS verse.resprov_generator_type CASCADE;
+CREATE TYPE verse.resprov_generator_type AS (
+		/* Type of natural resource */
+	 	resource_name_id		INT ,
+
+	 	/* Planets in the universe */
+	 	planets					DOUBLE PRECISION ,
+	 	
+	 	/* Providers of this type in the universe */
+	 	providers				DOUBLE PRECISION ,
+
+	 	/* Presence probability (from the resource's definition) */
+	 	presence				DOUBLE PRECISION ,
+
+	 	/* Total maximal quantity of this type of resource in the whole
+	 	 * universe.
+	 	 */
+	 	quantity				DOUBLE PRECISION ,
+	 	
+	 	/* Average quantity (from the resource's definition) */
+	 	quantity_avg			DOUBLE PRECISION ,
+	 	
+	 	/* Maximal deviation from the average quantity (from the resource's
+	 	 * definition)
+	 	 */
+	 	quantity_dev			DOUBLE PRECISION ,
+
+	 	/* Total extraction difficulty for this type of resource in the whole
+	 	 * universe.
+	 	 */
+	 	difficulty				DOUBLE PRECISION ,
+	 	
+	 	/* Average difficulty (from the resource's definition) */
+	 	difficulty_avg			DOUBLE PRECISION ,
+	 	
+	 	/* Maximal deviation from the average difficulty (from the resource's
+	 	 * definition)
+	 	 */
+	 	difficulty_dev			DOUBLE PRECISION ,
+
+	 	/* Total recovery rate for this type of resource in the whole
+	 	 * universe.
+	 	 */
+	 	recovery				DOUBLE PRECISION ,
+	 	
+	 	/* Average recovery rate (from the resource's definition) */
+	 	recovery_avg			DOUBLE PRECISION ,
+	 	
+	 	/* Maximal deviation from the average recovery rate (from the
+	 	 */
+	 	recovery_dev			DOUBLE PRECISION
+);
+
+
+
+/*
+ * Collect resource provider statistics
+ * 
+ * This procedure collects statistics about resource providers into a
+ * temporary table named resource_statistics, using the resprov_generator_type
+ * as the table's structure. The table will be dropped on commit.
+ * 
+ * This function is necessary because the statistics must be collected before
+ * new planets are generated.
+ */
+DROP FUNCTION IF EXISTS verse.collect_resprov_statistics( );
+CREATE FUNCTION verse.collect_resprov_statistics( )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $collect_resprov_statistics$
+
+BEGIN
+	 CREATE TEMP TABLE rp_stats
+	 	OF verse.resprov_generator_type
+	 	ON COMMIT DROP;
+
+	INSERT INTO rp_stats
+		SELECT resource_name_id ,
+					_pcount.planets AS planets ,
+					( CASE
+						WHEN _rp_stats.providers IS NULL THEN 0
+						ELSE _rp_stats.providers
+					END ) AS providers ,
+					natres_p_presence AS presence ,
+					( CASE
+						WHEN _rp_stats.tot_quantity IS NULL THEN 0
+						ELSE _rp_stats.tot_quantity
+					END ) AS quantity ,
+					natres_quantity_avg AS quantity_avg ,
+					natres_quantity_dev AS quantity_dev ,
+					( CASE
+						WHEN _rp_stats.tot_difficulty IS NULL THEN 0
+						ELSE _rp_stats.tot_difficulty
+					END ) AS difficulty ,
+					natres_difficulty_avg AS difficulty_avg ,
+					natres_difficulty_dev AS difficulty_dev ,
+					( CASE
+						WHEN _rp_stats.tot_recovery IS NULL THEN 0
+						ELSE _rp_stats.tot_recovery
+					END ) AS recovery ,
+					natres_recovery_avg AS recovery_avg ,
+					natres_recovery_dev AS recovery_dev
+			FROM defs.natural_resources
+				LEFT OUTER JOIN (
+					SELECT resource_name_id ,
+							COUNT(*) AS providers ,
+							SUM( resprov_quantity_max ) AS tot_quantity ,
+							SUM( resprov_difficulty ) AS tot_difficulty ,
+							SUM( resprov_recovery ) AS tot_recovery
+						FROM verse.resource_providers
+						GROUP BY resource_name_id
+					) AS _rp_stats USING ( resource_name_id )
+				CROSS JOIN (
+					SELECT COUNT(*) AS planets
+						FROM verse.planets
+				) AS _pcount;
+END;
+$collect_resprov_statistics$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.collect_resprov_statistics( )
+	FROM PUBLIC;
+
+
+
+/* Compute a random delta for one of the resource provider parameters
+ * 
+ * This function computes the total change on one of the resource provider
+ * parameters. The resulting value can then be split amongst resource
+ * providers as they are created.
+ * 
+ * Parameters:
+ *		_existing			Amount of existing resource providers
+ *		_new				Amount of resource providers being created
+ *		_total				Current total value for the parameter
+ *		_p_average			Average parameter value (from the definition)
+ *		_p_deviation		Parameter value deviation (from the definition)
+ *
+ * Returns:
+ *		?					The total value to distribute amongst new resource
+ *								providers
+ */
+DROP FUNCTION IF EXISTS verse.compute_rpp_delta( DOUBLE PRECISION , DOUBLE PRECISION ,
+		DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION );
+CREATE FUNCTION verse.compute_rpp_delta(
+			_existing		DOUBLE PRECISION ,
+			_new			DOUBLE PRECISION ,
+			_total			DOUBLE PRECISION ,
+			_p_average		DOUBLE PRECISION ,
+			_p_deviation	DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $compute_rpp_delta$
+
+DECLARE
+	_result	DOUBLE PRECISION;
+
+BEGIN
+	_result := verse.random_deviation( _p_average , _p_deviation )
+					* ( _existing + _new ) - _total;
+
+	IF _result < ( _p_average - _p_deviation ) * _new THEN
+		_result := ( _p_average - _p_deviation ) * _new;
+	ELSIF _result > ( _p_average + _p_deviation ) * _new THEN
+		_result := ( _p_average + _p_deviation ) * _new;
+	END IF;
+	
+	RETURN _result;
+END;
+$compute_rpp_delta$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.compute_rpp_delta( DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION )
+	FROM PUBLIC;
+
+
+
+/*
+ * Create a single resource provider
+ * 
+ * This function creates a single resource provider on some planet. It will
+ * return the updated values for the amount of providers left to handle and
+ * the totals.
+ * 
+ * Parameters:
+ *		_planet			The identifier of the planet to create a provider on
+ *		_data			The statistics and parameters for the type of resource
+ *		_providers_left	The amount of resource providers that still need to be
+ *							generated (including the current provider)
+ *		_tot_quantity	The total value left to distribute for the providers'
+ *							maximal quantity
+ *		_tot_difficulty	The total value left to distribute for the providers'
+ *							extraction difficulty
+ *		_tot_recovery	The total value left to distribute for the providers'
+ *							recovery rate
+ *
+ * Returns:
+ *		_providers_left	The updated value for the amount of providers left
+ *		_tot_quantity	The updated value for the total maximal quantity to
+ *							distribute
+ *		_tot_difficulty	The updated value for the total extraction difficulty
+ *							to distribute
+ *		_tot_recovery	The updated value for the total recovery rate to
+ *							distribute
+ */
+DROP FUNCTION IF EXISTS verse.create_resource_provider(
+	INT , verse.resprov_generator_type , INT , DOUBLE PRECISION ,
+	DOUBLE PRECISION , DOUBLE PRECISION );
+CREATE FUNCTION verse.create_resource_provider(
+			_planet					INT ,
+			_data					verse.resprov_generator_type ,
+			INOUT _providers_left	INT ,
+			INOUT _tot_quantity		DOUBLE PRECISION ,
+			INOUT _tot_difficulty	DOUBLE PRECISION ,
+			INOUT _tot_recovery		DOUBLE PRECISION )
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $create_resource_provider$
+
+DECLARE
+	_quantity		DOUBLE PRECISION;
+	_difficulty		DOUBLE PRECISION;
+	_recovery		DOUBLE PRECISION;
+
+BEGIN
+	_quantity := verse.get_random_part( _tot_quantity , _providers_left ,
+		_data.quantity_avg , _data.quantity_dev );
+	_difficulty := verse.get_random_part( _tot_difficulty , _providers_left ,
+		_data.difficulty_avg , _data.difficulty_dev );
+	_recovery := verse.get_random_part( _tot_recovery , _providers_left ,
+		_data.recovery_avg , _data.recovery_dev );
+
+	RAISE NOTICE 'Resource #% planet #%: quantity: %   difficulty: %   recovery: %',
+		_data.resource_name_id , _planet ,
+		_quantity , _difficulty , _recovery;
+
+	INSERT INTO verse.resource_providers (
+			planet_id , resource_name_id , resprov_quantity_max ,
+			resprov_quantity , resprov_difficulty , resprov_recovery
+		) VALUES (
+			_planet , _data.resource_name_id , _quantity ,
+			_quantity , _difficulty , _recovery
+		);
+
+	_tot_quantity := _tot_quantity - _quantity;
+	_tot_difficulty := _tot_difficulty - _difficulty;
+	_tot_recovery := _tot_recovery - _recovery;
+	_providers_left := _providers_left - 1;
+END;
+$create_resource_provider$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.create_resource_provider( INT ,
+			verse.resprov_generator_type , INT , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION )
+	FROM PUBLIC;
+
+
+
+/*
+ * Create resource providers for a given type of resource
+ * 
+ * This function will create resource providers for some specified type of
+ * resource in an area of the universe. It tries to balance the generated
+ * values according to the resource's definition.
+ * 
+ * Parameters:
+ *		_area		The area to generate resource providers in
+ *		_data		The identifier, definition and statistics for the type of
+ *						resource
+ */
+DROP FUNCTION IF EXISTS verse.create_resource_providers(
+	verse.generator_area_type , verse.resprov_generator_type );
+CREATE FUNCTION verse.create_resource_providers(
+			_area verse.generator_area_type ,
+			_data verse.resprov_generator_type )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $create_resource_providers$
+
+DECLARE
+	_ncount			INT;
+	_create			INT;
+	_tot_quantity	DOUBLE PRECISION;
+	_tot_difficulty	DOUBLE PRECISION;
+	_tot_recovery	DOUBLE PRECISION;
+	_planet			INT;
+
+BEGIN
+	_ncount := ( _area.x1 - _area.x0 + 1 ) * ( _area.y1 - _area.y0 + 1 ) * 5;
+
+	-- Determine the amount of providers to create
+	_create := FLOOR( ( _data.planets + _ncount ) * _data.presence - _data.providers )::INT;
+	IF _create <= 0 THEN
+		RETURN;
+	ELSIF _create > _ncount THEN
+		_create := _ncount;
+	END IF;
+
+	-- Compute the total delta for quantity, difficulty and recovery rate
+	_tot_quantity := verse.compute_rpp_delta( _data.providers , _create ,
+			_data.quantity , _data.quantity_avg , _data.quantity_dev );
+	_tot_difficulty := verse.compute_rpp_delta( _data.providers , _create ,
+			_data.difficulty , _data.difficulty_avg , _data.difficulty_dev );
+	_tot_recovery := verse.compute_rpp_delta( _data.providers , _create ,
+			_data.recovery , _data.recovery_avg , _data.recovery_dev );
+
+	RAISE NOTICE 'Resource #%: % new provider(s), quantity: % (avg. %) , difficulty: % (avg. %), recovery: % (avg. %)',
+		_data.resource_name_id , _create ,
+		_tot_quantity , _tot_quantity / _create ,
+		_tot_difficulty , _tot_difficulty / _create ,
+		_tot_recovery , _tot_recovery / _create;
+
+	-- Select random planets to add resource providers to
+	FOR _planet IN SELECT * FROM verse.list_random_planets_in( _area , _create )
+	LOOP
+		SELECT INTO _create , _tot_quantity , _tot_difficulty , _tot_recovery
+			* FROM verse.create_resource_provider( _planet , _data , _create ,
+					_tot_quantity , _tot_difficulty , _tot_recovery );
+	END LOOP;
+
+END;
+$create_resource_providers$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.create_resource_providers( verse.generator_area_type ,
+			verse.resprov_generator_type )
+	FROM PUBLIC;
+
+
+
+/*
+ * Create resource providers in some area of the universe
+ * 
+ * This function creates resource providers in the specified area using the
+ * statistics collected before the area was created to balance the resource
+ * providers' parameters.
+ * 
+ * Parameters:
+ *		_area		The area to generate resource providers in
+ */
+DROP FUNCTION IF EXISTS verse.create_resource_providers( verse.generator_area_type );
+CREATE FUNCTION verse.create_resource_providers( _area verse.generator_area_type )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $create_resource_providers$
+
+DECLARE
+	_rp_data		verse.resprov_generator_type;
+
+BEGIN
+	FOR _rp_data IN SELECT * FROM rp_stats
+	LOOP
+		PERFORM verse.create_resource_providers( _area , _rp_data );
+	END LOOP;
+END;
+$create_resource_providers$ LANGUAGE PLPGSQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION verse.create_resource_providers( verse.generator_area_type )
+	FROM PUBLIC;
diff --git a/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql b/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
index 780986a..1a135ea 100644
--- a/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
+++ b/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
@@ -245,11 +245,12 @@ $$ LANGUAGE plpgsql;
 -- Generate multiple systems at the specified coordinates
 --
 -- Parameters:
---	(x0,y0)-(x1,y1)		Area to generate
+--	_area				Area to generate
 --	ipop				Initial population
 --
 
-CREATE OR REPLACE FUNCTION verse.create_systems( x0 INT , y0 INT , x1 INT , y1 INT , ipop REAL )
+DROP FUNCTION IF EXISTS verse.create_systems( verse.generator_area_type , REAL );
+CREATE FUNCTION verse.create_systems( _area verse.generator_area_type , ipop REAL )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -259,18 +260,27 @@ DECLARE
 	y		INT;
 	npics	INT;
 BEGIN
+	PERFORM verse.collect_resource_statistics( );
+
 	npics := floor( sys.get_constant( 'game.universe.pictures' ) );
-	FOR x IN x0 .. x1
+	FOR x IN _area.x0 .. area.x1
 	LOOP
-		FOR y IN y0 .. y1
+		FOR y IN area.y0 .. area.y1
 		LOOP
 			PERFORM verse.create_system( x , y , ipop , npics );
 		END LOOP;
 	END LOOP;
+
+	PERFORM verse.create_resource_providers( _area );
 END;
 $$ LANGUAGE plpgsql;
 
 
+REVOKE EXECUTE
+	ON FUNCTION verse.create_systems( _area verse.generator_area_type , REAL )
+	FROM PUBLIC;
+
+
 
 --
 -- Generate the initial universe
@@ -348,7 +358,7 @@ BEGIN
 
 	-- Get average population and generate new systems
 	SELECT INTO pop AVG( population ) FROM verse.planets;
-	PERFORM verse.create_systems( x0 , y0 , x1 , y1 , pop );
+	PERFORM verse.create_systems( ROW( x0 , y0 , x1 , y1 ) , pop );
 END;
 $$ LANGUAGE plpgsql;
 
@@ -391,4 +401,4 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION verse.generate() TO :dbuser;
\ No newline at end of file
+GRANT EXECUTE ON FUNCTION verse.generate() TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02500-uoc-resource-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/010-uoc-resource-internal.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/defs/02500-uoc-resource-internal.sql
rename to legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/010-uoc-resource-internal.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02501-uoc-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/020-uoc-resource.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/defs/02501-uoc-resource.sql
rename to legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/020-uoc-resource.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02502-uoc-natres-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/030-uoc-natres-internal.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/defs/02502-uoc-natres-internal.sql
rename to legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/030-uoc-natres-internal.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/defs/02503-uoc-natural-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/040-uoc-natural-resource.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/defs/02503-uoc-natural-resource.sql
rename to legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/040-uoc-natural-resource.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/050-computation/010-get-random-part.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/050-computation/010-get-random-part.sql
new file mode 100644
index 0000000..7192fee
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/050-computation/010-get-random-part.sql
@@ -0,0 +1,44 @@
+/*
+ * Test the verse.get_random_part() function
+ */
+BEGIN;
+	SELECT plan( 6 );
+
+	/* First set of tests: results of the function with a fake
+	 * verse.random_deviation() that always returns the minimal possible
+	 * value.
+	 */
+	CREATE OR REPLACE FUNCTION verse.random_deviation( _mean DOUBLE PRECISION , _deviation DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+		STRICT VOLATILE
+	AS $$
+		SELECT $1 - $2;
+	$$ LANGUAGE SQL;
+
+	SELECT diag_test_name( 'verse.get_random_part() - Single part, minimal random value' );
+	SELECT is( verse.get_random_part( 0.5 , 1 , 0.5 , 0.25 ) , 0.5::DOUBLE PRECISION );
+	SELECT diag_test_name( 'verse.get_random_part() - Two parts, some extra quantity, minimal random value' );
+	SELECT is( verse.get_random_part( 0.75 , 2 , 0.5 , 0.25 ) , 0.25::DOUBLE PRECISION );
+	SELECT diag_test_name( 'verse.get_random_part() - Two parts, maximal quantity, minimal random value' );
+	SELECT is( verse.get_random_part( 1.5 , 2 , 0.5 , 0.25 ) , 0.75::DOUBLE PRECISION );
+
+	/* Second set of tests: results of the function with a fake
+	 * verse.random_deviation() that always returns the maximal possible
+	 * value.
+	 */
+	CREATE OR REPLACE FUNCTION verse.random_deviation( _mean DOUBLE PRECISION , _deviation DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+		STRICT VOLATILE
+	AS $$
+		SELECT $1 + $2;
+	$$ LANGUAGE SQL;
+
+	SELECT diag_test_name( 'verse.get_random_part() - Single part, maximal random value' );
+	SELECT is( verse.get_random_part( 0.5 , 1 , 0.5 , 0.25 ) , 0.5::DOUBLE PRECISION );
+	SELECT diag_test_name( 'verse.get_random_part() - Two parts, some extra quantity, maximal random value' );
+	SELECT is( verse.get_random_part( 1 , 2 , 0.5 , 0.25 ) , 0.75::DOUBLE PRECISION );
+	SELECT diag_test_name( 'verse.get_random_part() - Two parts, minimal quantity, maximal random value' );
+	SELECT is( verse.get_random_part( 0.5 , 2 , 0.5 , 0.25 ) , 0.25::DOUBLE PRECISION );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/053-generator-basics/010-list-random-planets-in.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/053-generator-basics/010-list-random-planets-in.sql
new file mode 100644
index 0000000..ba89819
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/053-generator-basics/010-list-random-planets-in.sql
@@ -0,0 +1,41 @@
+/*
+ * Test the verse.list_random_planets_in() function
+ */
+BEGIN;
+	
+	/* We need two systems with one planet each. Systems will be located at
+	 * (0,0) and (1,1).
+	 */
+	\i utils/strings.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_raw_planets( 10 , 'test' );
+
+	/***** TESTS BEGIN HERE *****/ 
+	SELECT plan( 5 );
+
+	SELECT diag_test_name( 'verse.list_random_planets_in() - Empty set if count = 0' );
+	SELECT is_empty( $$
+		SELECT * FROM verse.list_random_planets_in( ROW( 0 , 0 , 1 , 1 ) , 0 )
+	$$ );
+
+	SELECT diag_test_name( 'verse.list_random_planets_in() - Empty set if outside of existing universe' );
+	SELECT is_empty( $$
+		SELECT * FROM verse.list_random_planets_in( ROW( 2 , 2 , 3 , 3 ) , 2 )
+	$$ );
+
+	SELECT diag_test_name( 'verse.list_random_planets_in() - Requested count > actual count' );
+	SELECT is( COUNT(*) , 5::BIGINT )
+		FROM verse.list_random_planets_in( ROW( 1 , 1 , 2 , 2 ) , 7 );
+
+	SELECT diag_test_name( 'verse.list_random_planets_in() - Requested count = actual count' );
+	SELECT is( COUNT(*) , 5::BIGINT )
+		FROM verse.list_random_planets_in( ROW( 1 , 1 , 2 , 2 ) , 5 );
+
+	SELECT diag_test_name( 'verse.list_random_planets_in() - Requested count < actual count' );
+	SELECT is( COUNT(*) , 4::BIGINT )
+		FROM verse.list_random_planets_in( ROW( 1 , 1 , 2 , 2 ) , 4 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/010-collect-resprov-statistics.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/010-collect-resprov-statistics.sql
new file mode 100644
index 0000000..2b61f59
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/010-collect-resprov-statistics.sql
@@ -0,0 +1,72 @@
+/*
+ * Test the verse.collect_resprov_statistics( ) function
+ */
+BEGIN;
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'testResource' );
+	SELECT _create_raw_planets( 5 , 'testPlanet' );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 6 );
+
+	SELECT verse.collect_resprov_statistics( );
+	
+	SELECT diag_test_name( 'verse.collect_resprov_statistics( ) - Temporary table creation' );
+	SELECT has_table( 'rp_stats' );
+
+	SELECT diag_test_name( 'verse.collect_resprov_statistics( ) - Temporary table rows' );
+	SELECT is( COUNT(*)::INT , 2 ) FROM rp_stats;
+
+	SELECT diag_test_name( 'verse.collect_resprov_statistics( ) - rp_stats contains definition data' );
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM (
+			SELECT ( d.natres_quantity_avg = s.quantity_avg
+					AND d.natres_quantity_dev = s.quantity_dev
+					AND d.natres_difficulty_avg = s.difficulty_avg
+					AND d.natres_difficulty_dev = s.difficulty_dev
+					AND d.natres_recovery_avg = s.recovery_avg
+					AND d.natres_recovery_dev = s.recovery_dev
+					AND d.natres_p_presence = s.presence ) AS ok
+				FROM rp_stats s
+					INNER JOIN defs.natural_resources d
+						USING ( resource_name_id )
+			) sq
+		WHERE sq.ok;
+
+	SELECT diag_test_name( 'verse.collect_resprov_statistics( ) - rp_stats contains planet counts' );
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM (
+			SELECT planets = 5 AS ok
+				FROM rp_stats
+			) sq
+		WHERE sq.ok;
+
+	SELECT diag_test_name( 'verse.collect_resprov_statistics( ) - rp_stats sums at 0 with no providers' );
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM (
+			SELECT ( providers = 0 AND quantity = 0
+					AND difficulty = 0 AND recovery = 0 ) AS ok
+				FROM rp_stats
+			) sq
+		WHERE sq.ok;
+
+	DROP TABLE rp_stats;
+	SELECT _create_resource_provider( 'testPlanet1' , 'testResource1' );
+	SELECT verse.collect_resprov_statistics( );
+
+	SELECT diag_test_name( 'verse.collect_resprov_statistics( ) - rp_stats sums' );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM (
+			SELECT ( resource_name_id = _get_string( 'testResource1' )
+					AND providers = 1 AND quantity = 100
+					AND difficulty = 0.5 AND recovery = 0.5 ) AS ok
+				FROM rp_stats
+			) sq
+		WHERE sq.ok;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/020-compute-rpp-delta.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/020-compute-rpp-delta.sql
new file mode 100644
index 0000000..2a14193
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/020-compute-rpp-delta.sql
@@ -0,0 +1,56 @@
+/*
+ * Test the verse.compute_rpp_delta( ) function
+ */
+BEGIN;
+	SELECT plan( 12 );
+	
+	/* First set of tests: results of the function with a fake
+	 * verse.random_deviation() that always returns the minimal possible
+	 * value.
+	 */
+	CREATE OR REPLACE FUNCTION verse.random_deviation( _mean DOUBLE PRECISION , _deviation DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+		STRICT VOLATILE
+	AS $$
+		SELECT $1 - $2;
+	$$ LANGUAGE SQL;
+
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - No existing value, random at minimal value' );
+	SELECT is( verse.compute_rpp_delta( 0 , 10 , 0 , 1 , 0.5 )::NUMERIC , 5.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value below minimum, random at minimal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 0 , 1 , 0.5 )::NUMERIC , 10.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value at minimum, random at minimal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 5 , 1 , 0.5 )::NUMERIC , 5.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value at average, random at minimal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 10 , 1 , 0.5 )::NUMERIC , 5.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value at maximum, random at minimal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 15 , 1 , 0.5 )::NUMERIC , 5.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value over maximum, random at minimal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 20 , 1 , 0.5 )::NUMERIC , 5.0 );
+
+	/* Second set of tests: results of the function with a fake
+	 * verse.random_deviation() that always returns the maximal possible
+	 * value.
+	 */
+	CREATE OR REPLACE FUNCTION verse.random_deviation( _mean DOUBLE PRECISION , _deviation DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+		STRICT VOLATILE
+	AS $$
+		SELECT $1 + $2;
+	$$ LANGUAGE SQL;
+
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - No existing value, random at maximal value' );
+	SELECT is( verse.compute_rpp_delta( 0 , 10 , 0 , 1 , 0.5 )::NUMERIC , 15.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value below minimum, random at maximal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 0 , 1 , 0.5 )::NUMERIC , 15.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value at minimum, random at maximal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 5 , 1 , 0.5 )::NUMERIC , 15.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value at average, random at maximal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 10 , 1 , 0.5 )::NUMERIC , 15.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value at maximum, random at maximal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 15 , 1 , 0.5 )::NUMERIC , 15.0 );
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Existing value over maximum, random at maximal value' );
+	SELECT is( verse.compute_rpp_delta( 10 , 10 , 20 , 1 , 0.5 )::NUMERIC , 10.0 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/030-create-resource-provider.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/030-create-resource-provider.sql
new file mode 100644
index 0000000..68f7eeb
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/030-create-resource-provider.sql
@@ -0,0 +1,62 @@
+/*
+ * Test the verse.create_resource_provider( ) function
+ */
+BEGIN;
+
+	/* Before any actual testing, we need to drop FK constraints on the RP
+	 * table and create a table which will contain the results of the
+	 * function.
+	 */
+
+	ALTER TABLE verse.resource_providers
+		DROP CONSTRAINT fk_resprov_planet ,
+		DROP CONSTRAINT fk_resprov_resource;
+
+	CREATE TEMPORARY TABLE test_result(
+			_providers_left	INT ,
+			_tot_quantity	DOUBLE PRECISION ,
+			_tot_difficulty	DOUBLE PRECISION ,
+			_tot_recovery	DOUBLE PRECISION
+	) ON COMMIT DROP;
+
+	/* Now we call the function using a crafted resource statistics row which
+	 * will make it easy to check the results.
+	 */
+	INSERT INTO test_result
+		SELECT * FROM verse.create_resource_provider( 12 ,
+			ROW( 34 , 0.0 , 0.0 , 0.0 , 0.0 , 5.0 , 0.0 , 0.0 , 0.75 , 0.0 , 0.0 , 0.5 , 0 ) ,
+			2 , 10 , 1.5 , 1 );
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 11 );
+
+
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - Output exists' );
+	SELECT is( COUNT(*)::INT , 1 ) FROM test_result;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - _providers_left updated' );
+	SELECT is( _providers_left , 1 ) FROM test_result;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - _tot_quantity updated' );
+	SELECT is( _tot_quantity::NUMERIC , 5.0 ) FROM test_result;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - _tot_difficulty updated' );
+	SELECT is( _tot_difficulty::NUMERIC , 0.75 ) FROM test_result;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - _tot_recovery updated' );
+	SELECT is( _tot_recovery::NUMERIC , 0.5 ) FROM test_result;
+
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - Resource provider exists' );
+	SELECT is( COUNT(*)::INT , 1 ) FROM verse.resource_providers;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - Resource provider primary key' );
+	SELECT is( COUNT(*)::INT , 1 ) FROM verse.resource_providers
+		WHERE planet_id = 12 AND resource_name_id = 34;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - Resource provider is full' );
+	SELECT is( COUNT(*)::INT , 1 ) FROM verse.resource_providers
+		WHERE resprov_quantity = resprov_quantity_max;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - Resource provider maximal quantity' );
+	SELECT is( resprov_quantity_max::NUMERIC , 5.0 ) FROM verse.resource_providers;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - Resource provider extraction difficulty' );
+	SELECT is( resprov_difficulty::NUMERIC , 0.75 ) FROM verse.resource_providers;
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - Resource provider recovery rate' );
+	SELECT is( resprov_recovery::NUMERIC , 0.5 ) FROM verse.resource_providers;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/040-create-resource-providers-with-type.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/040-create-resource-providers-with-type.sql
new file mode 100644
index 0000000..d491740
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/040-create-resource-providers-with-type.sql
@@ -0,0 +1,129 @@
+/*
+ * Check the variant of verse.create_resource_providers( ) which takes a
+ * resource statistics record as a parameter.
+ */
+BEGIN;
+
+	/* Temporary table which will store information about calls to the
+	 * verse.create_resource_provider( ) function
+	 */
+	CREATE TEMPORARY TABLE test_result(
+		planet		INT ,
+		resource	INT ,
+		quantity	DOUBLE PRECISION ,
+		difficulty	DOUBLE PRECISION ,
+		recovery	DOUBLE PRECISION
+	) ON COMMIT DROP;
+
+	/* Replace verse.list_random_planets_in() with a function that simply
+	 * counts.
+	 */
+	CREATE OR REPLACE FUNCTION verse.list_random_planets_in(
+				_area verse.generator_area_type ,
+				_count INT )
+			RETURNS SETOF INT
+			STRICT VOLATILE
+			SECURITY INVOKER
+		AS $list_random_planets_in$
+	DECLARE
+		i INT;
+	BEGIN
+		FOR i IN 1 .. _count
+		LOOP
+			RETURN NEXT i;
+		END LOOP;
+	END;
+	$list_random_planets_in$ LANGUAGE PLPGSQL;
+	
+	/* Replace verse.create_resource_provider( ) with a function that writes
+	 * to the test_result temporary table.
+	 */
+	CREATE OR REPLACE FUNCTION verse.create_resource_provider(
+			_planet					INT ,
+			_data					verse.resprov_generator_type ,
+			INOUT _providers_left	INT ,
+			INOUT _tot_quantity		DOUBLE PRECISION ,
+			INOUT _tot_difficulty	DOUBLE PRECISION ,
+			INOUT _tot_recovery		DOUBLE PRECISION )
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $create_resource_provider$
+	BEGIN
+		INSERT INTO test_result VALUES(
+			_planet , _data.resource_name_id ,
+			_tot_quantity / _providers_left::DOUBLE PRECISION ,
+			_tot_difficulty / _providers_left::DOUBLE PRECISION ,
+			_tot_recovery / _providers_left::DOUBLE PRECISION
+		);
+		
+		_tot_quantity := ( _providers_left - 1 ) * _tot_quantity / _providers_left;
+		_tot_difficulty := ( _providers_left - 1 ) * _tot_difficulty / _providers_left;
+		_tot_recovery := ( _providers_left - 1 ) * _tot_recovery / _providers_left;
+		_providers_left := _providers_left - 1;
+	END;
+	$create_resource_provider$ LANGUAGE PLPGSQL;
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 11 );
+
+
+	/* First set of test: empty universe */
+	SELECT verse.create_resource_providers(
+		ROW( 0 , 0 , 1 , 1 ) ,
+		ROW( 42 , 0.0 , 0.0 , 0.5 , 0.0 , 5.0 , 0.0 , 0.0 , 0.75 , 0.0 , 0.0 , 0.5 , 0 )
+	);
+
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Empty universe - Providers created' );
+	SELECT is( COUNT(*)::INT , 10 ) FROM test_result WHERE resource = 42;
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Empty universe - Planet identifiers' );
+	SELECT results_eq(
+		'SELECT planet FROM test_result ORDER BY planet' ,
+		ARRAY[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] 
+	);	
+
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Empty universe - Total quantity' );
+	SELECT is( SUM(quantity)::NUMERIC , 50.0 ) FROM test_result;
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Empty universe - Quantity per planet' );
+	SELECT is( COUNT(*)::INT , 10 ) FROM test_result
+		WHERE quantity = 5;
+
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Empty universe - Total difficulty' );
+	SELECT is( SUM(difficulty)::NUMERIC , 7.5 ) FROM test_result;
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Empty universe - Difficulty per planet' );
+	SELECT is( COUNT(*)::INT , 10 ) FROM test_result
+		WHERE difficulty = 0.75;
+
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Empty universe - Total recovery rate' );
+	SELECT is( SUM(recovery)::NUMERIC , 5.0 ) FROM test_result;
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Empty universe - Recovery rate per planet' );
+	SELECT is( COUNT(*)::INT , 10 ) FROM test_result
+		WHERE recovery = 0.5;
+
+
+	/* Second set of tests: balancing providers presence */
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Providers created in balanced universe' );
+	DELETE FROM test_result;
+	SELECT verse.create_resource_providers(
+		ROW( 0 , 0 , 1 , 1 ) ,
+		ROW( 42 , 20.0 , 10.0 , 0.5 , 0.0 , 5.0 , 0.0 , 0.0 , 0.75 , 0.0 , 0.0 , 0.5 , 0 )
+	);
+	SELECT is( COUNT(*)::INT , 10 ) FROM test_result WHERE resource = 42;
+
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Providers created in universe with low providers count' );
+	DELETE FROM test_result;
+	SELECT verse.create_resource_providers(
+		ROW( 0 , 0 , 1 , 1 ) ,
+		ROW( 42 , 20.0 , 5.0 , 0.5 , 0.0 , 5.0 , 0.0 , 0.0 , 0.75 , 0.0 , 0.0 , 0.5 , 0 )
+	);
+	SELECT is( COUNT(*)::INT , 15 ) FROM test_result WHERE resource = 42;
+
+	SELECT diag_test_name( 'verse.create_resource_provider( area , stats ) - Providers created in universe with high providers count' );
+	DELETE FROM test_result;
+	SELECT verse.create_resource_providers(
+		ROW( 0 , 0 , 1 , 1 ) ,
+		ROW( 42 , 20.0 , 15.0 , 0.5 , 0.0 , 5.0 , 0.0 , 0.0 , 0.75 , 0.0 , 0.0 , 0.5 , 0 )
+	);
+	SELECT is( COUNT(*)::INT , 5 ) FROM test_result WHERE resource = 42;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/050-create-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/050-create-resource-providers.sql
new file mode 100644
index 0000000..6c5ea83
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/050-create-resource-providers.sql
@@ -0,0 +1,35 @@
+/*
+ * Test the "main" verse.create_resource_providers( ) function
+ */
+BEGIN;
+
+	/* Create a dummy rp_stats table that contains a few identifiers */
+	 CREATE TEMP TABLE rp_stats
+	 	OF verse.resprov_generator_type
+	 	ON COMMIT DROP;
+	 INSERT INTO rp_stats ( resource_name_id )
+	 	VALUES ( 1 ) , ( 2 ) , ( 3 ) , ( 4 );
+
+	 /* Replace the verse.create_resource_providers( area , type ) function
+	  * with a function that deletes the resource type it was given from
+	  * rp_stats.
+	  */
+	CREATE OR REPLACE FUNCTION verse.create_resource_providers(
+			_area verse.generator_area_type ,
+			_data verse.resprov_generator_type )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $create_resource_providers$
+	BEGIN
+		DELETE FROM rp_stats WHERE resource_name_id = _data.resource_name_id;
+	END;
+	$create_resource_providers$ LANGUAGE PLPGSQL;
+
+	SELECT plan(1);
+	SELECT diag_test_name( 'verse.create_resource_providers( area ) - Calls to per-type function' );
+	SELECT verse.create_resource_providers( ROW( 0 , 0 , 1 , 1 ) );
+	SELECT is( COUNT(*)::INT , 0 ) FROM rp_stats;
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/verse/14500-compute-provider-regeneration.sql b/legacyworlds-server-data/db-structure/tests/admin/functions/145-resource-providers/010-compute-provider-regeneration.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/verse/14500-compute-provider-regeneration.sql
rename to legacyworlds-server-data/db-structure/tests/admin/functions/145-resource-providers/010-compute-provider-regeneration.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/050-computation/010-get-random-part.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/050-computation/010-get-random-part.sql
new file mode 100644
index 0000000..5ce2907
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/050-computation/010-get-random-part.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on verse.get_random_part()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'verse.get_random_part( ) - Privileges' );
+	PREPARE _test_this AS
+		SELECT verse.get_random_part( 0.5 , 1 , 0.5 , 0.25 );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/053-generator-basics/010-list-random-planets-in.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/053-generator-basics/010-list-random-planets-in.sql
new file mode 100644
index 0000000..0f124ca
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/053-generator-basics/010-list-random-planets-in.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on verse.list_random_planets_in()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'verse.list_random_planets_in( ) - Privileges' );
+	PREPARE _test_this AS
+		SELECT * FROM verse.list_random_planets_in( ROW( 2 , 2 , 3 , 3 ) , 2 );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/010-collect-resprov-statistics.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/010-collect-resprov-statistics.sql
new file mode 100644
index 0000000..a47c717
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/010-collect-resprov-statistics.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on verse.collect_resprov_statistics()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'verse.collect_resprov_statistics( ) - Privileges' );
+	PREPARE _test_this AS
+		SELECT verse.collect_resprov_statistics( );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/020-compute-rpp-delta.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/020-compute-rpp-delta.sql
new file mode 100644
index 0000000..f309075
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/020-compute-rpp-delta.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on verse.compute_rpp_delta( )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'verse.compute_rpp_delta( ) - Privileges' );
+	PREPARE _test_this AS
+		SELECT verse.compute_rpp_delta( 10 , 10 , 20 , 1 , 0.5 );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/030-create-resource-provider.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/030-create-resource-provider.sql
new file mode 100644
index 0000000..0c1368f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/030-create-resource-provider.sql
@@ -0,0 +1,16 @@
+/*
+ * Test privileges on verse.create_resource_provider( )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'verse.create_resource_provider( ) - Privileges' );
+	PREPARE _test_this AS
+		SELECT * FROM verse.create_resource_provider( 12 ,
+			ROW( 34 , 0.0 , 0.0 , 0.0 , 0.0 , 5.0 , 0.0 , 0.0 , 0.75 , 0.0 , 0.0 , 0.5 , 0 ) ,
+			2 , 10 , 1.5 , 1 );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/040-create-resource-providers-with-type.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/040-create-resource-providers-with-type.sql
new file mode 100644
index 0000000..5081dd8
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/040-create-resource-providers-with-type.sql
@@ -0,0 +1,18 @@
+/*
+ * Test privileges on the variant of verse.create_resource_providers( ) which
+ * takes a resource statistics record as a parameter.
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'verse.create_resource_providers( area , type ) - Privileges' );
+	PREPARE _test_this AS
+		SELECT verse.create_resource_providers(
+			ROW( 0 , 0 , 1 , 1 ) ,
+			ROW( 42 , 0.0 , 0.0 , 0.5 , 0.0 , 5.0 , 0.0 , 0.0 , 0.75 , 0.0 , 0.0 , 0.5 , 0 )
+		);
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/050-create-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/050-create-resource-providers.sql
new file mode 100644
index 0000000..a1b1cf7
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/050-create-resource-providers.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on the "main" verse.create_resource_providers( ) function
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'verse.create_resource_providers( area ) - Privileges' );
+	PREPARE _test_this AS
+		SELECT verse.create_resource_providers( ROW( 0 , 0 , 1 , 1 ) );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From e50775ec76489b98ac142261fd03bb1f8aecbc0a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Fri, 6 Jan 2012 11:19:19 +0100
Subject: [PATCH 13/94] Database definition & tests organisation

* The main loader script has been updated to generate the list of files
it needs to load automatically. As a consequence, files that contained
manually-maintained lists of scripts have been removed, and definition
directories have been renamed accordingly.

* PostgreSQL extension loading and configuration has been moved to a
separate script to be loaded automatically in the main transaction.

* Data and function definition scripts that had the -data or -functions
suffix have been renamed (the suffix is unnecessary).

* Unit tests have been reorganised to follow the definition's structure.

* Documentation has been improved
---
 .../db-structure/database.sql                 | 44 ++++---------
 .../db-structure/parts/010-data.sql           | 30 ---------
 .../{000-schemas.sql => 010-schemas.sql}      |  0
 .../db-structure/parts/020-extensions.sql     | 23 +++++++
 .../db-structure/parts/020-functions.sql      | 40 ------------
 .../parts/{data => 030-data}/000-typedefs.sql |  0
 .../010-i18n.sql}                             |  0
 .../020-prefs.sql}                            |  0
 .../030-users.sql}                            |  0
 .../035-session.sql}                          |  0
 .../040-admin.sql}                            |  0
 .../050-accounts.sql}                         |  0
 .../055-bugs.sql}                             |  0
 .../060-naming.sql}                           |  0
 .../070-constants.sql}                        |  0
 .../075-resources.sql}                        |  0
 .../080-techs.sql}                            |  0
 .../090-buildables.sql}                       |  0
 .../100-universe.sql}                         |  0
 .../110-empires.sql}                          |  0
 .../120-construction.sql}                     |  0
 .../130-fleets.sql}                           |  0
 .../140-status.sql}                           |  0
 .../150-logs.sql}                             |  0
 .../160-battle.sql}                           |  0
 .../170-events.sql}                           |  0
 .../180-messages.sql}                         |  0
 .../db-structure/parts/030-updates.sql        | 23 -------
 .../000-defs.sql}                             |  0
 .../002-sys.sql}                              |  0
 .../005-logs.sql}                             |  0
 .../010-constants.sql}                        |  0
 .../020-naming.sql}                           |  0
 .../025-resources.sql}                        |  0
 .../030-tech.sql}                             |  0
 .../035-users.sql}                            |  0
 .../040-empire.sql}                           |  0
 .../050-computation.sql}                      |  0
 .../053-generator-basics.sql                  |  0
 .../055-generator-resources.sql               |  0
 .../060-universe.sql}                         |  0
 .../070-users.sql}                            |  0
 .../075-session.sql}                          |  0
 .../080-buildings.sql}                        |  0
 .../100-status.sql}                           |  0
 .../110-prefs.sql}                            |  0
 .../120-map.sql}                              |  0
 .../140-planets.sql}                          |  0
 .../145-resource-providers.sql}               |  0
 .../150-battle.sql}                           |  0
 .../160-battle-views.sql                      |  0
 .../163-alliance.sql}                         |  0
 .../165-fleets.sql}                           |  0
 .../167-planet-list.sql                       |  0
 .../170-events.sql}                           |  0
 .../180-messages.sql}                         |  0
 .../190-admin.sql}                            |  0
 .../200-bugs.sql}                             |  0
 .../210-admin-overview.sql                    |  0
 .../000-updates-ctrl.sql                      |  0
 .../010-empire-money.sql                      |  0
 .../020-empire-research.sql                   |  0
 .../025-empire-debt.sql                       |  0
 .../030-fleet-arrivals.sql                    |  0
 .../040-fleet-movements.sql                   |  0
 .../050-fleet-status.sql                      |  0
 .../060-planet-battle.sql                     |  0
 .../070-planet-abandon.sql                    |  0
 .../080-planet-construction.sql               |  0
 .../090-planet-military.sql                   |  0
 .../100-planet-population.sql                 |  0
 .../105-planet-resource-regeneration.sql      |  0
 .../110-planet-money.sql                      |  0
 .../075-resources/010-resources.sql}          |  0
 .../075-resources/020-natural-resources.sql}  |  0
 .../100-universe/010-resource-providers.sql}  |  0
 .../110-empires/010-empire-resources.sql}     |  0
 .../020-empire-mining-settings.sql}           |  0
 .../030-empire-planet-mining-settings.sql}    |  0
 .../010-uoc-resource-internal.sql             |  0
 .../025-resources/020-uoc-resource.sql        |  0
 .../025-resources/030-uoc-natres-internal.sql |  0
 .../040-uoc-natural-resource.sql              |  0
 .../050-computation/010-get-random-part.sql   |  0
 .../010-list-random-planets-in.sql            |  0
 .../010-collect-resprov-statistics.sql        |  0
 .../020-compute-rpp-delta.sql                 |  0
 .../030-create-resource-provider.sql          |  0
 ...40-create-resource-providers-with-type.sql |  0
 .../050-create-resource-providers.sql         |  0
 .../010-compute-provider-regeneration.sql     |  0
 .../010-process-planet-res-regen-updates.sql} |  0
 .../075-resources/010-defs-resources.sql}     |  0
 .../020-defs-natural-resources.sql}           |  0
 .../010-verse-resource-providers.sql}         |  0
 .../110-empires/010-empire-resources.sql}     |  0
 .../020-empire-mining-settings.sql}           |  0
 .../030-empire-planet-mining-settings.sql}    |  0
 .../010-uoc-resource-internal.sql}            |  0
 .../025-resources/020-uoc-resource.sql}       |  0
 .../030-uoc-natres-internal.sql}              |  0
 .../040-uoc-natural-resource.sql}             |  0
 .../050-computation/010-get-random-part.sql   |  0
 .../010-list-random-planets-in.sql            |  0
 .../010-collect-resprov-statistics.sql        |  0
 .../020-compute-rpp-delta.sql                 |  0
 .../030-create-resource-provider.sql          |  0
 ...40-create-resource-providers-with-type.sql |  0
 .../050-create-resource-providers.sql         |  0
 .../010-compute-provider-regeneration.sql}    |  0
 .../010-process-planet-res-regen-updates.sql} |  0
 legacyworlds/doc/database.txt                 | 62 +++++++++++++------
 112 files changed, 78 insertions(+), 144 deletions(-)
 delete mode 100644 legacyworlds-server-data/db-structure/parts/010-data.sql
 rename legacyworlds-server-data/db-structure/parts/{000-schemas.sql => 010-schemas.sql} (100%)
 create mode 100644 legacyworlds-server-data/db-structure/parts/020-extensions.sql
 delete mode 100644 legacyworlds-server-data/db-structure/parts/020-functions.sql
 rename legacyworlds-server-data/db-structure/parts/{data => 030-data}/000-typedefs.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/010-i18n-data.sql => 030-data/010-i18n.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/020-prefs-data.sql => 030-data/020-prefs.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/030-users-data.sql => 030-data/030-users.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/035-session-data.sql => 030-data/035-session.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/040-admin-data.sql => 030-data/040-admin.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/050-accounts-data.sql => 030-data/050-accounts.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/055-bugs-data.sql => 030-data/055-bugs.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/060-naming-data.sql => 030-data/060-naming.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/070-constants-data.sql => 030-data/070-constants.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/075-resources-data.sql => 030-data/075-resources.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/080-techs-data.sql => 030-data/080-techs.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/090-buildables-data.sql => 030-data/090-buildables.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/100-universe-data.sql => 030-data/100-universe.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/110-empires-data.sql => 030-data/110-empires.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/120-construction-data.sql => 030-data/120-construction.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/130-fleets-data.sql => 030-data/130-fleets.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/140-status-data.sql => 030-data/140-status.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/150-logs-data.sql => 030-data/150-logs.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/160-battle-data.sql => 030-data/160-battle.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/170-events-data.sql => 030-data/170-events.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{data/180-messages-data.sql => 030-data/180-messages.sql} (100%)
 delete mode 100644 legacyworlds-server-data/db-structure/parts/030-updates.sql
 rename legacyworlds-server-data/db-structure/parts/{functions/000-defs-functions.sql => 040-functions/000-defs.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/002-sys-functions.sql => 040-functions/002-sys.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/005-logs-functions.sql => 040-functions/005-logs.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/010-constants-functions.sql => 040-functions/010-constants.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/020-naming-functions.sql => 040-functions/020-naming.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/025-resources-functions.sql => 040-functions/025-resources.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/030-tech-functions.sql => 040-functions/030-tech.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/035-users-view.sql => 040-functions/035-users.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/040-empire-functions.sql => 040-functions/040-empire.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/050-computation-functions.sql => 040-functions/050-computation.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions => 040-functions}/053-generator-basics.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions => 040-functions}/055-generator-resources.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/060-universe-functions.sql => 040-functions/060-universe.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/070-users-functions.sql => 040-functions/070-users.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/075-session-functions.sql => 040-functions/075-session.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/080-buildings-functions.sql => 040-functions/080-buildings.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/100-status-functions.sql => 040-functions/100-status.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/110-prefs-functions.sql => 040-functions/110-prefs.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/120-map-functions.sql => 040-functions/120-map.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/140-planets-functions.sql => 040-functions/140-planets.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/145-resource-providers-functions.sql => 040-functions/145-resource-providers.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/150-battle-functions.sql => 040-functions/150-battle.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions => 040-functions}/160-battle-views.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/163-alliance-functions.sql => 040-functions/163-alliance.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/165-fleets-functions.sql => 040-functions/165-fleets.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions => 040-functions}/167-planet-list.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/170-event-functions.sql => 040-functions/170-events.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/180-messages-functions.sql => 040-functions/180-messages.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/190-admin-functions.sql => 040-functions/190-admin.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions/200-bugs-functions.sql => 040-functions/200-bugs.sql} (100%)
 rename legacyworlds-server-data/db-structure/parts/{functions => 040-functions}/210-admin-overview.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/000-updates-ctrl.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/010-empire-money.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/020-empire-research.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/025-empire-debt.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/030-fleet-arrivals.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/040-fleet-movements.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/050-fleet-status.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/060-planet-battle.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/070-planet-abandon.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/080-planet-construction.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/090-planet-military.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/100-planet-population.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/105-planet-resource-regeneration.sql (100%)
 rename legacyworlds-server-data/db-structure/parts/{updates => 050-updates}/110-planet-money.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{data/07500-resources.sql => 030-data/075-resources/010-resources.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{data/07501-natural-resources.sql => 030-data/075-resources/020-natural-resources.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{data/10003-resource-providers.sql => 030-data/100-universe/010-resource-providers.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{data/11001-empire-resources.sql => 030-data/110-empires/010-empire-resources.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{data/11002-empire-mining-settings.sql => 030-data/110-empires/020-empire-mining-settings.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{data/11003-empire-planet-mining-settings.sql => 030-data/110-empires/030-empire-planet-mining-settings.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/025-resources/010-uoc-resource-internal.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/025-resources/020-uoc-resource.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/025-resources/030-uoc-natres-internal.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/025-resources/040-uoc-natural-resource.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/050-computation/010-get-random-part.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/053-generator-basics/010-list-random-planets-in.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/055-generator-resources/010-collect-resprov-statistics.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/055-generator-resources/020-compute-rpp-delta.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/055-generator-resources/030-create-resource-provider.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/055-generator-resources/040-create-resource-providers-with-type.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/055-generator-resources/050-create-resource-providers.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{functions => 040-functions}/145-resource-providers/010-compute-provider-regeneration.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/{updates/10500-process-planet-res-regen-updates.sql => 050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/data/07500-defs-resources.sql => 030-data/075-resources/010-defs-resources.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/data/07501-defs-natural-resources.sql => 030-data/075-resources/020-defs-natural-resources.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/data/10003-verse-resource-providers.sql => 030-data/100-universe/010-verse-resource-providers.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/data/11001-empire-resources.sql => 030-data/110-empires/010-empire-resources.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/data/11002-empire-mining-settings.sql => 030-data/110-empires/020-empire-mining-settings.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/data/11003-empire-planet-mining-settings.sql => 030-data/110-empires/030-empire-planet-mining-settings.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/02500-uoc-resource-internal.sql => 040-functions/025-resources/010-uoc-resource-internal.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/02501-uoc-resource.sql => 040-functions/025-resources/020-uoc-resource.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/02502-uoc-natres-internal.sql => 040-functions/025-resources/030-uoc-natres-internal.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/02503-uoc-natural-resource.sql => 040-functions/025-resources/040-uoc-natural-resource.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions => 040-functions}/050-computation/010-get-random-part.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions => 040-functions}/053-generator-basics/010-list-random-planets-in.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/055-generator-resources.sql => 040-functions/055-generator-resources}/010-collect-resprov-statistics.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/055-generator-resources.sql => 040-functions/055-generator-resources}/020-compute-rpp-delta.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/055-generator-resources.sql => 040-functions/055-generator-resources}/030-create-resource-provider.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/055-generator-resources.sql => 040-functions/055-generator-resources}/040-create-resource-providers-with-type.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/055-generator-resources.sql => 040-functions/055-generator-resources}/050-create-resource-providers.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/functions/14500-compute-provider-regeneration.sql => 040-functions/145-resource-providers/010-compute-provider-regeneration.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/{priv/updates/10500-process-planet-res-regen-updates.sql => 050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql} (100%)

diff --git a/legacyworlds-server-data/db-structure/database.sql b/legacyworlds-server-data/db-structure/database.sql
index 7b8f951..50b7913 100644
--- a/legacyworlds-server-data/db-structure/database.sql
+++ b/legacyworlds-server-data/db-structure/database.sql
@@ -1,12 +1,12 @@
 -- LegacyWorlds Beta 6
 -- PostgreSQL database scripts
 --
--- Main database script
+-- Main database loader script
 --
 -- Initialises the various roles and the database itself,
 -- then processes scripts from the "parts" directory.
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2012, DeepClone Development
 -- --------------------------------------------------------
 
 -- Read configuration from file
@@ -18,6 +18,12 @@
 \set dbupass ''''`grep ^password= db-config.txt | sed -e s/.*=// -e "s/'/''/g"`''''
 
 
+/* List all files in the "parts" directory and create a loader script from
+ * that.
+ */
+\! find parts -type f -name '*.sql' | sort | sed -e 's/^/\\i /' > loader.tmp
+
+
 -- Connect to the main system database
 \c postgres :pgadmin
 
@@ -36,34 +42,10 @@ GRANT CONNECT ON DATABASE :dbname TO :dbuser;
 -- Connect to the LW database with the PostgreSQL admin user
 \c :dbname :pgadmin
 
--- Register the dblink extension
-CREATE EXTENSION dblink;
-
--- Create foreign data wrapper and server used to write logs from within
--- transanctions
-CREATE FOREIGN DATA WRAPPER pgsql
-	VALIDATOR postgresql_fdw_validator;
-CREATE SERVER srv_logging
-	FOREIGN DATA WRAPPER pgsql
-	OPTIONS ( hostaddr '127.0.0.1' , dbname :dbname_string );
-CREATE USER MAPPING FOR :dbuser
-	SERVER srv_logging
-	OPTIONS ( user :dbuser_string , password :dbupass );
-GRANT USAGE ON FOREIGN SERVER srv_logging TO :dbuser;
-
-
+/* Load everything from the loader script */
 BEGIN;
+\i loader.tmp
+COMMIT;
 
--- Create database schemas
-\i parts/000-schemas.sql
-
--- Process structure definition scripts
-\i parts/010-data.sql
-
--- Process functions and views definition scripts
-\i parts/020-functions.sql
-
--- Process game update functions
-\i parts/030-updates.sql
-
-COMMIT;
\ No newline at end of file
+/* Delete loader script */
+\! rm loader.tmp
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/010-data.sql b/legacyworlds-server-data/db-structure/parts/010-data.sql
deleted file mode 100644
index f5e52b7..0000000
--- a/legacyworlds-server-data/db-structure/parts/010-data.sql
+++ /dev/null
@@ -1,30 +0,0 @@
--- LegacyWorlds Beta 6
--- PostgreSQL database scripts
---
--- Structures creation
---
--- Copyright(C) 2004-2010, DeepClone Development
--- --------------------------------------------------------
-
-\i parts/data/000-typedefs.sql
-\i parts/data/010-i18n-data.sql
-\i parts/data/020-prefs-data.sql
-\i parts/data/030-users-data.sql
-\i parts/data/035-session-data.sql
-\i parts/data/040-admin-data.sql
-\i parts/data/050-accounts-data.sql
-\i parts/data/055-bugs-data.sql
-\i parts/data/060-naming-data.sql
-\i parts/data/070-constants-data.sql
-\i parts/data/075-resources-data.sql
-\i parts/data/080-techs-data.sql
-\i parts/data/090-buildables-data.sql
-\i parts/data/100-universe-data.sql
-\i parts/data/110-empires-data.sql
-\i parts/data/120-construction-data.sql
-\i parts/data/130-fleets-data.sql
-\i parts/data/140-status-data.sql
-\i parts/data/150-logs-data.sql
-\i parts/data/160-battle-data.sql
-\i parts/data/170-events-data.sql
-\i parts/data/180-messages-data.sql
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/000-schemas.sql b/legacyworlds-server-data/db-structure/parts/010-schemas.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/000-schemas.sql
rename to legacyworlds-server-data/db-structure/parts/010-schemas.sql
diff --git a/legacyworlds-server-data/db-structure/parts/020-extensions.sql b/legacyworlds-server-data/db-structure/parts/020-extensions.sql
new file mode 100644
index 0000000..0889c1d
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/020-extensions.sql
@@ -0,0 +1,23 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Load and configure required PostgreSQL extensions
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+/* Register the dblink extension */
+CREATE EXTENSION dblink;
+
+/* Create foreign data wrapper and server used to write logs from within
+ * transanctions
+ */
+CREATE FOREIGN DATA WRAPPER pgsql
+	VALIDATOR postgresql_fdw_validator;
+CREATE SERVER srv_logging
+	FOREIGN DATA WRAPPER pgsql
+	OPTIONS ( hostaddr '127.0.0.1' , dbname :dbname_string );
+CREATE USER MAPPING FOR :dbuser
+	SERVER srv_logging
+	OPTIONS ( user :dbuser_string , password :dbupass );
+GRANT USAGE ON FOREIGN SERVER srv_logging TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/parts/020-functions.sql b/legacyworlds-server-data/db-structure/parts/020-functions.sql
deleted file mode 100644
index 027e727..0000000
--- a/legacyworlds-server-data/db-structure/parts/020-functions.sql
+++ /dev/null
@@ -1,40 +0,0 @@
--- LegacyWorlds Beta 6
--- PostgreSQL database scripts
---
--- Functions and views
---
--- Copyright(C) 2004-2010, DeepClone Development
--- --------------------------------------------------------
-
-\i parts/functions/000-defs-functions.sql
-\i parts/functions/002-sys-functions.sql
-\i parts/functions/005-logs-functions.sql
-\i parts/functions/010-constants-functions.sql
-\i parts/functions/020-naming-functions.sql
-\i parts/functions/025-resources-functions.sql
-\i parts/functions/030-tech-functions.sql
-\i parts/functions/035-users-view.sql
-\i parts/functions/040-empire-functions.sql
-\i parts/functions/050-computation-functions.sql
-\i parts/functions/053-generator-basics.sql
-\i parts/functions/055-generator-resources.sql
-\i parts/functions/060-universe-functions.sql
-\i parts/functions/070-users-functions.sql
-\i parts/functions/075-session-functions.sql
-\i parts/functions/080-buildings-functions.sql
-\i parts/functions/100-status-functions.sql
-\i parts/functions/110-prefs-functions.sql
-\i parts/functions/120-map-functions.sql
-\i parts/functions/140-planets-functions.sql
-\i parts/functions/145-resource-providers-functions.sql
-\i parts/functions/150-battle-functions.sql
-\i parts/functions/160-battle-views.sql
-\i parts/functions/163-alliance-functions.sql
-\i parts/functions/165-fleets-functions.sql
-\i parts/functions/167-planet-list.sql
-\i parts/functions/170-event-functions.sql
-\i parts/functions/180-messages-functions.sql
-\i parts/functions/190-admin-functions.sql
-\i parts/functions/200-bugs-functions.sql
-\i parts/functions/210-admin-overview.sql
-
diff --git a/legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql b/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/000-typedefs.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/010-i18n-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/010-i18n.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/010-i18n-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/010-i18n.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/020-prefs-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/020-prefs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/020-prefs-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/020-prefs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/030-users-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/030-users.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/030-users-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/030-users.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/035-session-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/035-session.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/035-session-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/035-session.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/040-admin-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/040-admin.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/040-admin-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/040-admin.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/050-accounts-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/050-accounts.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/050-accounts-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/050-accounts.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/055-bugs-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/055-bugs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/055-bugs-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/055-bugs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/060-naming-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/060-naming.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/060-naming-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/060-naming.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/070-constants.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/070-constants.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/075-resources-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/075-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/075-resources-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/075-resources.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/120-construction.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/120-construction.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/130-fleets.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/130-fleets.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/140-status-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/140-status-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/140-status.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/150-logs-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/150-logs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/150-logs-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/150-logs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/160-battle-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/160-battle.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/160-battle-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/160-battle.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/170-events-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/170-events-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
diff --git a/legacyworlds-server-data/db-structure/parts/data/180-messages-data.sql b/legacyworlds-server-data/db-structure/parts/030-data/180-messages.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/data/180-messages-data.sql
rename to legacyworlds-server-data/db-structure/parts/030-data/180-messages.sql
diff --git a/legacyworlds-server-data/db-structure/parts/030-updates.sql b/legacyworlds-server-data/db-structure/parts/030-updates.sql
deleted file mode 100644
index 12efd35..0000000
--- a/legacyworlds-server-data/db-structure/parts/030-updates.sql
+++ /dev/null
@@ -1,23 +0,0 @@
--- LegacyWorlds Beta 6
--- PostgreSQL database scripts
---
--- Game updates
---
--- Copyright(C) 2004-2010, DeepClone Development
--- --------------------------------------------------------
-
-
-\i parts/updates/000-updates-ctrl.sql
-\i parts/updates/010-empire-money.sql
-\i parts/updates/020-empire-research.sql
-\i parts/updates/025-empire-debt.sql
-\i parts/updates/030-fleet-arrivals.sql
-\i parts/updates/040-fleet-movements.sql
-\i parts/updates/050-fleet-status.sql
-\i parts/updates/060-planet-battle.sql
-\i parts/updates/070-planet-abandon.sql
-\i parts/updates/080-planet-construction.sql
-\i parts/updates/090-planet-military.sql
-\i parts/updates/100-planet-population.sql
-\i parts/updates/105-planet-resource-regeneration.sql
-\i parts/updates/110-planet-money.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/000-defs-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/000-defs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/000-defs-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/000-defs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/002-sys.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/002-sys-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/002-sys.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/005-logs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/005-logs-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/005-logs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/010-constants.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/010-constants.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/020-naming-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/020-naming.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/020-naming-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/020-naming.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/025-resources-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/035-users-view.sql b/legacyworlds-server-data/db-structure/parts/040-functions/035-users.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/035-users-view.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/035-users.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/050-computation.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/050-computation.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/053-generator-basics.sql b/legacyworlds-server-data/db-structure/parts/040-functions/053-generator-basics.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/053-generator-basics.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/053-generator-basics.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/055-generator-resources.sql b/legacyworlds-server-data/db-structure/parts/040-functions/055-generator-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/055-generator-resources.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/055-generator-resources.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/070-users.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/070-users-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/070-users.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/075-session-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/075-session.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/075-session-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/075-session.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/080-buildings-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/080-buildings.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/080-buildings-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/080-buildings.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/100-status-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/100-status.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/100-status-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/100-status.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/110-prefs-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/110-prefs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/110-prefs-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/110-prefs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/120-map-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/120-map.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/120-map-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/120-map.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/145-resource-providers-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/145-resource-providers-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/150-battle.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/150-battle.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql b/legacyworlds-server-data/db-structure/parts/040-functions/160-battle-views.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/160-battle-views.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/163-alliance-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/163-alliance.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/163-alliance-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/163-alliance.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/165-fleets.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/165-fleets.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql b/legacyworlds-server-data/db-structure/parts/040-functions/167-planet-list.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/167-planet-list.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/180-messages.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/180-messages-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/180-messages.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/190-admin.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/190-admin-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/190-admin.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
diff --git a/legacyworlds-server-data/db-structure/parts/functions/210-admin-overview.sql b/legacyworlds-server-data/db-structure/parts/040-functions/210-admin-overview.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/functions/210-admin-overview.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/210-admin-overview.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql b/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql b/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql b/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/030-fleet-arrivals.sql b/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/030-fleet-arrivals.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/040-fleet-movements.sql b/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/040-fleet-movements.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/050-fleet-status.sql b/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/050-fleet-status.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql b/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/070-planet-abandon.sql b/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/070-planet-abandon.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql b/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql b/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql b/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/105-planet-resource-regeneration.sql b/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/105-planet-resource-regeneration.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql
diff --git a/legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql b/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql
rename to legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/data/07500-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/075-resources/010-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/data/07500-resources.sql
rename to legacyworlds-server-data/db-structure/tests/admin/030-data/075-resources/010-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/data/07501-natural-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/075-resources/020-natural-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/data/07501-natural-resources.sql
rename to legacyworlds-server-data/db-structure/tests/admin/030-data/075-resources/020-natural-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/data/10003-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/010-resource-providers.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/data/10003-resource-providers.sql
rename to legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/010-resource-providers.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/data/11001-empire-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/010-empire-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/data/11001-empire-resources.sql
rename to legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/010-empire-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/data/11002-empire-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/020-empire-mining-settings.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/data/11002-empire-mining-settings.sql
rename to legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/020-empire-mining-settings.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/data/11003-empire-planet-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/030-empire-planet-mining-settings.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/data/11003-empire-planet-mining-settings.sql
rename to legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/030-empire-planet-mining-settings.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/010-uoc-resource-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/010-uoc-resource-internal.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/020-uoc-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/020-uoc-resource.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/030-uoc-natres-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/030-uoc-natres-internal.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/040-uoc-natural-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/025-resources/040-uoc-natural-resource.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/050-computation/010-get-random-part.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/010-get-random-part.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/050-computation/010-get-random-part.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/010-get-random-part.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/053-generator-basics/010-list-random-planets-in.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/053-generator-basics/010-list-random-planets-in.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/053-generator-basics/010-list-random-planets-in.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/053-generator-basics/010-list-random-planets-in.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/010-collect-resprov-statistics.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/010-collect-resprov-statistics.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/010-collect-resprov-statistics.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/010-collect-resprov-statistics.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/020-compute-rpp-delta.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/020-compute-rpp-delta.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/020-compute-rpp-delta.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/020-compute-rpp-delta.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/030-create-resource-provider.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/030-create-resource-provider.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/030-create-resource-provider.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/030-create-resource-provider.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/040-create-resource-providers-with-type.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/040-create-resource-providers-with-type.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/040-create-resource-providers-with-type.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/040-create-resource-providers-with-type.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/050-create-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/050-create-resource-providers.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/055-generator-resources/050-create-resource-providers.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/055-generator-resources/050-create-resource-providers.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/functions/145-resource-providers/010-compute-provider-regeneration.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/010-compute-provider-regeneration.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/functions/145-resource-providers/010-compute-provider-regeneration.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/010-compute-provider-regeneration.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/updates/10500-process-planet-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/updates/10500-process-planet-res-regen-updates.sql
rename to legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/07500-defs-resources.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/075-resources/010-defs-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/data/07500-defs-resources.sql
rename to legacyworlds-server-data/db-structure/tests/user/030-data/075-resources/010-defs-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/07501-defs-natural-resources.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/075-resources/020-defs-natural-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/data/07501-defs-natural-resources.sql
rename to legacyworlds-server-data/db-structure/tests/user/030-data/075-resources/020-defs-natural-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/10003-verse-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/010-verse-resource-providers.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/data/10003-verse-resource-providers.sql
rename to legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/010-verse-resource-providers.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/11001-empire-resources.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/110-empires/010-empire-resources.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/data/11001-empire-resources.sql
rename to legacyworlds-server-data/db-structure/tests/user/030-data/110-empires/010-empire-resources.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/11002-empire-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/110-empires/020-empire-mining-settings.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/data/11002-empire-mining-settings.sql
rename to legacyworlds-server-data/db-structure/tests/user/030-data/110-empires/020-empire-mining-settings.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/data/11003-empire-planet-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/110-empires/030-empire-planet-mining-settings.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/data/11003-empire-planet-mining-settings.sql
rename to legacyworlds-server-data/db-structure/tests/user/030-data/110-empires/030-empire-planet-mining-settings.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/02500-uoc-resource-internal.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/010-uoc-resource-internal.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/02500-uoc-resource-internal.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/010-uoc-resource-internal.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/02501-uoc-resource.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/020-uoc-resource.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/02501-uoc-resource.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/020-uoc-resource.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/02502-uoc-natres-internal.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/030-uoc-natres-internal.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/02502-uoc-natres-internal.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/030-uoc-natres-internal.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/02503-uoc-natural-resource.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/040-uoc-natural-resource.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/02503-uoc-natural-resource.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/040-uoc-natural-resource.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/050-computation/010-get-random-part.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/010-get-random-part.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/050-computation/010-get-random-part.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/010-get-random-part.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/053-generator-basics/010-list-random-planets-in.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/053-generator-basics/010-list-random-planets-in.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/053-generator-basics/010-list-random-planets-in.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/053-generator-basics/010-list-random-planets-in.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/010-collect-resprov-statistics.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/010-collect-resprov-statistics.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/010-collect-resprov-statistics.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/010-collect-resprov-statistics.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/020-compute-rpp-delta.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/020-compute-rpp-delta.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/020-compute-rpp-delta.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/020-compute-rpp-delta.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/030-create-resource-provider.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/030-create-resource-provider.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/030-create-resource-provider.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/030-create-resource-provider.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/040-create-resource-providers-with-type.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/040-create-resource-providers-with-type.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/040-create-resource-providers-with-type.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/040-create-resource-providers-with-type.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/050-create-resource-providers.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/050-create-resource-providers.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/055-generator-resources.sql/050-create-resource-providers.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/055-generator-resources/050-create-resource-providers.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/functions/14500-compute-provider-regeneration.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/010-compute-provider-regeneration.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/functions/14500-compute-provider-regeneration.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/010-compute-provider-regeneration.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/updates/10500-process-planet-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/priv/updates/10500-process-planet-res-regen-updates.sql
rename to legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
diff --git a/legacyworlds/doc/database.txt b/legacyworlds/doc/database.txt
index b2765e1..4d035a4 100644
--- a/legacyworlds/doc/database.txt
+++ b/legacyworlds/doc/database.txt
@@ -35,32 +35,52 @@ The root directory includes a "database.sql" script, which can be launched in
 psql to create the database and associated user.
 
 The parts/ sub-directory contains the various elements of the database's
-definition. It contains a few scripts, which will initialise the schemas and
-load other scripts from the sub-directories.
+definition.
 
- * parts/data/ contains all data structure definitions (types, tables,
-   indexes, constraints and some of the views).
- * parts/functions/ contains all general function definitions; this includes
-   both functions that are called internally and functions which are called
-   by the server. It also includes view definitions that depend on functions.
- * parts/updates/ contains all functions that implement the game's updates.
+All SQL files contained in this directory, as well as all sub-directories and
+their contents, are prefixed with some number which serves as an indication of
+the order in which the scripts are to be loaded. Numbering in a directory
+should start with 010 and increase by 10 for each file in the sequence.
+However, when adding new code, it is possible to add new files with
+intermediary values.
 
- The tests/ sub-directory contains the SQL source code for the pgTAP testing
- framework as well as the tests themselves. See below for more information.
+The parts/ sub-directory contains the following elements:
+ * schema and extension loaders,
+ * the xxx-data/ sub-directory which contains most data structure definitions
+   (types, tables, indexes, constraints and some of the views).
+ * the xxx-functions/ sub-directory which contains all general function
+   definitions; this includes both functions that are called internally and
+   functions which are called by the server. It also includes view definitions
+   that depend on functions or are used only by functions, as well as type
+   definitions used to pass values between functions or as return values.
+ * the xxx-updates/ sub-directory which contains all functions that implement
+   the game's updates.
+
+The tests/ sub-directory contains the SQL source code for the pgTAP testing
+framework as well as the tests themselves. See below for more information.
  
  
  Unit tests
  ----------
  
- There may be up to two sub-directories in the tests/ directory. The user/
- sub-directory would contain unit tests that must be executed as the standard
- user, while the admin/ directory would contain tests that required
- administrative permissions on the database.
+There are three sub-directories in the tests/ directory. The admin/ directory
+contains tests that require administrative permissions on the database
+(therefore most functionality checks can be found there), while the user/
+sub-directory contains unit tests that must be executed as the standard
+user (for example privileges checks). Finally, the utils/ sub-directory 
+contains scripts used by tests from the admin/ directory to create test
+data.
  
- In order to run the database unit tests, the following steps must be taken:
+In both directories, files are organised in a manner that is parallel to the
+contents of the database creation scripts. For each actual SQL file, a
+sub-directory with the same name (minus the ".sql" extension) can be created,
+each sub-directory containing the test suites for the definitions and
+functions from the corresponding file.
  
- 1) pg_prove must be installed. This can be achieved by running the following
-    command as root:
+In order to run the database unit tests, the following steps must be taken:
+ 
+1) pg_prove must be installed. This can be achieved by running the following
+   command as root:
  
  		 cpan TAP::Parser::SourceHandler::pgTAP
 
@@ -76,7 +96,8 @@ load other scripts from the sub-directories.
 At this point, it becomes possible to launch the test suites by issuing a
 command similar to:
 
-		pg_prove -d $DATABASE -U $USER `find $DIR/ -type f -name '*.sql'`
+		pg_prove -d $DATABASE -U $USER \
+				`find $DIR/ -type f -name '*.sql' | sort`
 
 where $DATABASE is the name of the database, $USER the name of the user that
 will execute the tests and $DIR being either admin or user.
@@ -87,5 +108,6 @@ Build system
 
 The build system will attempt to create the database using the scripts. It will
 stop at the first unsuccessful command. On success, it will proceed to loading
-pgTAP, then run all available unit tests. A failure will cause the build to be
-aborted. 
\ No newline at end of file
+pgTAP, then run all available unit tests: first it will execute tests from the
+admin/ sub-directory, then tests from the user/ sub-directory. A failure will
+cause the build to be aborted. 
\ No newline at end of file

From 3e109b13bcbf7fe415d13902bb6d5433696914ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 7 Jan 2012 11:14:17 +0100
Subject: [PATCH 14/94] SQL logging fixes

* Added user mapping on the "remote" logging database for the
administrative user. This allows calls to sys.write_sql_log() to succeed
when they are executed by code with administrative privileges.

* Added test suites for both the link to the database and the function
itself.

* Replaced RAISE NOTICE with actual logging in the universe generator
---
 .../db-structure/parts/020-extensions.sql     |  7 ++++
 .../parts/040-functions/005-logs.sql          | 39 ++++++++++--------
 .../040-functions/055-generator-resources.sql | 19 +++++----
 .../tests/admin/020-extensions/010-dblink.sql | 40 +++++++++++++++++++
 .../005-logs/010-write-sql-log.sql            | 32 +++++++++++++++
 .../tests/user/020-extensions/010-dblink.sql  | 23 +++++++++++
 .../user/030-data/150-logs/010-sys-logs.sql   | 29 ++++++++++++++
 .../005-logs/010-write-sql-log.sql            | 13 ++++++
 .../db-structure/tests/user/priv/sys/logs.sql | 39 ------------------
 9 files changed, 177 insertions(+), 64 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/020-extensions/010-dblink.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/005-logs/010-write-sql-log.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/020-extensions/010-dblink.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/030-data/150-logs/010-sys-logs.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/005-logs/010-write-sql-log.sql
 delete mode 100644 legacyworlds-server-data/db-structure/tests/user/priv/sys/logs.sql

diff --git a/legacyworlds-server-data/db-structure/parts/020-extensions.sql b/legacyworlds-server-data/db-structure/parts/020-extensions.sql
index 0889c1d..9adc475 100644
--- a/legacyworlds-server-data/db-structure/parts/020-extensions.sql
+++ b/legacyworlds-server-data/db-structure/parts/020-extensions.sql
@@ -14,10 +14,17 @@ CREATE EXTENSION dblink;
  */
 CREATE FOREIGN DATA WRAPPER pgsql
 	VALIDATOR postgresql_fdw_validator;
+
 CREATE SERVER srv_logging
 	FOREIGN DATA WRAPPER pgsql
 	OPTIONS ( hostaddr '127.0.0.1' , dbname :dbname_string );
+
+CREATE USER MAPPING FOR :pgadmin
+	SERVER srv_logging
+	OPTIONS ( user :dbuser_string , password :dbupass );
+
 CREATE USER MAPPING FOR :dbuser
 	SERVER srv_logging
 	OPTIONS ( user :dbuser_string , password :dbupass );
+
 GRANT USAGE ON FOREIGN SERVER srv_logging TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/005-logs.sql b/legacyworlds-server-data/db-structure/parts/040-functions/005-logs.sql
index 65bd5b7..0579cda 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/005-logs.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/005-logs.sql
@@ -124,21 +124,21 @@ $$ LANGUAGE plpgsql;
 GRANT EXECUTE ON FUNCTION sys.write_log( TEXT , log_level , TEXT ) TO :dbuser;
 
 
---
--- Remotely append a system log entry
---
--- This function is called from within transactions in order to write to the
--- system log. Unlike sys.write_log(), entries added through this function
--- will not be lost if the transaction is rolled back.
---
--- Since the function is meant to be called from SQL code, it does not return
--- anything as the identifier of the new entry is not required.
---
--- Parameters:
---	_component	The component that is appending to the log
---	_level		The log level
---	_message	The message to write
---
+/*
+ * Remotely append a system log entry
+ *
+ * This function is called from within transactions in order to write to the
+ * system log. Unlike sys.write_log(), entries added through this function
+ * will not be lost if the transaction is rolled back.
+ *
+ * Since the function is meant to be called from SQL code, it does not return
+ * anything as the identifier of the new entry is not required.
+ *
+ * Parameters:
+ *		_component		The component that is appending to the log
+ *		_level			The log level
+ *		_message		The message to write
+ */
 
 CREATE OR REPLACE FUNCTION sys.write_sql_log( _component TEXT , _level log_level , _message TEXT )
 		RETURNS VOID
@@ -146,14 +146,19 @@ CREATE OR REPLACE FUNCTION sys.write_sql_log( _component TEXT , _level log_level
 		SECURITY INVOKER
 	AS $write_sql_log$
 BEGIN
-	PERFORM dblink_connect( 'cn_logging' , 'srv_logging' );
+	BEGIN
+		PERFORM dblink_connect( 'cn_logging' , 'srv_logging' );
+	EXCEPTION
+		WHEN duplicate_object THEN
+			-- Ignore the error, assume we're connected
+	END;
+
 	PERFORM * FROM dblink( 'cn_logging' ,
 		'SELECT * FROM sys.write_log( '
 			|| quote_literal( _component ) || ' , '''
 			|| _level::text || '''::log_level , '
 			|| quote_literal( _message ) || ' )'
 	) AS ( entry_id bigint );
-	PERFORM dblink_disconnect( 'cn_logging' );
 END;
 $write_sql_log$ LANGUAGE plpgsql;
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/055-generator-resources.sql b/legacyworlds-server-data/db-structure/parts/040-functions/055-generator-resources.sql
index a2418ac..2e0e0fe 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/055-generator-resources.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/055-generator-resources.sql
@@ -250,9 +250,10 @@ BEGIN
 	_recovery := verse.get_random_part( _tot_recovery , _providers_left ,
 		_data.recovery_avg , _data.recovery_dev );
 
-	RAISE NOTICE 'Resource #% planet #%: quantity: %   difficulty: %   recovery: %',
-		_data.resource_name_id , _planet ,
-		_quantity , _difficulty , _recovery;
+	PERFORM sys.write_sql_log( 'UniverseGenerator' , 'TRACE'::log_level ,
+		'Resource #' || _data.resource_name_id || ',  planet #' || _planet
+		|| ': quantity ' || _quantity || ' , difficulty '
+		|| _difficulty || ' , recovery ' || _recovery );
 
 	INSERT INTO verse.resource_providers (
 			planet_id , resource_name_id , resprov_quantity_max ,
@@ -326,11 +327,13 @@ BEGIN
 	_tot_recovery := verse.compute_rpp_delta( _data.providers , _create ,
 			_data.recovery , _data.recovery_avg , _data.recovery_dev );
 
-	RAISE NOTICE 'Resource #%: % new provider(s), quantity: % (avg. %) , difficulty: % (avg. %), recovery: % (avg. %)',
-		_data.resource_name_id , _create ,
-		_tot_quantity , _tot_quantity / _create ,
-		_tot_difficulty , _tot_difficulty / _create ,
-		_tot_recovery , _tot_recovery / _create;
+	PERFORM sys.write_sql_log( 'UniverseGenerator' , 'TRACE'::log_level ,
+		'Resource #' || _data.resource_name_id || ': ' || _create
+		|| ' new provider(s), quantity: ' || _tot_quantity || ' (avg. '
+		|| ( _tot_quantity / _create ) || ') , difficulty: '
+		|| _tot_difficulty || ' (avg. ' || ( _tot_difficulty / _create )
+		|| '), recovery: ' || _tot_recovery || ' (avg. '
+		|| _tot_recovery / _create || ')' );
 
 	-- Select random planets to add resource providers to
 	FOR _planet IN SELECT * FROM verse.list_random_planets_in( _area , _create )
diff --git a/legacyworlds-server-data/db-structure/tests/admin/020-extensions/010-dblink.sql b/legacyworlds-server-data/db-structure/tests/admin/020-extensions/010-dblink.sql
new file mode 100644
index 0000000..24f8267
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/020-extensions/010-dblink.sql
@@ -0,0 +1,40 @@
+/*
+ * Test the presence and configuration of the dblink
+ * extension
+ */
+BEGIN;
+	SELECT plan( 6 );
+	
+	SELECT diag_test_name( 'dblink - dblink_connect() exists' );
+	SELECT has_function( 'dblink_connect' );
+
+	SELECT diag_test_name( 'dblink - Foreign data wrapper defined' );
+	SELECT is( p.proname , 'postgresql_fdw_validator' )
+		FROM pg_foreign_data_wrapper w
+			INNER JOIN pg_proc p ON w.fdwvalidator = p.oid
+		WHERE w.fdwname = 'pgsql';
+
+	SELECT diag_test_name( 'dblink - Foreign server defined' );
+	SELECT is ( w.fdwname , 'pgsql' )
+		FROM pg_foreign_server s
+			INNER JOIN pg_foreign_data_wrapper w
+				ON s.srvfdw = w.oid
+		WHERE s.srvname = 'srv_logging';
+
+	SELECT diag_test_name( 'dblink - Connection' );
+	SELECT lives_ok(
+		$$ SELECT dblink_connect( 'cn_logging' , 'srv_logging' ) $$
+	);
+	
+	SELECT diag_test_name( 'dblink - Remote user is not the administrator' );
+	SELECT isnt( username , current_user::TEXT )
+		FROM dblink( 'cn_logging' , 'SELECT current_user' )
+			AS ( username TEXT );
+
+	SELECT diag_test_name( 'dblink - Disconnection' );
+	SELECT lives_ok(
+		$$ SELECT dblink_disconnect( 'cn_logging' ) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/005-logs/010-write-sql-log.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/005-logs/010-write-sql-log.sql
new file mode 100644
index 0000000..e8bef15
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/005-logs/010-write-sql-log.sql
@@ -0,0 +1,32 @@
+/*
+ * Test the sys.write_sql_log( ) function
+ */
+BEGIN;
+	DELETE FROM sys.logs;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'sys.write_sql_log( ) - Initial call' );
+	SELECT lives_ok(
+		$$ SELECT sys.write_sql_log( 'test' , 'WARNING'::log_level , 'test' ) $$
+	);
+	SELECT diag_test_name( 'sys.write_sql_log( ) - Contents of system log after call' );
+	SELECT is( COUNT(*)::INT , 1 ) FROM sys.logs;
+	DELETE FROM sys.logs;
+	
+	SELECT diag_test_name( 'sys.write_sql_log( ) - Two calls in sequence' );
+	SELECT lives_ok(
+		$$ SELECT sys.write_sql_log( 'test' , 'WARNING'::log_level , 'test' );
+		SELECT sys.write_sql_log( 'test' , 'WARNING'::log_level , 'test' ) $$
+	);
+	DELETE FROM sys.logs;
+
+	SELECT diag_test_name( 'sys.write_sql_log( ) - Calling and rolling back' );
+	SAVEPOINT before_log;
+		SELECT sys.write_sql_log( 'test' , 'WARNING'::log_level , 'test' );
+		SELECT sys.write_log( 'test' , 'WARNING'::log_level , 'test' );
+	ROLLBACK TO SAVEPOINT before_log;	
+	SELECT is( COUNT(*)::INT , 1 ) FROM sys.logs;
+	DELETE FROM sys.logs;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/020-extensions/010-dblink.sql b/legacyworlds-server-data/db-structure/tests/user/020-extensions/010-dblink.sql
new file mode 100644
index 0000000..271e969
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/020-extensions/010-dblink.sql
@@ -0,0 +1,23 @@
+/*
+ * Test the configuration of the dblink extension from the user's perspective
+ */
+BEGIN;
+	SELECT plan( 3 );
+	
+	SELECT diag_test_name( 'dblink - Connection' );
+	SELECT lives_ok(
+		$$ SELECT dblink_connect( 'cn_logging' , 'srv_logging' ) $$
+	);
+	
+	SELECT diag_test_name( 'dblink - Remote user = local user' );
+	SELECT is( username , current_user::TEXT )
+		FROM dblink( 'cn_logging' , 'SELECT current_user' )
+			AS ( username TEXT );
+
+	SELECT diag_test_name( 'dblink - Disconnection' );
+	SELECT lives_ok(
+		$$ SELECT dblink_disconnect( 'cn_logging' ) $$
+	);
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/150-logs/010-sys-logs.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/150-logs/010-sys-logs.sql
new file mode 100644
index 0000000..78a8f77
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/150-logs/010-sys-logs.sql
@@ -0,0 +1,29 @@
+/*
+ * Test privileges on sys.logs
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'sys.logs - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO sys.logs( component , level , message )
+			VALUES ( 'test' , 'WARNING'::log_level , 'test' );
+		$$ , 42501 );
+
+	SELECT diag_test_name( 'sys.logs - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE sys.logs SET component = 'retest'; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.logs - SELECT privileges' );
+	SELECT lives_ok(
+		$$ SELECT * FROM sys.logs; $$
+	);
+
+	SELECT diag_test_name( 'sys.logs - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM sys.logs; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/005-logs/010-write-sql-log.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/005-logs/010-write-sql-log.sql
new file mode 100644
index 0000000..3996f8a
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/005-logs/010-write-sql-log.sql
@@ -0,0 +1,13 @@
+/*
+ * Test the sys.write_sql_log( ) function
+ */
+BEGIN;
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'sys.write_sql_log( ) - Privileges' );
+	SELECT lives_ok(
+		$$ SELECT sys.write_sql_log( 'test' , 'WARNING'::log_level , 'test' ) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/priv/sys/logs.sql b/legacyworlds-server-data/db-structure/tests/user/priv/sys/logs.sql
deleted file mode 100644
index e7c7948..0000000
--- a/legacyworlds-server-data/db-structure/tests/user/priv/sys/logs.sql
+++ /dev/null
@@ -1,39 +0,0 @@
-BEGIN;
-	SELECT plan( 3 );
-
-	--
-	-- Insertion through sys.write_log()
-	--
-	CREATE OR REPLACE FUNCTION _test_this( )
-		RETURNS BIGINT
-	AS $$
-		SELECT sys.write_log( 'test' , 'WARNING'::log_level , 'test' );
-	$$ LANGUAGE SQL;
-	SELECT lives_ok( 'SELECT _test_this()' );
-	DROP FUNCTION _test_this( );
-
-	--
-	-- Direct insertion must fail
-	--
-	CREATE FUNCTION _test_this( )
-		RETURNS VOID
-	AS $$
-		INSERT INTO sys.logs( component , level , message )
-			VALUES ( 'test' , 'WARNING'::log_level , 'test' );
-	$$ LANGUAGE SQL;
-	SELECT throws_ok( 'SELECT _test_this()' , 42501 );
-	DROP FUNCTION _test_this( );
-
-	--
-	-- Updates must fail
-	--
-	CREATE OR REPLACE FUNCTION _test_this( )
-		RETURNS VOID
-	AS $$
-		UPDATE sys.logs SET component = 'random' WHERE component = 'test';
-	$$ LANGUAGE SQL;
-	SELECT throws_ok( 'SELECT _test_this()' , 42501 );
-	DROP FUNCTION _test_this( );
-	
-	SELECT * FROM finish( );
-ROLLBACK;

From afc66166e01c4de4390a0e8a93f982f574bde904 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 10 Jan 2012 10:17:47 +0100
Subject: [PATCH 15/94] Mining settings

* Changed the way mining settings work: use a priority value (between 0
and 4) as the weight. Leaving them as they were before would have caused
numerous problems (and a lot of unnecessary code to work around them)

* Empire mining settings will be created along with the empire's own
record. By default all natural resources will have weight = 2.

* Added a set of four stored procedures which can be used to update an
empire's mining settings, including planet-specific settings. The
emp.mset_update_start() function can be used to start an update (on an
empire's settings if there is only one parameter, or on a planet's
settings if there are two parameters); the emp.mset_update_set() and
emp.mset_update_apply() functions are then used to modify the settings
and apply the changes, respectively.
---
 .../parts/030-data/110-empires.sql            |  15 +-
 .../parts/040-functions/040-empire.sql        |  57 +++--
 .../parts/040-functions/045-empire-mining.sql | 234 ++++++++++++++++++
 .../020-empire-mining-settings.sql            |  47 +++-
 .../030-empire-planet-mining-settings.sql     |  61 +++--
 .../040-empire/010-create-empire.sql          |  68 +++++
 .../010-mset-update-start.sql                 |  43 ++++
 .../015-mset-update-start-planet.sql          |  74 ++++++
 .../045-empire-mining/020-mset-update-set.sql |  37 +++
 .../030-mset-update-apply.sql                 |  93 +++++++
 .../040-empire/010-create-empire.sql          |  14 ++
 .../010-mset-update-start.sql                 |  14 ++
 .../015-mset-update-start-planet.sql          |  14 ++
 .../045-empire-mining/020-mset-update-set.sql |  14 ++
 .../030-mset-update-apply.sql                 |  15 ++
 15 files changed, 745 insertions(+), 55 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/010-create-empire.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/010-mset-update-start.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/015-mset-update-start-planet.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/030-mset-update-apply.sql

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
index a92e197..f6015dc 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
@@ -135,11 +135,12 @@ CREATE TABLE emp.mining_settings(
 	resource_name_id	INT NOT NULL ,
 
 	/* Weight to give to this type of resource when there are no planet-
-	 * specific settings.
+	 * specific settings. The weight is a value between 0 (lowest priority)
+	 * and 4 (highest priority)
 	 */ 
 	empmset_weight		INT NOT NULL
-							DEFAULT 1
-							CHECK( empmset_weight >= 0 ) ,
+							DEFAULT 2
+							CHECK( empmset_weight BETWEEN 0 AND 4 ) ,
 
 	/* Primary key on (empire,resource type) pairs */
 	PRIMARY KEY( empire_id , resource_name_id )
@@ -176,10 +177,12 @@ CREATE TABLE emp.planet_mining_settings(
 	/* Identifier of the type of resources */
 	resource_name_id	INT NOT NULL ,
 
-	/* Weight to give to this type of resource */ 
+	/* Weight to give to this type of resource. Works in a manner similar to 
+	 * the empmset_weight column of emp.mining_settings.
+	 */ 
 	emppmset_weight		INT NOT NULL
-							DEFAULT 1
-							CHECK( emppmset_weight >= 0 ) ,
+							DEFAULT 2
+							CHECK( emppmset_weight BETWEEN 0 AND 4 ) ,
 
 	/* Primary key on (empire,resource type) pairs */
 	PRIMARY KEY( empire_id , planet_id , resource_name_id )
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 8c18e73..67204c1 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -7,43 +7,60 @@
 -- --------------------------------------------------------
 
 
---
--- Empire creation
---
--- Parameters:
---	nid		Empire name identifier
---	pid		Planet identifier
---	icash	Initial cash
---
-CREATE OR REPLACE FUNCTION emp.create_empire( nid INT , pid INT , icash REAL )
+/*
+ * Empire creation
+ * 
+ * This function inserts the rows that represent an empire and its settings.
+ * It also initialises the empire's updates.
+ *
+ * Parameters:
+ *		_name_id		Empire name identifier
+ *		_planet_id		Planet identifier
+ *		_initial_cash	Initial cash
+ */
+DROP FUNCTION IF EXISTS emp.create_empire( INT , INT , REAL );
+CREATE FUNCTION emp.create_empire(
+			_name_id		INT ,
+			_planet_id		INT ,
+			_initial_cash	REAL )
 		RETURNS VOID
-		STRICT
-		VOLATILE
+		STRICT VOLATILE
 		SECURITY INVOKER
 	AS $$
 DECLARE
-	uid		BIGINT;
-	utp		update_type;
+	_update			BIGINT;
+	_update_type	update_type;
 BEGIN
 	-- Add empire and give initial planet
 	INSERT INTO emp.empires ( name_id , cash )
-		VALUES ( nid , icash );
+		VALUES ( _name_id , _initial_cash );
 	INSERT INTO emp.planets ( planet_id , empire_id )
-		VALUES ( pid , nid );
+		VALUES ( _planet_id , _name_id );
+
+	-- Add mining settings
+	INSERT INTO emp.mining_settings ( empire_id , resource_name_id )
+		SELECT _name_id , _resource.resource_name_id
+			FROM defs.natural_resources _resource;
 
 	-- Add empire update records
-	FOR utp IN SELECT x FROM unnest( enum_range( NULL::update_type ) ) AS x
-					WHERE x::text LIKE 'EMPIRE_%'
+	FOR _update_type IN SELECT _type
+			FROM unnest( enum_range( NULL::update_type ) ) AS _type
+			WHERE _type::text LIKE 'EMPIRE_%'
 	LOOP
 		INSERT INTO sys.updates( gu_type )
-			VALUES ( utp )
-			RETURNING id INTO uid;
+			VALUES ( _update_type )
+			RETURNING id INTO _update;
 		INSERT INTO emp.updates ( update_id , empire_id )
-			VALUES ( uid , nid );
+			VALUES ( _update , _name_id );
 	END LOOP;
 END;
 $$ LANGUAGE plpgsql;
 
+REVOKE EXECUTE
+	ON FUNCTION emp.create_empire( INT , INT , REAL )
+	FROM PUBLIC;
+
+
 
 --
 -- Returns a planet owner's empire size
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
new file mode 100644
index 0000000..9c4545f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
@@ -0,0 +1,234 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Functions that control and compute empire mining
+--
+-- Copyright(C) 2004-2010, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Prepare for an update on empire mining settings
+ * 
+ * This function creates a temporary table mimicking the structure of
+ * emp.mining_settings, and stores default settings into it.
+ * 
+ * Parameters:
+ *		_empire_id		The empire's identifier
+ *
+ * Returns:
+ *		?				True if the empire exists, false otherwise
+ */
+DROP FUNCTION IF EXISTS emp.mset_update_start( INT );
+CREATE FUNCTION emp.mset_update_start( _empire_id INT )
+		RETURNS BOOLEAN
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $mset_update_start$
+BEGIN
+
+	CREATE TEMPORARY TABLE mset_update(
+		empire_id			INT ,
+		resource_name_id	INT ,
+		empmset_weight		INT
+	) ON COMMIT DROP;
+
+	INSERT INTO mset_update
+		SELECT _mset.empire_id , _mset.resource_name_id , 2
+			FROM emp.empires _empire
+				INNER JOIN emp.mining_settings _mset
+					ON _empire.name_id = _mset.empire_id
+			WHERE _empire.name_id = _empire_id
+			FOR SHARE OF _empire
+			FOR UPDATE OF _mset;
+
+	RETURN FOUND;
+
+END;
+$mset_update_start$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.mset_update_start( INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION emp.mset_update_start( INT )
+	TO :dbuser;
+
+
+/*
+ * Prepare for an update on planet-specific mining settings
+ * 
+ * This function creates a temporary table mimicking the structure of
+ * emp.planet_mining_settings, and stores default settings into it, using the
+ * planet's list of resource provider as the source.
+ * 
+ * Parameters:
+ *		_empire_id		The empire's identifier
+ *		_planet_id		The planet's identifier
+ *
+ * Returns:
+ *		?				True if the empire exists and owns the planet, false
+ *							otherwise
+ */
+DROP FUNCTION IF EXISTS emp.mset_update_start( INT , INT );
+CREATE FUNCTION emp.mset_update_start( _empire_id INT , _planet_id INT )
+		RETURNS BOOLEAN
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $mset_update_start$
+BEGIN
+
+	CREATE TEMPORARY TABLE mset_update(
+		empire_id			INT ,
+		planet_id			INT ,
+		resource_name_id	INT ,
+		empmset_weight		INT
+	) ON COMMIT DROP;
+	
+	PERFORM 1
+		FROM emp.empires _empire
+			INNER JOIN emp.planets _emp_planet
+				ON _empire.name_id = _emp_planet.empire_id
+			INNER JOIN verse.planets _planet
+				ON _planet.name_id = _emp_planet.planet_id
+			INNER JOIN verse.resource_providers _resprov
+				ON _resprov.planet_id = _planet.name_id
+		WHERE _empire.name_id = _empire_id
+				AND _planet.name_id = _planet_id
+		FOR SHARE OF _empire, _emp_planet , _planet , _resprov;
+	IF NOT FOUND THEN
+		RETURN FALSE;
+	END IF;
+	
+	PERFORM 1
+		FROM emp.planet_mining_settings
+		WHERE empire_id = _empire_id AND planet_id = _planet_id
+		FOR UPDATE;
+
+	INSERT INTO mset_update
+		SELECT _empire_id , _planet_id , resource_name_id , 2
+			FROM verse.resource_providers
+			WHERE planet_id = _planet_id;
+
+	RETURN TRUE;
+
+END;
+$mset_update_start$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.mset_update_start( INT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION emp.mset_update_start( INT , INT )
+	TO :dbuser;
+
+
+
+/*
+ * Update the weight of some resource
+ * 
+ * This function updates the weight of a resource in the temporary table. It
+ * must be called after emp.mset_update_start() has initialised the table.
+ *
+ * Parameters:
+ *		_resource_id		The resource's identifier
+ *		_weight				The setting's new value
+ *
+ * Returns:
+ *		?					True if the resource exists, false otherwise.
+ */
+DROP FUNCTION IF EXISTS emp.mset_update_set( INT , INT );
+CREATE FUNCTION emp.mset_update_set( _resource_id INT , _weight INT )
+		RETURNS BOOLEAN
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $mset_update_set$
+BEGIN
+
+	UPDATE mset_update
+		SET empmset_weight = _weight
+		WHERE resource_name_id = _resource_id;
+	RETURN FOUND;
+
+END;
+$mset_update_set$ LANGUAGE PLPGSQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION emp.mset_update_set( INT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION emp.mset_update_set( INT , INT )
+	TO :dbuser;
+
+
+
+/*
+ * Apply a pending update to empire or planet mining settings
+ * 
+ * This function is called once mining settings (whether empire-wise or
+ * planet-specific) have been uploaded. It will apply all changes to the
+ * actual mining settings table.
+ * 
+ * Returns:
+ *		?			True if the update was applied, false if one of the
+ *						weights was invalid.
+ */
+DROP FUNCTION IF EXISTS emp.mset_update_apply( );
+CREATE FUNCTION emp.mset_update_apply( )
+		RETURNS BOOLEAN
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $mset_update_apply$
+
+DECLARE
+	_empire	INT;
+	_planet	INT;
+
+BEGIN
+	BEGIN
+
+		-- Get empire and planet identifier (will cause exception if this is
+		-- not a planet settings update).
+		SELECT INTO _empire , _planet
+				empire_id , planet_id
+			FROM mset_update
+			LIMIT 1;
+
+		DELETE FROM emp.planet_mining_settings
+			WHERE empire_id = _empire AND planet_id = _planet;
+		INSERT INTO emp.planet_mining_settings (
+					empire_id , planet_id , resource_name_id ,
+					emppmset_weight
+			) SELECT empire_id , planet_id ,
+					resource_name_id ,	empmset_weight
+				FROM mset_update;
+
+	EXCEPTION
+
+		-- These are empire-wide settings
+		WHEN undefined_column THEN
+			UPDATE emp.mining_settings _settings
+				SET empmset_weight = _update.empmset_weight
+				FROM mset_update _update
+				WHERE _update.empire_id = _settings.empire_id
+					AND _update.resource_name_id = _settings.resource_name_id;
+	END;
+	RETURN TRUE;
+
+EXCEPTION
+
+	-- One of the weights was invalid
+	WHEN check_violation THEN
+		RETURN FALSE;
+
+END;
+$mset_update_apply$ LANGUAGE PLPGSQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION emp.mset_update_apply( )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION emp.mset_update_apply( )
+	TO :dbuser;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/020-empire-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/020-empire-mining-settings.sql
index 8fb1158..5ddf3c4 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/020-empire-mining-settings.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/020-empire-mining-settings.sql
@@ -14,10 +14,10 @@ BEGIN;
 		SELECT id , 0 FROM naming.empire_names;
 	
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 10 );
+	SELECT plan( 13 );
 	
 	
-	SELECT diag_test_name( 'Valid empire mining settings record' );
+	SELECT diag_test_name( 'emp.mining_settings - Valid record' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -25,12 +25,12 @@ BEGIN;
 			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , 0
 		);
 	SELECT lives_ok( '_test_this' );
-	
-	SELECT diag_test_name( 'Duplicate empire mining settings record' );
+
+	SELECT diag_test_name( 'emp.mining_settings - Duplicate record' );
 	SELECT throws_ok( '_test_this' , 23505 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire mining settings records with same empire but different types' );
+	SELECT diag_test_name( 'emp.mining_settings - Same empire, different types' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -40,7 +40,7 @@ BEGIN;
 	SELECT lives_ok( '_test_this' );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire mining settings records with same type but different empires' );
+	SELECT diag_test_name( 'emp.mining_settings - Same type, different empires' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -51,8 +51,21 @@ BEGIN;
 	DEALLOCATE ALL;
 	DELETE FROM emp.mining_settings;
 
+	SELECT diag_test_name( 'emp.mining_settings - Valid record with default weight' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' )
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire mining settings record with NULL empire identifier' );
+	SELECT diag_test_name( 'emp.mining_settings - Default weight = 2' );
+	SELECT is( empmset_weight , 2 ) FROM emp.mining_settings;
+	DELETE FROM emp.mining_settings;
+
+	SELECT diag_test_name( 'emp.mining_settings - NULL empire identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -62,7 +75,7 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23502 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire mining settings record with invalid empire identifier' );
+	SELECT diag_test_name( 'emp.mining_settings - Invalid empire identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -73,7 +86,7 @@ BEGIN;
 	DEALLOCATE ALL;
 
 
-	SELECT diag_test_name( 'Empire mining settings record with NULL resource identifier' );
+	SELECT diag_test_name( 'emp.mining_settings - NULL resource identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -83,7 +96,7 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23502 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire mining settings record with invalid resource identifier' );
+	SELECT diag_test_name( 'emp.mining_settings - Invalid resource identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -94,7 +107,7 @@ BEGIN;
 	DEALLOCATE ALL;
 
 
-	SELECT diag_test_name( 'Empire mining settings record with NULL weight' );
+	SELECT diag_test_name( 'emp.mining_settings - NULL weight' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -104,7 +117,7 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23502 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire mining settings record with weight < 0' );
+	SELECT diag_test_name( 'emp.mining_settings - Weight < 0' );
 	PREPARE _test_this AS
 		INSERT INTO emp.mining_settings(
 			empire_id , resource_name_id , empmset_weight
@@ -114,6 +127,16 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23514 );
 	DEALLOCATE ALL;
 
+	SELECT diag_test_name( 'emp.mining_settings - Weight > 4' );
+	PREPARE _test_this AS
+		INSERT INTO emp.mining_settings(
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_string( 'testResource1' ) , 5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/030-empire-planet-mining-settings.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/030-empire-planet-mining-settings.sql
index ea1e154..15137d8 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/030-empire-planet-mining-settings.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/030-data/110-empires/030-empire-planet-mining-settings.sql
@@ -1,5 +1,5 @@
 /*
- * Test constraints and foreign keys on emp.mining_settings
+ * Test constraints and foreign keys on emp.planet_mining_settings
  */
 BEGIN;
 
@@ -20,10 +20,10 @@ BEGIN;
 	-- No provider for testResource2 on testPlanet2
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 14 );
+	SELECT plan( 17 );
 
 
-	SELECT diag_test_name( 'Valid empire planet mining settings record' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Valid record' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -34,11 +34,11 @@ BEGIN;
 		);
 	SELECT lives_ok( '_test_this' );
 	
-	SELECT diag_test_name( 'Duplicate empire planet mining settings record' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Duplicate record' );
 	SELECT throws_ok( '_test_this' , 23505 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire planet mining settings records with different types' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Different types' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -50,7 +50,7 @@ BEGIN;
 	SELECT lives_ok( '_test_this' );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire planet mining settings records with different empires' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Different empires' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -62,7 +62,7 @@ BEGIN;
 	SELECT lives_ok( '_test_this' );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire planet mining settings records with different planets' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Different planets' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -73,10 +73,25 @@ BEGIN;
 		);
 	SELECT lives_ok( '_test_this' );
 	DEALLOCATE ALL;
-	DELETE FROM emp.mining_settings;
+	DELETE FROM emp.planet_mining_settings;
+
+	SELECT diag_test_name( 'emp.planet_mining_settings - Valid record with default weight' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id , resource_name_id
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet1' ) ,
+			_get_string( 'testResource1' )
+		);
+	SELECT lives_ok( '_test_this' );
+	DEALLOCATE ALL;
+
+	SELECT diag_test_name( 'emp.planet_mining_settings - Default weight = 2' );
+	SELECT is( emppmset_weight , 2 ) FROM emp.planet_mining_settings;
+	DELETE FROM emp.planet_mining_settings;
 
 
-	SELECT diag_test_name( 'Empire planet mining setting record with NULL empire identifier' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - NULL empire identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -88,7 +103,7 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23502 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire planet mining setting record with invalid empire identifier' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Invalid empire identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -101,7 +116,7 @@ BEGIN;
 	DEALLOCATE ALL;
 
 
-	SELECT diag_test_name( 'Empire planet mining setting record with NULL planet identifier' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - NULL planet identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -113,7 +128,7 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23502 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire planet mining setting record with invalid planet identifier' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Invalid planet identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -126,7 +141,7 @@ BEGIN;
 	DEALLOCATE ALL;
 
 
-	SELECT diag_test_name( 'Empire planet mining setting record with NULL resource identifier' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - NULL resource identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -138,7 +153,7 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23502 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire planet mining setting record with invalid resource identifier' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Invalid resource identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -151,7 +166,7 @@ BEGIN;
 	DEALLOCATE ALL;
 
 
-	SELECT diag_test_name( 'Empire planet mining setting record with invalid resource provider identifier' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Invalid resource provider identifier' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -164,7 +179,7 @@ BEGIN;
 	DEALLOCATE ALL;
 
 
-	SELECT diag_test_name( 'Empire planet mining setting record with NULL weight' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - NULL weight' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -176,7 +191,7 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23502 );
 	DEALLOCATE ALL;
 
-	SELECT diag_test_name( 'Empire planet mining setting record with weight < 0' );
+	SELECT diag_test_name( 'emp.planet_mining_settings - Weight < 0' );
 	PREPARE _test_this AS
 		INSERT INTO emp.planet_mining_settings(
 			empire_id , planet_id ,
@@ -188,6 +203,18 @@ BEGIN;
 	SELECT throws_ok( '_test_this' , 23514 );
 	DEALLOCATE ALL;
 
+	SELECT diag_test_name( 'emp.planet_mining_settings - Weight > 4' );
+	PREPARE _test_this AS
+		INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id ,
+			resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testUser1' ) , _get_map_name( 'testPlanet1' ) ,
+			_get_string( 'testResource1' ) , 5
+		);
+	SELECT throws_ok( '_test_this' , 23514 );
+	DEALLOCATE ALL;
+
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
new file mode 100644
index 0000000..55f1b48
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
@@ -0,0 +1,68 @@
+/*
+ * Test the emp.create_empire() function
+ */
+BEGIN;
+	/* We need a pair of natural resources, a basic resource, a planet
+	 * and an empire name.
+	 */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'natRes' );
+	SELECT _create_resources( 1 , 'basicRes' );
+	SELECT _create_raw_planets( 1 , 'testPlanet' );
+	SELECT _create_emp_names( 1 , 'testEmp' );
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 7 );
+	
+	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) ,
+				_get_map_name( 'testPlanet1' ) ,
+				200.0 );
+
+	SELECT diag_test_name( 'emp.create_empire() - Empire exists' );
+	SELECT is( COUNT(*)::INT , 1 ) FROM emp.empires
+		WHERE name_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'emp.create_empire() - Empire cash' );
+	SELECT is( cash , 200.0::REAL ) FROM emp.empires
+		WHERE name_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'emp.create_empire() - Empire debt' );
+	SELECT is( debt , 0.0::REAL ) FROM emp.empires
+		WHERE name_id = _get_emp_name( 'testEmp1' );
+
+	SELECT diag_test_name( 'emp.create_empire() - Empire mining settings include all natural resources' );
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM defs.natural_resources
+			INNER JOIN emp.mining_settings
+				USING ( resource_name_id )
+		WHERE empire_id = _get_emp_name( 'testEmp1' );
+
+	SELECT diag_test_name( 'emp.create_empire() - Empire mining settings do not include basic resources' );
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM defs.resources
+			INNER JOIN emp.mining_settings
+				USING ( resource_name_id )
+		WHERE empire_id = _get_emp_name( 'testEmp1' );
+
+	SELECT diag_test_name( 'emp.create_empire() - Empire mining settings are all set to the same value' );
+	SELECT is( COUNT( _temp.* )::INT , 1 )
+		FROM ( SELECT DISTINCT empmset_weight
+					FROM emp.mining_settings
+					WHERE empire_id = _get_emp_name( 'testEmp1' )
+			) AS _temp;
+
+	SELECT diag_test_name( 'emp.create_empire() - Empire update records' );
+	SELECT is( _eur.quantity , _utv.quantity)
+		FROM (
+				SELECT COUNT(*) AS quantity FROM emp.updates
+					WHERE empire_id = _get_emp_name( 'testEmp1' )
+			) AS _eur , (
+				SELECT COUNT(*) AS quantity
+					FROM unnest( enum_range( NULL::update_type ) ) AS _row
+					WHERE _row::TEXT LIKE 'EMPIRE_%'
+			) AS _utv;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql
new file mode 100644
index 0000000..b73c7bc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql
@@ -0,0 +1,43 @@
+/*
+ * Test the emp.mset_update_start( INT ) function
+ */
+BEGIN;
+	/* We need a pair of natural resources and an empire with mining settings. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'natRes' );
+	SELECT _create_resources( 1 , 'basicRes' );
+	SELECT _create_raw_planets( 1 , 'testPlanet' );
+	SELECT _create_emp_names( 1 , 'testEmp' );
+	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) ,
+				_get_map_name( 'testPlanet1' ) ,
+				200.0 );
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 6 );
+	
+	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Return value on bad empire identifier' );
+	SELECT ok( NOT emp.mset_update_start( _get_bad_emp_name( ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Temporary table exists despite bad empire identifier' );
+	SELECT has_table( 'mset_update' );
+	DROP TABLE mset_update;
+
+	
+	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Return value on valid empire identifier' );
+	SELECT ok( emp.mset_update_start( _get_emp_name( 'testEmp1' ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Temporary table exists' );
+	SELECT has_table( 'mset_update' );
+	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Temporary table contains all required entries' );
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM mset_update
+		WHERE empire_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Temporary table does not contain extra entries' );
+	SELECT is( COUNT(*)::INT , 0 )
+		FROM mset_update
+		WHERE empire_id <> _get_emp_name( 'testEmp1' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
new file mode 100644
index 0000000..816508a
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
@@ -0,0 +1,74 @@
+/*
+ * Test the emp.mset_update_start( INT , INT ) function
+ */
+BEGIN;
+	/* We need a pair of natural resources, an empire, a pair of planets with
+	 * resource providers. The empire must own one of the planets.
+	 */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'testResource' );
+	SELECT _create_raw_planets( 3 , 'testPlanet' );
+	SELECT _create_emp_names( 1 , 'testEmp' );
+	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) ,
+				_get_map_name( 'testPlanet1' ) ,
+				200.0 );
+	SELECT _create_resource_provider( 'testPlanet1' , 'testResource1' );
+	SELECT _create_resource_provider( 'testPlanet2' , 'testResource1' );
+	INSERT INTO emp.planets ( empire_id , planet_id )
+		VALUES ( _get_emp_name( 'testEmp1' ) , 	_get_map_name( 'testPlanet3' ) );
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 14 );
+
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on bad empire identifier' );
+	SELECT ok( NOT emp.mset_update_start( _get_bad_emp_name( ) , _get_map_name( 'testPlanet1' ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite bad empire identifier' );
+	SELECT has_table( 'mset_update' );
+	DROP TABLE mset_update;
+
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on bad planet identifier' );
+	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_bad_map_name( ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite bad planet identifier' );
+	SELECT has_table( 'mset_update' );
+	DROP TABLE mset_update;
+
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on unowned planet identifier' );
+	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet2' ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite unowned planet identifier' );
+	SELECT has_table( 'mset_update' );
+	DROP TABLE mset_update;
+
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on unowned planet identifier' );
+	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet2' ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite unowned planet identifier' );
+	SELECT has_table( 'mset_update' );
+	DROP TABLE mset_update;
+
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on planet with no resource providers' );
+	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet3' ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite planet with no resource providers' );
+	SELECT has_table( 'mset_update' );
+	DROP TABLE mset_update;
+
+	
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on valid identifiers' );
+	SELECT ok( emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet1' ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists' );
+	SELECT has_table( 'mset_update' );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table contains all required entries' );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM mset_update
+		WHERE empire_id = _get_emp_name( 'testEmp1' )
+			AND planet_id = _get_map_name( 'testPlanet1' );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table does not contain extra entries' );
+	SELECT is( COUNT(*)::INT , 0 )
+		FROM mset_update
+		WHERE empire_id <> _get_emp_name( 'testEmp1' )
+			OR planet_id <> _get_map_name( 'testPlanet1' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql
new file mode 100644
index 0000000..7515bfb
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql
@@ -0,0 +1,37 @@
+/*
+ * Test the emp.mset_update_set() function
+ */
+BEGIN;
+	CREATE TEMPORARY TABLE mset_update(
+		empire_id			INT ,
+		resource_name_id	INT ,
+		empmset_weight		INT
+	) ON COMMIT DROP;
+	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 0 );
+	
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 7 );
+	
+	SELECT diag_test_name( 'emp.mset_update_set( ) - Valid update' );
+	SELECT ok( emp.mset_update_set( 1 , 1 ) );
+	SELECT diag_test_name( 'emp.mset_update_set( ) - Valid update results (1/2)' );
+	SELECT is( empmset_weight , 1 ) FROM mset_update WHERE resource_name_id = 1;
+	SELECT diag_test_name( 'emp.mset_update_set( ) - Valid update results (2/2)' );
+	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name_id = 2;
+	DELETE FROM mset_update;
+
+	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 0 );
+	SELECT diag_test_name( 'emp.mset_update_set( ) - Update on unknown resource' );
+	SELECT ok( NOT emp.mset_update_set( 12 , 1 ) );
+	SELECT diag_test_name( 'emp.mset_update_set( ) - Unknown resource update results (1/2)' );
+	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name_id = 1;
+	SELECT diag_test_name( 'emp.mset_update_set( ) - Unknown resource update results (2/2)' );
+	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name_id = 2;
+	DELETE FROM mset_update;
+
+	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 0 );
+	SELECT diag_test_name( 'emp.mset_update_set( ) - Update with invalid weight' );
+	SELECT ok( emp.mset_update_set( 1 , -1 ) );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
new file mode 100644
index 0000000..74d49b1
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
@@ -0,0 +1,93 @@
+/*
+ * Test the emp.mset_update_apply() function
+ */
+BEGIN;
+	/*
+	 * Remove foreign keys from the empire mining settings table, insert some
+	 * data into it.
+	 */
+	ALTER TABLE emp.mining_settings
+		DROP CONSTRAINT fk_empmset_empire ,
+		DROP CONSTRAINT fk_empmset_resource;
+	INSERT INTO emp.mining_settings ( empire_id , resource_name_id )
+		VALUES ( 1 , 1 ) , ( 1 , 2 );
+
+	/* Create the temporary table */
+	CREATE TEMPORARY TABLE mset_update(
+		empire_id			INT ,
+		resource_name_id	INT ,
+		empmset_weight		INT
+	) ON COMMIT DROP;
+	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 4 );
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT no_plan( );
+
+	SELECT diag_test_name( 'emp.mset_update_apply() - Applying valid empire update' );
+	SELECT ok( emp.mset_update_apply( ) );
+	SELECT diag_test_name( 'emp.mset_update_apply() - Empire update results' );
+	SELECT set_eq(
+		$$ SELECT resource_name_id , empmset_weight FROM emp.mining_settings $$ ,
+		$$ VALUES ( 1 , 0 ) , ( 2 , 4 ) $$
+	);
+
+	/* Reset temporary table and settings */
+	DELETE FROM mset_update;
+	DELETE FROM emp.mining_settings;
+	INSERT INTO emp.mining_settings ( empire_id , resource_name_id )
+		VALUES ( 1 , 1 ) , ( 1 , 2 );
+
+	INSERT INTO mset_update VALUES ( 1 , 1 , -1 ) , ( 1 , 2 , 4 );
+	SELECT diag_test_name( 'emp.mset_update_apply() - Applying invalid empire update' );
+	SELECT ok( NOT emp.mset_update_apply( ) );
+	SELECT diag_test_name( 'emp.mset_update_apply() - Invalid empire update results' );
+	SELECT set_eq(
+		$$ SELECT resource_name_id , empmset_weight FROM emp.mining_settings $$ ,
+		$$ VALUES ( 1 , 2 ) , ( 2 , 2 ) $$
+	);
+
+
+	/* Re-create the temporary table to store planet mining settings, remove
+	 * constraints from the planet mining settings table, insert data into
+	 * both.
+	 */ 
+	ALTER TABLE emp.planet_mining_settings
+		DROP CONSTRAINT fk_emppmset_empire ,
+		DROP CONSTRAINT fk_emppmset_resource;
+	INSERT INTO emp.planet_mining_settings ( empire_id , planet_id , resource_name_id )
+		VALUES ( 1 , 1 , 1 ) , ( 1 , 1 , 2 );
+
+	DROP TABLE mset_update;
+	CREATE TEMPORARY TABLE mset_update(
+		empire_id			INT ,
+		planet_id			INT ,
+		resource_name_id	INT ,
+		empmset_weight		INT
+	) ON COMMIT DROP;
+	INSERT INTO mset_update VALUES ( 1 , 1 , 1 , 0 ) , ( 1 , 1 , 2 , 4 );
+
+	SELECT diag_test_name( 'emp.mset_update_apply() - Applying valid planet update' );
+	SELECT ok( emp.mset_update_apply( ) );
+	SELECT diag_test_name( 'emp.mset_update_apply() - Planet update results' );
+	SELECT set_eq(
+		$$ SELECT resource_name_id , emppmset_weight FROM emp.planet_mining_settings $$ ,
+		$$ VALUES ( 1 , 0 ) , ( 2 , 4 ) $$
+	);
+
+	/* Reset temporary table and settings */
+	DELETE FROM mset_update;
+	DELETE FROM emp.planet_mining_settings;
+	INSERT INTO emp.planet_mining_settings ( empire_id , planet_id , resource_name_id )
+		VALUES ( 1 , 1 , 1 ) , ( 1 , 1 , 2 );
+
+	INSERT INTO mset_update VALUES ( 1 , 1 , 1 , -1 ) , ( 1 , 1 , 2 , 4 );
+	SELECT diag_test_name( 'emp.mset_update_apply() - Applying invalid planet update' );
+	SELECT ok( NOT emp.mset_update_apply( ) );
+	SELECT diag_test_name( 'emp.mset_update_apply() - Invalid planet update results' );
+	SELECT set_eq(
+		$$ SELECT resource_name_id , emppmset_weight FROM emp.planet_mining_settings $$ ,
+		$$ VALUES ( 1 , 2 ) , ( 2 , 2 ) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/010-create-empire.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/010-create-empire.sql
new file mode 100644
index 0000000..11e01b6
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/010-create-empire.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on emp.create_empire()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.create_empire() - Privileges' );
+	SELECT throws_ok( $$
+			SELECT emp.create_empire( 1 , 1 , 200.0 )
+		$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/010-mset-update-start.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/010-mset-update-start.sql
new file mode 100644
index 0000000..598c26b
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/010-mset-update-start.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on emp.mset_update_start( INT )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Privileges' );
+	SELECT lives_ok( $$
+			SELECT emp.mset_update_start( 1 )
+		$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/015-mset-update-start-planet.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/015-mset-update-start-planet.sql
new file mode 100644
index 0000000..cf0e1ec
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/015-mset-update-start-planet.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on emp.mset_update_start( INT , INT )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Privileges' );
+	SELECT lives_ok( $$
+			SELECT emp.mset_update_start( 1 , 1 )
+		$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql
new file mode 100644
index 0000000..cc37514
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on emp.mset_update_set()
+ */
+BEGIN;
+	SELECT emp.mset_update_start( 1 );
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.mset_update_set() - Privileges' );
+	SELECT lives_ok( $$
+			SELECT emp.mset_update_set( 1 , -1 )
+		$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/030-mset-update-apply.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/030-mset-update-apply.sql
new file mode 100644
index 0000000..6f349fb
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/030-mset-update-apply.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on emp.mset_update_set()
+ */
+BEGIN;
+	SELECT emp.mset_update_start( 1 );
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.mset_update_apply() - Privileges' );
+	SELECT lives_ok( $$
+			SELECT emp.mset_update_apply( )
+		$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From 37555841ce142d3f2fa4a0767864fff6219b85b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 10 Jan 2012 10:48:56 +0100
Subject: [PATCH 16/94] Empire resources initialisation

* Empire resources will be initialised when an empire is created

* When a new resource is created through the appropriate defs.uoc_...
function, it will be added to all empires as well.
---
 .../parts/040-functions/025-resources.sql     | 12 +++++++++
 .../parts/040-functions/040-empire.sql        |  4 +++
 .../010-uoc-resource-internal.sql             | 17 ++++++++++++-
 .../025-resources/020-uoc-resource.sql        | 23 ++++++++++++++++-
 .../025-resources/030-uoc-natres-internal.sql | 18 ++++++++++++-
 .../040-uoc-natural-resource.sql              | 25 ++++++++++++++++++-
 .../040-empire/010-create-empire.sql          | 18 +++++++------
 7 files changed, 106 insertions(+), 11 deletions(-)

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
index 053f1f1..0937af2 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
@@ -102,6 +102,12 @@ BEGIN
 		) VALUES (
 			_name_id , _desc_id , _cat_id , _weight
 		);
+
+		-- Add the resource to all empires
+		INSERT INTO emp.resources ( empire_id , resource_name_id )
+			SELECT name_id , _name_id
+				FROM emp.empires;
+
 		RETURN 'CREATED';
 	EXCEPTION
 		WHEN unique_violation THEN
@@ -312,6 +318,12 @@ BEGIN
 			_difficulty_avg , _difficulty_dev ,
 			_recovery_avg , _recovery_dev
 		);
+
+		-- Add the resource to all empires
+		INSERT INTO emp.resources ( empire_id , resource_name_id )
+			SELECT name_id , _name_id
+				FROM emp.empires;
+
 		RETURN 'CREATED';
 	END IF;
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 67204c1..7963876 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -42,6 +42,10 @@ BEGIN
 		SELECT _name_id , _resource.resource_name_id
 			FROM defs.natural_resources _resource;
 
+	-- Add empire resources
+	INSERT INTO emp.resources ( empire_id , resource_name_id )
+		SELECT _name_id , resource_name_id FROM defs.resources;
+
 	-- Add empire update records
 	FOR _update_type IN SELECT _type
 			FROM unnest( enum_range( NULL::update_type ) ) AS _type
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
index 10bee48..9dc1a03 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
@@ -10,7 +10,7 @@ BEGIN;
 	SELECT _create_natural_resources( 1 , 'natRes' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 15 );
+	SELECT plan( 16 );
 
 	SELECT diag_test_name( 'defs.uoc_resource_internal() - creation without category' );
 	SELECT is( defs.uoc_resource_internal( 'test3' , 'test4' , NULL , 1 ) , 'CREATED' );
@@ -69,6 +69,21 @@ BEGIN;
 
 	SELECT diag_test_name( 'defs.uoc_resource_internal() - weight <= 0' );
 	SELECT is( defs.uoc_resource_internal( 'test1' , 'test2' , NULL , 0 ) , 'BAD_VALUE' );
+	
+	/* Reset resources, create empire */
+	DELETE FROM defs.natural_resources;
+	DELETE FROM defs.resources;
+	ALTER TABLE emp.empires
+		DROP CONSTRAINT fk_empires_name;
+	INSERT INTO emp.empires ( name_id , cash )
+		VALUES ( 1 , 0 );
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - new resources are added to empires' );
+	SELECT defs.uoc_resource_internal( 'test3' , 'test4' , NULL , 1 );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.resources
+		WHERE empire_id = 1
+			AND resource_name_id = _get_string( 'test3' );
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
index 113634d..3b08cfd 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
@@ -10,7 +10,7 @@ BEGIN;
 	SELECT _create_natural_resources( 1 , 'natRes' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 22 );
+	SELECT plan( 24 );
 
 	SELECT diag_test_name( 'defs.uoc_resource() - NULL name (no category)' );
 	SELECT is( defs.uoc_resource( NULL , 'test2' , 1 ) , NULL );
@@ -86,5 +86,26 @@ BEGIN;
 	SELECT diag_test_name( 'defs.uoc_resource() - weight <= 0' );
 	SELECT is( defs.uoc_resource( 'natRes1' , 'test2' , 0 ) , 'BAD_VALUE' );
 
+	/* Reset resources, create empire */
+	DELETE FROM defs.natural_resources;
+	DELETE FROM defs.resources;
+	ALTER TABLE emp.empires
+		DROP CONSTRAINT fk_empires_name;
+	INSERT INTO emp.empires ( name_id , cash )
+		VALUES ( 1 , 0 );
+
+	SELECT diag_test_name( 'defs.uoc_resource() - new resources are added to empires (no category)' );
+	SELECT defs.uoc_resource( 'test1' , 'test2' , 1 );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.resources
+		WHERE empire_id = 1
+			AND resource_name_id = _get_string( 'test1' );
+	SELECT diag_test_name( 'defs.uoc_resource() - new resources are added to empires' );
+	SELECT defs.uoc_resource( 'test3' , 'test4' , 'test5' , 1 );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.resources
+		WHERE empire_id = 1
+			AND resource_name_id = _get_string( 'test3' );
+
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
index d442020..79f5a3b 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
@@ -10,7 +10,7 @@ BEGIN;
 	SELECT _create_resources( 1 , 'basicRes' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 37 );
+	SELECT plan( 38 );
 
 	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation without category' );
 	SELECT is( defs.uoc_natres_internal( 'test3' , 'test4' , NULL , 1 ,
@@ -171,5 +171,21 @@ BEGIN;
 	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
 		0.5 , 100 , 50 , 0.5 , 0.05 , 0.75 , 0.5 ) , 'BAD_VALUE' );
 
+	/* Reset resources, create empire */
+	DELETE FROM defs.natural_resources;
+	DELETE FROM defs.resources;
+	ALTER TABLE emp.empires
+		DROP CONSTRAINT fk_empires_name;
+	INSERT INTO emp.empires ( name_id , cash )
+		VALUES ( 1 , 0 );
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - new resources are added to empires' );
+	SELECT defs.uoc_natres_internal( 'test1' , 'test2' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.resources
+		WHERE empire_id = 1
+			AND resource_name_id = _get_string( 'test1' );
+
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
index 3f8f119..87889fe 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
@@ -10,7 +10,7 @@ BEGIN;
 	SELECT _create_resources( 1 , 'basicRes' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 58 );
+	SELECT plan( 60 );
 
 
 	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL name (no category)' );
@@ -238,5 +238,28 @@ BEGIN;
 	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
 		0.5 , 100 , 50 , 0.5 , 0.05 , 0.75 , 0.5 ) , 'BAD_VALUE' );
 
+	/* Reset resources, create empire */
+	DELETE FROM defs.natural_resources;
+	DELETE FROM defs.resources;
+	ALTER TABLE emp.empires
+		DROP CONSTRAINT fk_empires_name;
+	INSERT INTO emp.empires ( name_id , cash )
+		VALUES ( 1 , 0 );
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - new resources are added to empires (no category)' );
+	SELECT defs.uoc_natural_resource( 'test1' , 'test2' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.resources
+		WHERE empire_id = 1
+			AND resource_name_id = _get_string( 'test1' );
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - new resources are added to empires' );
+	SELECT defs.uoc_natural_resource( 'test3' , 'test4' , 'test5' , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.resources
+		WHERE empire_id = 1
+			AND resource_name_id = _get_string( 'test3' );
+
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
index 55f1b48..72671df 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
@@ -16,7 +16,7 @@ BEGIN;
 	SELECT _create_emp_names( 1 , 'testEmp' );
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 7 );
+	SELECT plan( 8 );
 	
 	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) ,
 				_get_map_name( 'testPlanet1' ) ,
@@ -46,12 +46,16 @@ BEGIN;
 				USING ( resource_name_id )
 		WHERE empire_id = _get_emp_name( 'testEmp1' );
 
-	SELECT diag_test_name( 'emp.create_empire() - Empire mining settings are all set to the same value' );
-	SELECT is( COUNT( _temp.* )::INT , 1 )
-		FROM ( SELECT DISTINCT empmset_weight
-					FROM emp.mining_settings
-					WHERE empire_id = _get_emp_name( 'testEmp1' )
-			) AS _temp;
+	SELECT diag_test_name( 'emp.create_empire() - Empire mining settings are all set to 2' );
+	SELECT is( COUNT( * )::INT , 0 )
+		FROM emp.mining_settings
+		WHERE empire_id = _get_emp_name( 'testEmp1' )
+			AND empmset_weight <> 2;
+
+	SELECT diag_test_name( 'emp.create_empire() - Empire resources have been initialised' );
+	SELECT is( COUNT(*)::INT , 3 )
+		FROM emp.resources
+		WHERE empire_id = _get_emp_name( 'testEmp1' );
 
 	SELECT diag_test_name( 'emp.create_empire() - Empire update records' );
 	SELECT is( _eur.quantity , _utv.quantity)

From b49bc1a44fca15cf215462c292fec1d76fa48882 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 10 Jan 2012 12:30:47 +0100
Subject: [PATCH 17/94] Planet resources

* Added missing table that should store a planet's resources data
(income and upkeep for each type of resource).

* Modified resource definition functions and universe generator to
initialise planet resource records as well

* Heavy clean-up in resource definition function unit tests
---
 .../parts/030-data/100-universe.sql           |  46 +++
 .../parts/040-functions/025-resources.sql     |  56 +++-
 .../parts/040-functions/060-universe.sql      |   4 +
 .../100-universe/020-planet-resources.sql     | 156 +++++++++
 .../005-add-resource-records.sql              |  39 +++
 .../010-uoc-resource-internal.sql             |  32 +-
 .../025-resources/020-uoc-resource.sql        | 131 ++------
 .../025-resources/030-uoc-natres-internal.sql |  30 +-
 .../040-uoc-natural-resource.sql              | 307 +++---------------
 .../020-verse-planet-resources.sql            |  28 ++
 .../005-add-resource-records.sql              |  14 +
 11 files changed, 465 insertions(+), 378 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/020-planet-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/020-verse-planet-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/005-add-resource-records.sql

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql b/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql
index 58ddf52..2453ded 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql
@@ -135,6 +135,52 @@ ALTER TABLE verse.planet_money
 		FOREIGN KEY (planet_id) REFERENCES verse.planets;
 
 
+
+/*
+ * Planet resource changes
+ * ------------------------
+ * 
+ * This table stores the results of planet resource updates. It will then be
+ * used to update the owning empires' resources.
+ * 
+ * This table applies to both basic and natural resources. 
+ */
+CREATE TABLE verse.planet_resources(
+
+	/* Identifier of the planet */
+	planet_id			INT NOT NULL ,
+
+	/* Identifier of the resource type */
+	resource_name_id	INT NOT NULL ,
+
+	/* Quantity of that resource which was somehow gained at the last game
+	 * update.
+	 */
+	pres_income			DOUBLE PRECISION NOT NULL
+							DEFAULT 0
+							CHECK( pres_income >= 0 ) ,
+
+	/* Quantity of that resource used by the planet's various buildings at the
+	 * last game update.
+	 */
+	pres_upkeep			DOUBLE PRECISION NOT NULL
+							DEFAULT 0
+							CHECK( pres_upkeep >= 0 ) ,
+
+	/* Primary key on (planet,type of resource) */
+	PRIMARY KEY( planet_id , resource_name_id )
+);
+
+CREATE INDEX idx_pres_resource
+	ON verse.planet_resources( resource_name_id );
+
+ALTER TABLE verse.planet_resources
+	ADD CONSTRAINT fk_pres_planet
+		FOREIGN KEY ( planet_id ) REFERENCES verse.planets ,
+	ADD CONSTRAINT fk_pres_resource
+		FOREIGN KEY ( resource_name_id ) REFERENCES defs.resources;
+
+
 --
 -- Buildings
 --
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
index 0937af2..3a47212 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
@@ -38,6 +38,35 @@ CREATE TYPE defs.resource_update_result
 	);
 
 
+/*
+ * Add a resource to all empire and planet records
+ * 
+ * This function makes sure that all empire and planet records have a row for
+ * a newly-created resource.
+ * 
+ * Parameters:
+ *		_resource		The identifier of the resource to add
+ */
+DROP FUNCTION IF EXISTS defs.add_resource_records( INT );
+CREATE FUNCTION defs.add_resource_records( _resource INT )
+	RETURNS VOID
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $$
+
+	INSERT INTO emp.resources ( empire_id , resource_name_id )
+			SELECT name_id , $1 FROM emp.empires;
+
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+			SELECT name_id , $1 FROM verse.planets;
+
+$$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.add_resource_records( INT )
+	FROM PUBLIC;
+
+
 
 /*
  * Create or update a basic resource
@@ -60,7 +89,8 @@ CREATE TYPE defs.resource_update_result
  * Returns:
  *		?				the result code for the operation
  */
-CREATE OR REPLACE FUNCTION defs.uoc_resource_internal(
+DROP FUNCTION IF EXISTS defs.uoc_resource_internal( TEXT , TEXT , TEXT , INT );
+CREATE FUNCTION defs.uoc_resource_internal(
 				_name TEXT ,
 				_description TEXT ,
 				_category TEXT ,
@@ -103,11 +133,7 @@ BEGIN
 			_name_id , _desc_id , _cat_id , _weight
 		);
 
-		-- Add the resource to all empires
-		INSERT INTO emp.resources ( empire_id , resource_name_id )
-			SELECT name_id , _name_id
-				FROM emp.empires;
-
+		PERFORM defs.add_resource_records( _name_id );
 		RETURN 'CREATED';
 	EXCEPTION
 		WHEN unique_violation THEN
@@ -163,7 +189,8 @@ REVOKE EXECUTE
  * Returns:
  *		?				the result code for the operation
  */
-CREATE OR REPLACE FUNCTION defs.uoc_resource(
+DROP FUNCTION IF EXISTS defs.uoc_resource( TEXT , TEXT , INT );
+CREATE FUNCTION defs.uoc_resource(
 			_name TEXT ,
 			_description TEXT ,
 			_weight INT )
@@ -196,7 +223,8 @@ GRANT EXECUTE
  * Returns:
  *		?				the result code for the operation
  */
-CREATE OR REPLACE FUNCTION defs.uoc_resource(
+DROP FUNCTION IF EXISTS defs.uoc_resource( TEXT , TEXT , TEXT , INT );
+CREATE FUNCTION defs.uoc_resource(
 			_name TEXT ,
 			_description TEXT ,
 			_category TEXT ,
@@ -246,7 +274,11 @@ GRANT EXECUTE
  * Returns:
  *		?				the result code for the operation
  */
-CREATE OR REPLACE FUNCTION defs.uoc_natres_internal(
+DROP FUNCTION IF EXISTS defs.uoc_natres_internal( TEXT ,  TEXT , TEXT , INT ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION );
+CREATE FUNCTION defs.uoc_natres_internal(
 				_name TEXT ,
 				_description TEXT ,
 				_category TEXT ,
@@ -319,11 +351,7 @@ BEGIN
 			_recovery_avg , _recovery_dev
 		);
 
-		-- Add the resource to all empires
-		INSERT INTO emp.resources ( empire_id , resource_name_id )
-			SELECT name_id , _name_id
-				FROM emp.empires;
-
+		PERFORM defs.add_resource_records( _name_id );
 		RETURN 'CREATED';
 	END IF;
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
index 1a135ea..67e58c1 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
@@ -193,6 +193,10 @@ BEGIN
 					verse.get_raw_production( pnid , 'CASH'::building_output_type )
 				) , verse.get_planet_upkeep( pnid ) );
 
+	-- FIXME: for now, just stick data about resources in the appropriate table
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+		SELECT pnid , resource_name_id FROM defs.resources;
+
 	-- Add planet update records
 	FOR utp IN SELECT x FROM unnest( enum_range( NULL::update_type ) ) AS x
 					WHERE x::text LIKE 'PLANET_%'
diff --git a/legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/020-planet-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/020-planet-resources.sql
new file mode 100644
index 0000000..e20ba17
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/020-planet-resources.sql
@@ -0,0 +1,156 @@
+/*
+ * Test constraints and foreign keys on verse.planet_resources
+ */
+BEGIN;
+	/* We need a pair of resource definitions and a pair of planets. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'testResource' );
+	SELECT _create_raw_planets( 2 , 'testPlanet' );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 16 );
+	
+	SELECT diag_test_name( 'verse.planet_resources - Valid record' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income , pres_upkeep
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			0 , 0
+		); $$
+	);
+	DELETE FROM verse.planet_resources;
+
+	SELECT diag_test_name( 'verse.planet_resources - Inserting with default values' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' )
+		); $$
+	);
+	SELECT diag_test_name( 'verse.planet_resources - Default income = 0' );
+	SELECT is( pres_income , 0::DOUBLE PRECISION )
+		FROM verse.planet_resources;
+	SELECT diag_test_name( 'verse.planet_resources - Default upkeep = 0' );
+	SELECT is( pres_upkeep , 0::DOUBLE PRECISION )
+		FROM verse.planet_resources;
+	
+	SELECT diag_test_name( 'verse.planet_resources - Duplicate record' );
+	SELECT throws_ok($$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' )
+		); $$ , 23505 );
+	
+	SELECT diag_test_name( 'verse.planet_resources - Different planet, same resource' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet2' ) , _get_string( 'testResource1' )
+		); $$
+	);
+	
+	SELECT diag_test_name( 'verse.planet_resources - Different resource, same planet' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource2' )
+		); $$
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Different planet and resource' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet2' ) , _get_string( 'testResource2' )
+		); $$
+	);
+	DELETE FROM verse.planet_resources;
+
+	SELECT diag_test_name( 'verse.planet_resources - NULL planet identifier' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			NULL , _get_string( 'testResource1' )
+		); $$ , 23502
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - NULL resource identifier' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , NULL
+		); $$ , 23502
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - NULL income' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			NULL
+		); $$ , 23502
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - NULL upkeep' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_upkeep
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			NULL
+		); $$ , 23502
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Invalid planet identifier' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_bad_map_name( ) , _get_string( 'testResource1' )
+		); $$ , 23503
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Invalid resource identifier' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_bad_string( )
+		); $$ , 23503
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Income < 0' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			-1
+		); $$ , 23514
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Upkeep > 0' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_upkeep
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			-1
+		); $$ , 23514
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
new file mode 100644
index 0000000..58ba524
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
@@ -0,0 +1,39 @@
+/*
+ * Test the defs.add_resource_records() function
+ */
+BEGIN;
+	/* Remove foreign keys from empires and planets, then insert dummy records
+	 * into both tables.
+	 */
+	ALTER TABLE emp.empires
+		DROP CONSTRAINT fk_empires_name;
+	INSERT INTO emp.empires ( name_id , cash )
+		VALUES ( 1 , 0 );
+
+	ALTER TABLE verse.planets
+		DROP CONSTRAINT fk_planets_name ,
+		DROP CONSTRAINT fk_planets_system;
+	INSERT INTO verse.planets ( name_id , system_id , orbit , picture , population )
+		VALUES ( 1 , 1 , 1 , 1 , 1 );
+
+	/* Create a resource type and call the function */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	SELECT _create_resources( 1 , 'test' );
+	SELECT defs.add_resource_records( _get_string( 'test1' ) );
+	
+	/***** TESTS BEGIN HERE *****/
+	SELECT no_plan( );
+
+	SELECT diag_test_name( 'defs.add_resource_records() - Empire record created' );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.resources
+		WHERE empire_id = 1 AND resource_name_id = _get_string( 'test1' );
+
+	SELECT diag_test_name( 'defs.add_resource_records() - Planet record created' );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM verse.planet_resources
+		WHERE planet_id = 1 AND resource_name_id = _get_string( 'test1' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
index 9dc1a03..2c2d08c 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
@@ -10,7 +10,7 @@ BEGIN;
 	SELECT _create_natural_resources( 1 , 'natRes' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 16 );
+	SELECT plan( 17 );
 
 	SELECT diag_test_name( 'defs.uoc_resource_internal() - creation without category' );
 	SELECT is( defs.uoc_resource_internal( 'test3' , 'test4' , NULL , 1 ) , 'CREATED' );
@@ -70,20 +70,38 @@ BEGIN;
 	SELECT diag_test_name( 'defs.uoc_resource_internal() - weight <= 0' );
 	SELECT is( defs.uoc_resource_internal( 'test1' , 'test2' , NULL , 0 ) , 'BAD_VALUE' );
 	
-	/* Reset resources, create empire */
+	/* Reset resources, create temporary table, replace
+	 * defs.add_resource_records() to make sure it's called when needed (and
+	 * only then)
+	 */
 	DELETE FROM defs.natural_resources;
 	DELETE FROM defs.resources;
+	CREATE TEMPORARY TABLE arr_called( on_id INT ) ON COMMIT DROP;
+	CREATE OR REPLACE FUNCTION defs.add_resource_records( _resource INT )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $$
+		INSERT INTO arr_called VALUES( $1 );
+	$$ LANGUAGE SQL;
 	ALTER TABLE emp.empires
 		DROP CONSTRAINT fk_empires_name;
 	INSERT INTO emp.empires ( name_id , cash )
 		VALUES ( 1 , 0 );
 
-	SELECT diag_test_name( 'defs.uoc_resource_internal() - new resources are added to empires' );
-	SELECT defs.uoc_resource_internal( 'test3' , 'test4' , NULL , 1 );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - defs.add_resource_record() called on new resource' );
+	SELECT defs.uoc_resource_internal( 'test1' , 'test4' , NULL , 1 );
 	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test3' );
+		FROM arr_called
+		WHERE on_id = _get_string( 'test1' );
+	DELETE FROM arr_called;
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - defs.add_resource_record() not called on resource update' );
+	SELECT defs.uoc_resource_internal( 'test1' , 'test3' , NULL , 1 );
+	SELECT is( COUNT(*)::INT , 0 )
+		FROM arr_called
+		WHERE on_id = _get_string( 'test1' );
+	DELETE FROM arr_called;
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
index 3b08cfd..aa6af61 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
@@ -2,110 +2,45 @@
  * Test the defs.uoc_resource() functions
  */
 BEGIN;
-	
-	/* We need a few strings to be used when creating resource definitions, and a natural resource. */
-	\i utils/strings.sql
-	\i utils/resources.sql
-	SELECT _create_test_strings( 7 );
-	SELECT _create_natural_resources( 1 , 'natRes' );
+	/* Create a fake internal function that will just push its parameters to
+	 * a temporary table.
+	 */
+	CREATE TEMPORARY TABLE calls_to_internal(
+		name TEXT , description TEXT , category TEXT , weight INT 
+	) ON COMMIT DROP;
+	CREATE OR REPLACE FUNCTION defs.uoc_resource_internal(
+				_name TEXT ,
+				_description TEXT ,
+				_category TEXT ,
+				_weight INT )
+		RETURNS defs.resource_update_result
+		CALLED ON NULL INPUT
+		VOLATILE
+		SECURITY INVOKER
+	AS $$
+		INSERT INTO calls_to_internal VALUES (
+			$1 , $2 , CASE WHEN $3 IS NULL THEN 'NULL' ELSE $3 END , $4
+		);
+		SELECT 'BAD_STRINGS'::defs.resource_update_result;
+	$$ LANGUAGE SQL;
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 24 );
+	SELECT plan( 2 );
 
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL name (no category)' );
-	SELECT is( defs.uoc_resource( NULL , 'test2' , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL description (no category)' );
-	SELECT is( defs.uoc_resource( 'test1' , NULL , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL weight (no category)' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test2' , NULL ) , NULL );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL name (with category)' );
-	SELECT is( defs.uoc_resource( NULL , 'test2' , 'test3' , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL description (with category)' );
-	SELECT is( defs.uoc_resource( 'test1' , NULL , 'test3' , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL category' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test2' , NULL , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL weight (with category)' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test2' , 'test3' , NULL ) , NULL );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - creation without category' );
-	SELECT is( defs.uoc_resource( 'test3' , 'test4' , 1 ) , 'CREATED' );
-	SELECT diag_test_name( 'defs.uoc_resource() - creation results without category' );
-	SELECT results_eq(
-		$$ SELECT resource_name_id , resource_description_id , resource_weight
-				FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' )
-					AND resource_category_id IS NULL $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 1 ) $$
+	SELECT diag_test_name( 'defs.uoc_resource() - Calls internal function (without category)' );
+	SELECT defs.uoc_resource( 'a' , 'b' , 1 );
+	SELECT set_eq(
+		$$ SELECT * FROM calls_to_internal; $$ ,
+		$$ VALUES( 'a' , 'b' , 'NULL' , 1 ) $$
 	);
+	DELETE FROM calls_to_internal;
 
-	SELECT diag_test_name( 'defs.uoc_resource() - creation with category' );
-	SELECT is( defs.uoc_resource( 'test5' , 'test6' , 'test7' , 1 ) , 'CREATED' );
-	SELECT diag_test_name( 'defs.uoc_resource() - creation results with category' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
-		$$ VALUES ( _get_string( 'test5' ) , _get_string( 'test6' ) , _get_string( 'test7' ) , 1 ) $$
+	SELECT diag_test_name( 'defs.uoc_resource() - Calls internal function (without category)' );
+	SELECT defs.uoc_resource( 'a' , 'b' , 'c' , 1 );
+	SELECT set_eq(
+		$$ SELECT * FROM calls_to_internal; $$ ,
+		$$ VALUES( 'a' , 'b' , 'c' , 1 ) $$
 	);
 
-	SELECT diag_test_name( 'defs.uoc_resource() - update without category' );
-	SELECT is( defs.uoc_resource( 'test3' , 'test7', 2 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_resource() - update results without category' );
-	SELECT results_eq(
-		$$ SELECT resource_name_id , resource_description_id , resource_weight
-				FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' )
-					AND resource_category_id IS NULL $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test7' ) , 2 ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_resource() - update with category' );
-	SELECT is( defs.uoc_resource( 'test3' , 'test4' , 'test7' , 1 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_resource() - update results with category' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , _get_string( 'test7' ) , 1 ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_resource() - incorrect name string' );
-	SELECT is( defs.uoc_resource( 'does-not-exist' , 'test2' , 1 ) , 'BAD_STRINGS' );
-	SELECT diag_test_name( 'defs.uoc_resource() - incorrect description string' );
-	SELECT is( defs.uoc_resource( 'test1' , 'does-not-exist' , 1 ) , 'BAD_STRINGS' );
-	SELECT diag_test_name( 'defs.uoc_resource() - incorrect category string' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test2' , 'does-not-exist' , 1 ) , 'BAD_STRINGS' );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - duplicate description on new resource' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test4' , 1 ) , 'DUP_DESCR' );
-	SELECT diag_test_name( 'defs.uoc_resource() - duplicate description on existing resource' );
-	SELECT is( defs.uoc_resource( 'test5' , 'test4' , 1 ) , 'DUP_DESCR' );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - update on natural resource' );
-	SELECT is( defs.uoc_resource( 'natRes1' , 'test2' , 1 ) , 'BAD_TYPE' );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - weight <= 0' );
-	SELECT is( defs.uoc_resource( 'natRes1' , 'test2' , 0 ) , 'BAD_VALUE' );
-
-	/* Reset resources, create empire */
-	DELETE FROM defs.natural_resources;
-	DELETE FROM defs.resources;
-	ALTER TABLE emp.empires
-		DROP CONSTRAINT fk_empires_name;
-	INSERT INTO emp.empires ( name_id , cash )
-		VALUES ( 1 , 0 );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - new resources are added to empires (no category)' );
-	SELECT defs.uoc_resource( 'test1' , 'test2' , 1 );
-	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test1' );
-	SELECT diag_test_name( 'defs.uoc_resource() - new resources are added to empires' );
-	SELECT defs.uoc_resource( 'test3' , 'test4' , 'test5' , 1 );
-	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test3' );
-
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
index 79f5a3b..f9a67a4 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
@@ -10,7 +10,7 @@ BEGIN;
 	SELECT _create_resources( 1 , 'basicRes' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 38 );
+	SELECT plan( 39 );
 
 	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation without category' );
 	SELECT is( defs.uoc_natres_internal( 'test3' , 'test4' , NULL , 1 ,
@@ -171,21 +171,39 @@ BEGIN;
 	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
 		0.5 , 100 , 50 , 0.5 , 0.05 , 0.75 , 0.5 ) , 'BAD_VALUE' );
 
-	/* Reset resources, create empire */
+	/* Reset resources, create temporary table, replace
+	 * defs.add_resource_records() to make sure it's called when needed (and
+	 * only then)
+	 */
 	DELETE FROM defs.natural_resources;
 	DELETE FROM defs.resources;
+	CREATE TEMPORARY TABLE arr_called( on_id INT ) ON COMMIT DROP;
+	CREATE OR REPLACE FUNCTION defs.add_resource_records( _resource INT )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $$
+		INSERT INTO arr_called VALUES( $1 );
+	$$ LANGUAGE SQL;
 	ALTER TABLE emp.empires
 		DROP CONSTRAINT fk_empires_name;
 	INSERT INTO emp.empires ( name_id , cash )
 		VALUES ( 1 , 0 );
 
-	SELECT diag_test_name( 'defs.uoc_natres_internal() - new resources are added to empires' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - defs.add_resource_record() called on new resource' );
 	SELECT defs.uoc_natres_internal( 'test1' , 'test2' , NULL , 1 ,
 		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
 	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test1' );
+		FROM arr_called
+		WHERE on_id = _get_string( 'test1' );
+	DELETE FROM arr_called;
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - defs.add_resource_record() not called on resource update' );
+	SELECT defs.uoc_natres_internal( 'test1' , 'test3' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
+	SELECT is( COUNT(*)::INT , 0 )
+		FROM arr_called
+		WHERE on_id = _get_string( 'test1' );
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
index 87889fe..b27130c 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
@@ -2,264 +2,65 @@
  * Test the defs.uoc_natural_resource() functions
  */
 BEGIN;
-	
-	/* We need a few strings to be used when creating resource definitions, and a basic resource. */
-	\i utils/strings.sql
-	\i utils/resources.sql
-	SELECT _create_test_strings( 7 );
-	SELECT _create_resources( 1 , 'basicRes' );
+	/* Create a fake internal function that will just push its parameters to
+	 * a temporary table.
+	 */
+	CREATE TEMPORARY TABLE calls_to_internal(
+		name TEXT ,
+		description TEXT ,
+		category TEXT ,
+		weight INT ,
+		presence DOUBLE PRECISION ,
+		quantity_avg DOUBLE PRECISION ,
+		quantity_dev DOUBLE PRECISION ,
+		difficulty_avg DOUBLE PRECISION ,
+		difficulty_dev DOUBLE PRECISION ,
+		recovery_avg DOUBLE PRECISION ,
+		recovery_dev DOUBLE PRECISION
+	) ON COMMIT DROP;
+	CREATE OR REPLACE FUNCTION defs.uoc_natres_internal(
+				_name TEXT ,
+				_description TEXT ,
+				_category TEXT ,
+				_weight INT ,
+				_presence DOUBLE PRECISION ,
+				_quantity_avg DOUBLE PRECISION ,
+				_quantity_dev DOUBLE PRECISION ,
+				_difficulty_avg DOUBLE PRECISION ,
+				_difficulty_dev DOUBLE PRECISION ,
+				_recovery_avg DOUBLE PRECISION ,
+				_recovery_dev DOUBLE PRECISION )
+		RETURNS defs.resource_update_result
+		CALLED ON NULL INPUT
+		VOLATILE
+		SECURITY INVOKER
+	AS $$
+		INSERT INTO calls_to_internal VALUES (
+			$1 , $2 , CASE WHEN $3 IS NULL THEN 'NULL' ELSE $3 END , $4 ,
+			$5 , $6 , $7 , $8 , $9 , $10 , $11
+		);
+		SELECT 'BAD_STRINGS'::defs.resource_update_result;
+	$$ LANGUAGE SQL;
+
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 60 );
+	SELECT plan( 2 );
 
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL name (no category)' );
-	SELECT is( defs.uoc_natural_resource( NULL , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL description (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , NULL , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL weight (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , NULL ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL presence (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		NULL , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average quantity (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , NULL , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL quantity deviation (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , NULL , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average difficulty (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , NULL , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL difficulty deviation (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , NULL , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average recovery (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , NULL , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL recovery deviation (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , NULL ) , NULL );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL name' );
-	SELECT is( defs.uoc_natural_resource( NULL , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL description' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , NULL , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL category' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , NULL , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL weight' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , NULL ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL presence' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		NULL , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average quantity' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , NULL , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL quantity deviation' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , NULL , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average difficulty' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , NULL , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL difficulty deviation' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , NULL , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average recovery' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , NULL , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL recovery deviation' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , NULL ) , NULL );
-
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation without category' );
-	SELECT is( defs.uoc_natural_resource( 'test3' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'CREATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results without category - basic' );
-	SELECT results_eq(
-		$$ SELECT resource_name_id , resource_description_id , resource_weight
-				FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' )
-					AND resource_category_id IS NULL $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 1 ) $$
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - Calls internal function (without category)' );
+	SELECT defs.uoc_natural_resource( 'a' , 'b' , 1 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 );
+	SELECT set_eq(
+		$$ SELECT * FROM calls_to_internal; $$ ,
+		$$ VALUES( 'a' , 'b' , 'NULL' , 1 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 ) $$
 	);
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results without category - nat. res.' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.natural_resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
-			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
-			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
+	DELETE FROM calls_to_internal;
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - Calls internal function (with category)' );
+	SELECT defs.uoc_natural_resource( 'a' , 'b' , 'c' , 1 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 );
+	SELECT set_eq(
+		$$ SELECT * FROM calls_to_internal; $$ ,
+		$$ VALUES( 'a' , 'b' , 'c' , 1 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 ) $$
 	);
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation with category' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test6' , 'test7' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'CREATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results with category - basic' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
-		$$ VALUES ( _get_string( 'test5' ) , _get_string( 'test6' ) , _get_string( 'test7' ) , 1 ) $$
-	);
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results with category - nat. res.' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.natural_resources
-				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
-		$$ VALUES ( _get_string( 'test5' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
-			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
-			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update without category' );
-	SELECT is( defs.uoc_natural_resource( 'test3' , 'test7' , 2 ,
-		0.3 , 300 , 30 , 0.3 , 0.03 , 0.3 , 0.03 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results without category - basic' );
-	SELECT results_eq(
-		$$ SELECT resource_name_id , resource_description_id , resource_weight
-				FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' )
-					AND resource_category_id IS NULL $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test7' ) , 2 ) $$
-	);
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results without category - nat. res.' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.natural_resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , 0.3::DOUBLE PRECISION , 300::DOUBLE PRECISION ,
-			30::DOUBLE PRECISION , 0.3::DOUBLE PRECISION , 0.03::DOUBLE PRECISION ,
-			0.3::DOUBLE PRECISION , 0.03::DOUBLE PRECISION ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update with category' );
-	SELECT is( defs.uoc_natural_resource( 'test3' , 'test4' , 'test7' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results with category' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , _get_string( 'test7' ) , 1 ) $$
-	);
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results with category - nat. res.' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.natural_resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
-			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
-			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect name string' );
-	SELECT is( defs.uoc_natural_resource( 'does-not-exist' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect description string' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'does-not-exist' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect category string' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test2' , 'does-not-exist' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - duplicate description on new resource' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'DUP_DESCR' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - duplicate description on existing resource' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'DUP_DESCR' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update on basic resource' );
-	SELECT is( defs.uoc_natural_resource( 'basicRes1' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_TYPE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - weight <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 0 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - P(presence) <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - P(presence) >= 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - max. quantity <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 0 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - quantity dev. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , -1 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - quantity max. - dev. <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 100 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. = 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0 , 0 , 0.5 , 0.05 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , -0.00001 , 0 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. = 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 1 , 0 , 0.5 , 0.05 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. > 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 1.00001 , 0 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty dev. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , -1 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. - dev. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.25 , 0.5 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. + dev. > 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.75 , 0.5 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0 , 0 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. = 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 1 , 0 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. > 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 1.0001 , 0 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery dev. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , -0.25 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. - dev. <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.25 , 0.25 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. + dev. > 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.75 , 0.5 ) , 'BAD_VALUE' );
-
-	/* Reset resources, create empire */
-	DELETE FROM defs.natural_resources;
-	DELETE FROM defs.resources;
-	ALTER TABLE emp.empires
-		DROP CONSTRAINT fk_empires_name;
-	INSERT INTO emp.empires ( name_id , cash )
-		VALUES ( 1 , 0 );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - new resources are added to empires (no category)' );
-	SELECT defs.uoc_natural_resource( 'test1' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
-	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test1' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - new resources are added to empires' );
-	SELECT defs.uoc_natural_resource( 'test3' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
-	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test3' );
+	DELETE FROM calls_to_internal;
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/020-verse-planet-resources.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/020-verse-planet-resources.sql
new file mode 100644
index 0000000..00fa1e4
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/020-verse-planet-resources.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on verse.planet_resources
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'verse.planet_resources - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO verse.planet_resources( resource_name_id ) VALUES ( 1 ); $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.resource_providers - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE verse.planet_resources SET pres_income = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.planet_resources - SELECT privileges' );
+	SELECT throws_ok(
+		$$ SELECT * FROM verse.planet_resources; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.planet_resources - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM verse.planet_resources; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/005-add-resource-records.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/005-add-resource-records.sql
new file mode 100644
index 0000000..ee0a892
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/005-add-resource-records.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.add_resource_records()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.add_resource_records() - Privileges' );
+	PREPARE _test_this AS
+		SELECT defs.add_resource_records( 1 );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From 04e550709a1929a07877d609b50235dcdd939d73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 10 Jan 2012 13:29:56 +0100
Subject: [PATCH 18/94] Empire resources update

* Added a new update that computes the amount of resources possessed and
owed by empires. At the moment this computation ignores fleets, as their
upkeep does not use the resource system.
---
 .../parts/030-data/000-typedefs.sql           |  13 ++-
 .../050-updates/015-empire-resources.sql      |  63 +++++++++++
 .../010-process-empire-resources-updates.sql  | 101 ++++++++++++++++++
 .../010-process-empire-resources-updates.sql  |  11 ++
 4 files changed, 186 insertions(+), 2 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/015-empire-resources/010-process-empire-resources-updates.sql

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql b/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql
index edcef12..ea6934d 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql
@@ -29,9 +29,18 @@ CREATE TYPE log_type
 -- Update types
 CREATE TYPE update_type	AS ENUM (
 
-	/* Empires' money is being updated using the previous update's results */
+	/* Empires' money is being updated using the previous update's results
+	 * 
+	 * FIXME: this update type should disappear, as everything it does will
+	 * be replaced by the resources update.
+	 */
 	'EMPIRE_MONEY' ,
-	
+
+	/* Empires' resources are being updated using the previous update's
+	 * results.
+	 */
+	'EMPIRE_RESOURCES' ,
+
 	/* Empire research points are being attributed to technologies */
 	'EMPIRE_RESEARCH' ,
 	
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql b/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
new file mode 100644
index 0000000..89c40e2
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
@@ -0,0 +1,63 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates - empire resources
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Empire resources update
+ * 
+ * For each empire in the update batch, compute the new amount of resources
+ * based on the upkeep and income from planets.
+ * 
+ * FIXME: this should also include fleet upkeeps. This situation will be
+ *        resolved once the system is modified to rely solely on resources.
+ * 
+ * Parameters:
+ *		_tick		The identifier of the game update
+ */
+DROP FUNCTION IF EXISTS sys.process_empire_resources_updates( BIGINT );
+CREATE FUNCTION sys.process_empire_resources_updates( _tick BIGINT )
+	RETURNS VOID
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $process_empire_resources_updates$
+
+	UPDATE emp.resources _emp_resources
+
+		SET empres_possessed = CASE
+				WHEN _emp_resources.empres_possessed + _pl_resources.pres_income - _pl_resources.pres_upkeep > 0 THEN
+					_emp_resources.empres_possessed + _pl_resources.pres_income - _pl_resources.pres_upkeep
+				ELSE
+					0
+				END ,
+			empres_owed = CASE
+				WHEN _emp_resources.empres_possessed + _pl_resources.pres_income - _pl_resources.pres_upkeep < 0 THEN
+					 _pl_resources.pres_upkeep - _emp_resources.empres_possessed - _pl_resources.pres_income 
+				ELSE
+					0
+				END
+
+		FROM sys.updates _upd_sys
+			INNER JOIN emp.updates _upd_emp
+				ON _upd_emp.update_id = _upd_sys.id
+			INNER JOIN emp.planets _emp_planets
+				USING ( empire_id )
+			INNER JOIN verse.planet_resources _pl_resources
+				USING ( planet_id )
+
+		WHERE _upd_sys.last_tick = $1
+			AND _upd_sys.status = 'PROCESSING'
+			AND _upd_sys.gu_type = 'EMPIRE_RESOURCES'
+			AND _emp_resources.empire_id = _upd_emp.empire_id
+			AND _emp_resources.resource_name_id = _pl_resources.resource_name_id;
+
+$process_empire_resources_updates$ LANGUAGE SQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION sys.process_empire_resources_updates( BIGINT )
+	FROM PUBLIC;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
new file mode 100644
index 0000000..2b4f57a
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
@@ -0,0 +1,101 @@
+/*
+ * Test the sys.process_empire_resources_updates() function
+ */
+BEGIN;
+	/* We need:
+	 *  - three types of resources,
+	 *  - two empire with resources records, and the corresponding update
+	 *    record,
+	 *  - two planets, set to belong to the aforementioned empires, with
+	 *    resources records attached. 
+	 */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_resources( 3 , 'testResource' );
+	SELECT _create_raw_planets( 2 , 'testPlanet' );
+	SELECT _create_emp_names( 2 , 'testEmp' );
+	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet1' ) , 1 );
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+		SELECT _get_map_name( 'testPlanet1' ) , resource_name_id FROM defs.resources;
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+		SELECT _get_map_name( 'testPlanet2' ) , resource_name_id FROM defs.resources;
+
+	/* Set up the planets' resource records so that:
+	 *	- income > upkeep for testResource1 ,
+	 *	- income < upkeep for testResource2 ,
+	 *	- income = upkeep = 0 for testResource3 ,
+	 */
+	UPDATE verse.planet_resources SET pres_income = 12
+		WHERE resource_name_id = _get_string( 'testResource1' );
+	UPDATE verse.planet_resources SET pres_upkeep = 34
+		WHERE resource_name_id = _get_string( 'testResource2' );
+
+	/* Make sure update 0 is set to be processed for testEmp1 */
+	UPDATE sys.updates
+		SET status = 'PROCESSING' , last_tick = 0;
+
+	/* Create testEmp2 empire, make sure it will not be processed */
+	SELECT emp.create_empire( _get_emp_name( 'testEmp2' ) , _get_map_name( 'testPlanet2' ) , 1 );
+	UPDATE sys.updates _su
+		SET status = 'PROCESSED' , last_tick = 0
+		FROM emp.updates _eu
+			WHERE _eu.update_id = _su.id AND _eu.empire_id = _get_emp_name( 'testEmp2' );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 19 );
+
+	SELECT sys.process_empire_resources_updates( 10 );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Wrong update identifier (1/6)' );
+	SELECT is( empres_possessed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource1' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Wrong update identifier (2/6)' );
+	SELECT is( empres_owed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource1' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Wrong update identifier (3/6)' );
+	SELECT is( empres_possessed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource2' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Wrong update identifier (4/6)' );
+	SELECT is( empres_owed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource2' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Wrong update identifier (5/6)' );
+	SELECT is( empres_possessed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource3' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Wrong update identifier (6/6)' );
+	SELECT is( empres_owed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource3' );
+
+	SELECT sys.process_empire_resources_updates( 0 );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 1 - Possessed quantity increased' );
+	SELECT is( empres_possessed , 12::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource1' )
+			AND empire_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 1 - Owed quantity unchanged' );
+	SELECT is( empres_owed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource1' )
+			AND empire_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 2 - Possessed quantity unchanged' );
+	SELECT is( empres_possessed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource2' )
+			AND empire_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 2 - Owed quantity increased' );
+	SELECT is( empres_owed , 34::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource2' )
+			AND empire_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 3 - Possessed quantity unchanged' );
+	SELECT is( empres_possessed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource3' )
+			AND empire_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 3 - Owed quantity unchanged' );
+	SELECT is( empres_owed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource3' )
+			AND empire_id = _get_emp_name( 'testEmp1' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resources unchanged for already processed empire' );
+	SELECT is( SUM( empres_owed + empres_possessed ) , 0::DOUBLE PRECISION )
+		FROM emp.resources
+		WHERE empire_id = _get_emp_name( 'testEmp2' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/015-empire-resources/010-process-empire-resources-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
new file mode 100644
index 0000000..80c7ff8
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.process_empire_resources_updates()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.process_empire_resources_updates() - Privileges' );
+	SELECT throws_ok( 'SELECT sys.process_empire_resources_updates( 1 )' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From c18bdc2d1f1a01de444fad23b46aee830b32c87e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 14 Jan 2012 09:02:24 +0100
Subject: [PATCH 19/94] Dirty SQL test system

* Added a system that allows "dirty" tests to be written. These tests
actually need to change the database, so a temporary test database must
be created to run them.
---
 build/post-build.sh                           |  7 ++-
 .../000-dirty-tests-self-check/prepare.sql    | 23 +++++++++
 .../000-dirty-tests-self-check/run-test.sql   | 17 +++++++
 .../tests/dirty/run-dirty-tests.sh            | 37 +++++++++++++++
 legacyworlds/doc/database.txt                 | 47 +++++++++++++------
 5 files changed, 114 insertions(+), 17 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/prepare.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/run-test.sql
 create mode 100755 legacyworlds-server-data/db-structure/tests/dirty/run-dirty-tests.sh

diff --git a/build/post-build.sh b/build/post-build.sh
index 3fb786e..1f9c8fb 100755
--- a/build/post-build.sh
+++ b/build/post-build.sh
@@ -9,8 +9,8 @@ user=tests
 password=tests
 EOF
 fi
-TEST_DATABASE="`grep ^db= db-config.txt | sed -e s/.*=//`"
-TEST_USER="`grep ^user= db-config.txt | sed -e s/.*=//`"
+export TEST_DATABASE="`grep ^db= db-config.txt | sed -e s/.*=//`"
+export TEST_USER="`grep ^user= db-config.txt | sed -e s/.*=//`"
 
 echo
 echo
@@ -53,3 +53,6 @@ fi
 if [ -d user ]; then
 	pg_prove -U $TEST_USER -d $TEST_DATABASE `find user/ -type f -name '*.sql'` || exit 1
 fi
+if [ -x dirty/run-dirty-tests.sql ]; then
+	( cd dirty; exec ./run-dirty-tests.sql ) || exit 1
+fi
diff --git a/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/prepare.sql b/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/prepare.sql
new file mode 100644
index 0000000..d7d0d76
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/prepare.sql
@@ -0,0 +1,23 @@
+/*
+ * Dirty test system self-check
+ *
+ * Insert an address, it should exist during the main test. Also create a
+ * function and a table which are used to synchronise execution.
+ */
+BEGIN;
+	INSERT INTO users.addresses ( address )
+		VALUES ( 'prepare@example.org' );
+
+	CREATE TABLE _barriers( _barrier BIGINT );
+
+	CREATE FUNCTION _synchronise_tests( _lock BIGINT )
+		RETURNS VOID
+	AS $$
+	BEGIN
+		WHILE pg_try_advisory_lock( _lock )
+		LOOP
+			PERFORM pg_advisory_unlock( _lock );
+		END LOOP;
+	END;
+	$$ LANGUAGE PLPGSQL;
+COMMIT;
diff --git a/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/run-test.sql b/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/run-test.sql
new file mode 100644
index 0000000..6786ab2
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/run-test.sql
@@ -0,0 +1,17 @@
+/*
+ * Dirty test system self-check
+ *
+ * Insert an address, it should exist during the main test.
+ */
+BEGIN;
+	SELECT no_plan( );
+
+	SELECT pg_advisory_lock( 1 );
+
+	SELECT diag_test_name( 'Dirty test system self-check - prepare.sql was executed' );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM users.addresses
+		WHERE address = 'prepare@example.org';
+
+	SELECT * FROM finish( );
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/dirty/run-dirty-tests.sh b/legacyworlds-server-data/db-structure/tests/dirty/run-dirty-tests.sh
new file mode 100755
index 0000000..07f1cf4
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/dirty/run-dirty-tests.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+if [ -z "$TEST_DATABASE" ]; then
+	echo "Missing TEST_DATABASE environment variable"
+	exit 1;
+fi
+
+echo
+echo 'Running "dirty" database tests'
+echo
+
+find -mindepth 1 -maxdepth 1 -type d | sort | while read testdir; do
+
+	[ -f "$testdir/run-test.sql" ] || continue
+
+	echo "Dirty test $testdir"
+	echo "--------------------------------------------"
+	echo
+
+	echo "Creating temporary database ..."
+	echo "DROP DATABASE IF EXISTS dirty_tests; CREATE DATABASE dirty_tests TEMPLATE $TEST_DATABASE;" \
+		| psql -vQUIET=1 -vON_ERROR_STOP=1 $TEST_DATABASE || exit 1
+
+	if [ -f "$testdir/prepare.sql" ]; then
+		echo "Preparing database ..."
+		( cd .. && exec psql -vQUIET=1 -vON_ERROR_STOP=1 --file "dirty/$testdir/prepare.sql" dirty_tests ) || exit 1
+	fi
+
+	echo "Running main test ..."
+	( cd .. && pg_prove -d dirty_tests dirty/$testdir/run-test.sql ) || exit 1
+
+	echo "Dropping temporary database ..."
+	echo -e "DROP DATABASE IF EXISTS dirty_tests;" \
+		| psql -vQUIET=1 -vON_ERROR_STOP=1 $TEST_DATABASE || exit 1
+
+	echo
+done
diff --git a/legacyworlds/doc/database.txt b/legacyworlds/doc/database.txt
index 4d035a4..15ae48e 100644
--- a/legacyworlds/doc/database.txt
+++ b/legacyworlds/doc/database.txt
@@ -60,22 +60,33 @@ The tests/ sub-directory contains the SQL source code for the pgTAP testing
 framework as well as the tests themselves. See below for more information.
  
  
- Unit tests
- ----------
+Unit tests
+----------
  
-There are three sub-directories in the tests/ directory. The admin/ directory
+There are four sub-directories in the tests/ directory. The admin/ directory
 contains tests that require administrative permissions on the database
 (therefore most functionality checks can be found there), while the user/
 sub-directory contains unit tests that must be executed as the standard
-user (for example privileges checks). Finally, the utils/ sub-directory 
-contains scripts used by tests from the admin/ directory to create test
-data.
+user (for example privileges checks).
+
+The dirty/ sub-directory contains tests which require actual changes to
+be committed to the database; while unit tests are not supposed to be executed
+on a loaded database anyway, these specific tests could cause problems with
+other tests, and therefore run on copies of the database.
+
+Finally, the utils/ sub-directory contains scripts used by tests from both the
+admin/ and dirty/ sub-directories to create test data.
  
-In both directories, files are organised in a manner that is parallel to the
-contents of the database creation scripts. For each actual SQL file, a
-sub-directory with the same name (minus the ".sql" extension) can be created,
-each sub-directory containing the test suites for the definitions and
-functions from the corresponding file.
+In both the admin/ and user/ directories, files are organised in a manner that
+is parallel to the contents of the database creation scripts. For each actual
+SQL file, a sub-directory with the same name (minus the ".sql" extension) can
+be created, each sub-directory containing the test suites for the definitions
+and functions from the corresponding file.
+
+The dirty/ sub-directory contains a script which can be used to run the
+"dirty" test suites, as well as one directory per test suite. Each test suite
+directory may contain a "prepare.sql" script, to be executed before the actual
+tests, as well as a "run-test.sql" which runs the actual tests.
  
 In order to run the database unit tests, the following steps must be taken:
  
@@ -93,14 +104,20 @@ In order to run the database unit tests, the following steps must be taken:
 4) The tests/pgtap.sql script must be loaded into the database as the
    administrative user.
 
-At this point, it becomes possible to launch the test suites by issuing a
+At this point, it becomes possible to launch the basic test suites by issuing a
 command similar to:
 
 		pg_prove -d $DATABASE -U $USER \
 				`find $DIR/ -type f -name '*.sql' | sort`
 
 where $DATABASE is the name of the database, $USER the name of the user that
-will execute the tests and $DIR being either admin or user.
+will execute the tests and $DIR being either admin or user. The "dirty" test
+suite can be launched by running:
+
+		TEST_DATABASE=$DATABASE ./run-dirty-tests.sh
+
+Note that the dirty tests will fail unless all existing connections to the
+main database are closed.
 
 
 Build system
@@ -109,5 +126,5 @@ Build system
 The build system will attempt to create the database using the scripts. It will
 stop at the first unsuccessful command. On success, it will proceed to loading
 pgTAP, then run all available unit tests: first it will execute tests from the
-admin/ sub-directory, then tests from the user/ sub-directory. A failure will
-cause the build to be aborted. 
\ No newline at end of file
+admin/ sub-directory, then tests from the user/ sub-directory, and finally the
+dirty tests. A failure will cause the build to be aborted. 

From 038bba896af8fd78f75a4321d27f3e8496b3fee2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 10 Jan 2012 16:28:25 +0100
Subject: [PATCH 20/94] Mining computations update

* Added a few that can be used to retrieve mining settings for resource
providers from empire-owned planets from either the empire-wide or
planet-specific settings
---
 .../040-functions/145-resource-providers.sql  |  40 +++++++
 .../020-mining-settings-view.sql              | 107 ++++++++++++++++++
 .../020-mining-settings-view.sql              |  11 ++
 3 files changed, 158 insertions(+)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/020-mining-settings-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/020-mining-settings-view.sql

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
index b79d4fd..aec2a56 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
@@ -59,3 +59,43 @@ REVOKE EXECUTE
 		DOUBLE PRECISION , DOUBLE PRECISION ,
 		DOUBLE PRECISION )
 	FROM PUBLIC;
+
+
+
+/*
+ * Mining settings view
+ * ---------------------
+ *
+ * This view lists mining settings being used on planets owned by empires
+ * for each resource providers. The settings are taken from planet-specific
+ * settings if they are available, or from empire-wide settings.
+ * 
+ * Columns:
+ *
+ *		planet_id			The planet's identifier
+ *		resource_name_id	The type of resources
+ *		mset_weight			The setting to use for mining priorities
+ *		mset_specific		True if the settings are specific for this planet,
+ *								false if empire-wise settings are in use. 
+ */
+DROP VIEW IF EXISTS emp.mining_settings_view CASCADE;
+CREATE VIEW emp.mining_settings_view
+	AS SELECT planet_id , resource_name_id ,
+				( CASE
+					WHEN _pl_settings.planet_id IS NULL THEN
+						_emp_settings.empmset_weight
+					ELSE
+						_pl_settings.emppmset_weight
+				END ) AS mset_weight ,
+				( _pl_settings.planet_id IS NOT NULL ) AS mset_specific
+			FROM verse.resource_providers
+				INNER JOIN emp.planets
+					USING ( planet_id )
+				INNER JOIN emp.mining_settings _emp_settings
+					USING ( empire_id , resource_name_id )
+				LEFT OUTER JOIN emp.planet_mining_settings _pl_settings
+					USING ( planet_id , empire_id , resource_name_id );
+
+GRANT SELECT
+	ON emp.mining_settings_view
+	TO :dbuser;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/020-mining-settings-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/020-mining-settings-view.sql
new file mode 100644
index 0000000..0c60575
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/020-mining-settings-view.sql
@@ -0,0 +1,107 @@
+/*
+ * Test the emp.mining_settings_view view
+ */
+BEGIN;
+	/* Create 1 natural resource, 3 planets and 2 empire names */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 1 , 'testResource' );
+	SELECT _create_raw_planets( 3 , 'testPlanet' );
+	SELECT _create_emp_names( 2 , 'testEmp' );
+
+	/*
+	 * Create an empire which possesses a planet, but has no planet-specific
+	 * mining settings. Modify the default empire-wide settings so we can
+	 * check the results.
+	 * 
+	 * The planet only has a resource provider for one type of resource.
+	 */
+	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet1' ) , 1 );
+	UPDATE emp.mining_settings
+		SET empmset_weight = 3
+		WHERE empire_id = _get_emp_name( 'testEmp1' )
+			AND resource_name_id = _get_string( 'testResource1' );
+	INSERT INTO verse.resource_providers ( planet_id , resource_name_id ,
+			resprov_quantity_max , resprov_quantity ,
+			resprov_difficulty , resprov_recovery )
+		VALUES (
+			 _get_map_name( 'testPlanet1' ) ,  _get_string( 'testResource1' ) ,
+			 100 , 50 ,
+			 0.5 , 0.5
+		);
+
+	/*
+	 * Add a resource provider to the second planet; create an empire which
+	 * owns the planet and has planet-specific settings for the resource
+	 * provider.
+	 * 
+	 * This empire also has settings for the testPlanet1 planet.
+	 */
+	INSERT INTO verse.resource_providers ( planet_id , resource_name_id ,
+			resprov_quantity_max , resprov_quantity ,
+			resprov_difficulty , resprov_recovery )
+		VALUES (
+			 _get_map_name( 'testPlanet2' ) ,  _get_string( 'testResource1' ) ,
+			 100 , 50 ,
+			 0.5 , 0.5
+		);
+	SELECT emp.create_empire( _get_emp_name( 'testEmp2' ) , _get_map_name( 'testPlanet2' ) , 1 );
+	INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id , resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'testEmp2' ) , _get_map_name( 'testPlanet2' ) ,
+			_get_string( 'testResource1' ) , 0
+		) , (
+			_get_emp_name( 'testEmp2' ) , _get_map_name( 'testPlanet1' ) ,
+			_get_string( 'testResource1' ) , 0
+		);
+
+	/*
+	 * While the last planet is not owned by any empire, make sure it has a
+	 * resource provider.
+	 */
+	INSERT INTO verse.resource_providers ( planet_id , resource_name_id ,
+			resprov_quantity_max , resprov_quantity ,
+			resprov_difficulty , resprov_recovery )
+		VALUES (
+			 _get_map_name( 'testPlanet3' ) ,  _get_string( 'testResource1' ) ,
+			 100 , 50 ,
+			 0.5 , 0.5
+		);
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 6 );
+
+	SELECT diag_test_name( 'emp.mining_settings_view - Ignore neutral planets' );
+	SELECT is( COUNT(*)::INT , 0 )
+		FROM emp.mining_settings_view
+		WHERE planet_id = _get_map_name( 'testPlanet3' );
+
+	SELECT diag_test_name( 'emp.mining_settings_view - Only display provided resources' );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.mining_settings_view
+		WHERE planet_id = _get_map_name( 'testPlanet1' );
+	SELECT diag_test_name( 'emp.mining_settings_view - Settings from empire-wide table' );
+	SELECT ok( NOT mset_specific )
+		FROM emp.mining_settings_view
+		WHERE planet_id = _get_map_name( 'testPlanet1' );
+	SELECT diag_test_name( 'emp.mining_settings_view - Value from empire-wide table' );
+	SELECT is( mset_weight , 3 )
+		FROM emp.mining_settings_view
+		WHERE planet_id = _get_map_name( 'testPlanet1' );
+		
+	SELECT diag_test_name( 'emp.mining_settings_view - Settings from planet-specific table' );
+	SELECT ok( mset_specific )
+		FROM emp.mining_settings_view
+		WHERE planet_id = _get_map_name( 'testPlanet2' );
+	SELECT diag_test_name( 'emp.mining_settings_view - Value from planet-specific table' );
+	SELECT is( mset_weight , 0 )
+		FROM emp.mining_settings_view
+		WHERE planet_id = _get_map_name( 'testPlanet2' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/020-mining-settings-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/020-mining-settings-view.sql
new file mode 100644
index 0000000..6540213
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/020-mining-settings-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on emp.mining_settings_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.mining_settings_view - Privileges' );
+	SELECT lives_ok( 'SELECT * FROM emp.mining_settings_view' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From 74b6f2ab0984fe95fa4b44e14a47f0c84790e6c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 16 Jan 2012 12:35:20 +0100
Subject: [PATCH 21/94] Mining computation update

* Added various views and helper functions used by the mining
computation but which may be re-used in other parts.

* Added mining computation update type and associated update function

* New constants: game.resources.weightBase (the value used to compute
weights from mining settings) and game.resources.extraction (the
quantity extracted in a day from a full provider at difficulty 0)
---
 .../lw/beans/sys/ConstantsRegistrarBean.java  |   7 +
 .../parts/030-data/000-typedefs.sql           |   5 +-
 .../040-functions/145-resource-providers.sql  |  42 ++-
 .../parts/050-updates/120-planet-mining.sql   | 286 ++++++++++++++++++
 .../030-get-extraction-factor.sql             |  89 ++++++
 .../010-gu-pmc-weights-view.sql               |  44 +++
 .../020-gu-pmc-totals-view.sql                |  35 +++
 .../120-planet-mining/030-gu-pmc-get-data.sql |  89 ++++++
 .../040-gu-pmc-update-resource.sql            |  91 ++++++
 .../050-process-planet-mining-updates.sql     |  58 ++++
 .../dirty/010-mining-update-locks/prepare.sql |   8 +
 .../010-mining-update-locks/run-test.sql      | 104 +++++++
 .../030-get-extraction-factor.sql             |  13 +
 .../010-gu-pmc-weights-view.sql               |  11 +
 .../020-gu-pmc-totals-view.sql                |  11 +
 .../120-planet-mining/030-gu-pmc-get-data.sql |  11 +
 .../040-gu-pmc-update-resource.sql            |  13 +
 .../050-process-planet-mining-updates.sql     |  11 +
 .../setup-gu-pmc-get-data-test.sql            | 170 +++++++++++
 .../db-structure/tests/utils/locks-finder.sql |  43 +++
 20 files changed, 1139 insertions(+), 2 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/030-get-extraction-factor.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/dirty/010-mining-update-locks/prepare.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/dirty/010-mining-update-locks/run-test.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/030-get-extraction-factor.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/utils/locks-finder.sql

diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 38960a0..598e896 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -61,6 +61,13 @@ public class ConstantsRegistrarBean
 		cDesc = "Resource recovery dampener. Lower means less dampening";
 		defs.add( new ConstantDefinition( "game.resources.recoveryDampening" , "Natural resources" , cDesc , 1.5 , 1.0 ,
 				true ) );
+		cDesc = "Resource weight base value. This value is taken to the Xth power to compute the actual "
+				+ "weight when determining how mining work is distributed amongst a planet's resource providers.";
+		defs.add( new ConstantDefinition( "game.resources.weightBase" , "Natural resources" , cDesc , 10.0 , 1.1 ,
+				100.0 ) );
+		cDesc = "Resources extracted per work unit, per (real) day, from a full provider with minimal difficulty.";
+		defs.add( new ConstantDefinition( "game.resources.extraction" , "Natural resources" , cDesc , 1.0 , 1.0 ,
+				true ) );
 
 		// Happiness
 		String[] hcNames = {
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql b/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql
index ea6934d..ae64132 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/000-typedefs.sql
@@ -83,7 +83,10 @@ CREATE TYPE update_type	AS ENUM (
 	'PLANET_RES_REGEN' ,
 
 	/* Compute income and upkeep of planets */
-	'PLANET_MONEY'
+	'PLANET_MONEY' ,
+	
+	/* Compute mining results for planets owned by empires */
+	'PLANET_MINING'
 );
 
 -- Types of recapitulative e-mail messages
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
index aec2a56..0dfccb2 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
@@ -98,4 +98,44 @@ CREATE VIEW emp.mining_settings_view
 
 GRANT SELECT
 	ON emp.mining_settings_view
-	TO :dbuser;
\ No newline at end of file
+	TO :dbuser;
+
+
+
+/*
+ * Compute a resource provider's extraction factor
+ * 
+ * This function computes the extraction factor - a multiplier which makes
+ * mining more costly if the difficulty is high or if a provider is almost
+ * empty - based on a provider's fill ratio and difficulty.
+ * 
+ * The complete formula can be read on the Wiki:
+ *		https://wiki.legacyworlds.com/wiki/Mining#Resource_provider_extraction
+ *
+ * Parameters:
+ * 		_fill_ratio		The ratio between the provider's current and maximal
+ *							quantities
+ *		_difficulty		The provider's extraction difficulty.
+ *
+ * Returns:
+ *		?				The provider's extraction factor
+ */
+DROP FUNCTION IF EXISTS verse.get_extraction_factor( 
+	DOUBLE PRECISION , DOUBLE PRECISION );
+CREATE FUNCTION verse.get_extraction_factor(
+		_fill_ratio DOUBLE PRECISION ,
+		_difficulty	DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
+	STRICT IMMUTABLE
+	SECURITY INVOKER
+AS $get_extraction_factor$
+
+	SELECT ( 1 - $2 * 0.5 ) * POW( $1 , 1.5 + 2 * $2 );
+
+$get_extraction_factor$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.get_extraction_factor( 
+		DOUBLE PRECISION , DOUBLE PRECISION )
+	FROM PUBLIC;
+
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
new file mode 100644
index 0000000..dd16737
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
@@ -0,0 +1,286 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates - planet mining computations
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Mining computation weights view
+ * --------------------------------
+ * 
+ * This view computes the actual values used in the mining computations for
+ * each resource provider on all empire-owned planets.
+ * 
+ * Columns:
+ *		planet_id			The planet's identifier
+ *		resource_name_id	The resource type's identifier
+ *		pmc_weight			The computed weight
+ */
+DROP VIEW IF EXISTS sys.gu_pmc_weights_view CASCADE;
+CREATE VIEW sys.gu_pmc_weights_view
+	AS SELECT planet_id , resource_name_id ,
+				POW( sys.get_constant( 'game.resources.weightBase' ) ,
+					mset_weight ) AS pmc_weight
+			FROM emp.mining_settings_view;
+
+
+/*
+ * Mining computation - total weights view
+ * ----------------------------------------
+ *
+ * This view computes per-planet totals for actual mining weights.
+ * 
+ * Columns:
+ *		planet_id			The planet's identifier
+ *		pmc_total			The sum of all mining weights on the planet.
+ */
+DROP VIEW IF EXISTS sys.gu_pmc_totals_view CASCADE;
+CREATE VIEW sys.gu_pmc_totals_view
+	AS SELECT planet_id , SUM( pmc_weight ) AS pmc_total
+			FROM sys.gu_pmc_weights_view
+			GROUP BY planet_id;
+
+
+/*
+ * Planet mining update data
+ * --------------------------
+ * 
+ * This type is used by the records used by planet mining updates to compute a
+ * planet's mining output.
+ */
+DROP TYPE IF EXISTS sys.gu_pmc_data_type CASCADE;
+CREATE TYPE sys.gu_pmc_data_type AS (
+	/* The planet's identifier */
+	planet			INT ,
+
+	/* The resource's identifier */
+	resource		INT ,
+
+	/* The provider's quantity of resources */
+	quantity		DOUBLE PRECISION ,
+
+	/* The provider's maximal quantity of resources */
+	quantity_max	DOUBLE PRECISION ,
+
+	/* The provider's extraction difficulty */
+	difficulty		DOUBLE PRECISION ,
+
+	/* The empire who owns the planet, or NULL if the planet is neutral */
+	empire			INT ,
+
+	/* The planet's happiness, or NULL if the planet is neutral */
+	happiness		REAL ,
+
+	/* The weight computed from the resource's extraction priority as
+	 * set by either the empire in general or the planet-specific settings,
+	 * or NULL if the planet is neutral.
+	 */
+	weight			DOUBLE PRECISION ,
+
+	/* The total weight computed from either the empire-wide or the
+	 * planet-specific mining settings, or NULL if the planet is neutral.
+	 */
+	total_weight	DOUBLE PRECISION
+);
+
+
+
+/*
+ * Lock the rows and access the data used by the mining update
+ * 
+ * This function executes a single query which serves the dual purpose of
+ * locking all rows from various tables used by the planet mining update
+ * and returning the data needed by the computation.
+ *
+ * As far as locking is concerned, the following tables are locked: 
+ *	- Update records are already locked, so we don't care.
+ *	- Planets with resource providers are locked for share.
+ *	- Resource providers and corresponding resource records are locked
+ *		for update
+ *	- Resource definitions are locked for share.
+ *	- Owning empires, as well as their mining settings and any set of
+ *		planet-specific settings for one of the planets we're inspecting,
+ *		are locked for share.
+ *
+ * The data itself is returned as a set of rows using sys.gu_pmc_data_type 
+ * 
+ * Parameters:
+ *		_tick			The identifier of the current game update
+ */ 
+DROP FUNCTION IF EXISTS sys.gu_pmc_get_data( BIGINT );
+CREATE FUNCTION sys.gu_pmc_get_data( _tick BIGINT )
+	RETURNS SETOF sys.gu_pmc_data_type
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $gu_pmc_get_data$
+	SELECT planet_id AS planet ,
+			resource_name_id AS resource,
+			resprov_quantity AS quantity ,
+			resprov_quantity_max AS quantity_max ,
+			resprov_difficulty AS difficulty ,
+			empire_id AS empire ,
+			happiness ,
+			pmc_weight AS weight ,
+			pmc_total AS total_weight
+
+		FROM sys.updates _upd_sys
+			INNER JOIN verse.updates _upd_verse
+				ON _upd_sys.id = _upd_verse.update_id
+			INNER JOIN verse.planets _planet
+				ON _planet.name_id = _upd_verse.planet_id
+			INNER JOIN verse.resource_providers _resprov
+				USING ( planet_id )
+			INNER JOIN verse.planet_resources _pres
+				USING ( planet_id , resource_name_id )
+			LEFT OUTER JOIN (
+						SELECT _emp_planet.empire_id , _emp_planet.planet_id ,
+								_emset.resource_name_id , pmc_weight ,
+								pmc_total , _happ.current AS happiness
+		
+							FROM sys.updates _upd_sys
+								INNER JOIN verse.updates _upd_verse
+									ON _upd_sys.id = _upd_verse.update_id
+								INNER JOIN emp.planets _emp_planet
+									USING ( planet_id ) 
+								INNER JOIN emp.empires _emp
+									ON _emp_planet.empire_id = _emp.name_id
+								INNER JOIN emp.mining_settings _emset
+									USING ( empire_id )
+								INNER JOIN verse.planet_happiness _happ
+									USING ( planet_id )
+								INNER JOIN sys.gu_pmc_weights_view
+									USING ( planet_id , resource_name_id)
+								INNER JOIN sys.gu_pmc_totals_view
+									USING ( planet_id )
+								LEFT OUTER JOIN (
+											SELECT * FROM emp.planet_mining_settings
+												FOR SHARE
+										) AS _pmset
+									USING ( empire_id , planet_id , resource_name_id )
+	
+							WHERE _upd_sys.last_tick = $1
+								AND _upd_sys.status = 'PROCESSING'
+								AND _upd_sys.gu_type = 'PLANET_MINING'
+
+							FOR SHARE OF _emp_planet , _emp , _emset , _happ 
+					) AS _owner
+				USING ( planet_id , resource_name_id )
+	
+		WHERE _upd_sys.last_tick = $1
+			AND _upd_sys.status = 'PROCESSING'
+			AND _upd_sys.gu_type = 'PLANET_MINING'
+	
+		FOR UPDATE OF _resprov , _pres
+		FOR SHARE OF _planet ;
+$gu_pmc_get_data$ LANGUAGE SQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION sys.gu_pmc_get_data( BIGINT )
+	FROM PUBLIC;
+
+
+
+/*
+ * Update a planet's resource provider and corresponding resource record
+ *
+ * This function will compute the amount of resources extracted from a
+ * provider, and update both the provider itself and the corresponding
+ * resource record (setting the income to whatever quantity was extracted).
+ * 
+ * Parameters:
+ *		_input		Data about the resource provider to update
+ */
+DROP FUNCTION IF EXISTS sys.gu_pmc_update_resource( sys.gu_pmc_data_type );
+CREATE FUNCTION sys.gu_pmc_update_resource( _input sys.gu_pmc_data_type )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $gu_pmc_update_resource$
+
+DECLARE
+	_extraction	DOUBLE PRECISION;
+	_allocation	DOUBLE PRECISION;
+	_production	DOUBLE PRECISION;
+	_quantity	DOUBLE PRECISION;
+
+BEGIN
+
+	_extraction := _input.quantity * verse.get_extraction_factor(
+		_input.quantity / _input.quantity_max ,
+		_input.difficulty );
+	_allocation := _input.weight / _input.total_weight;
+	_production := verse.adjust_production(
+			verse.get_raw_production( _input.planet , 'MINE' ) ,
+			_input.happiness )
+		* sys.get_constant( 'game.resources.extraction' )
+		/ 1440.0; -- FIXME: hardcoded!
+
+	RAISE NOTICE 'Extraction % , allocation % , production %' , _extraction , _allocation , _production;
+	_quantity := _allocation * _production * _extraction;
+	IF _quantity > _input.quantity THEN
+		_quantity := _input.quantity;
+	END IF;
+	
+	UPDATE verse.resource_providers
+		SET resprov_quantity = resprov_quantity - _quantity
+		WHERE planet_id = _input.planet
+			AND resource_name_id = _input.resource;
+	UPDATE verse.planet_resources
+		SET pres_income = _quantity
+		WHERE planet_id = _input.planet
+			AND resource_name_id = _input.resource;
+
+END;
+$gu_pmc_update_resource$ LANGUAGE PLPGSQL;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.gu_pmc_update_resource( sys.gu_pmc_data_type )
+	FROM PUBLIC;
+
+
+
+/*
+ * Planet mining game update
+ * 
+ * Obtains all records to update for the current batch, then either set the
+ * income to 0 without modifying the resource provider if the planet is
+ * neutral, or call gu_pmc_update_resource() if there is an owner.
+ * 
+ * Parameters:
+ *		_tick		The current tick's identifier
+ */
+DROP FUNCTION IF EXISTS sys.process_planet_mining_updates( BIGINT );
+CREATE FUNCTION sys.process_planet_mining_updates( _tick BIGINT )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $process_planet_mining_updates$
+
+DECLARE
+	_row		sys.gu_pmc_data_type;
+
+BEGIN
+	FOR _row IN SELECT * FROM sys.gu_pmc_get_data( _tick )
+	LOOP
+		IF _row.empire IS NULL THEN
+			-- Set resource income to 0 on neutrals
+			UPDATE verse.planet_resources
+				SET pres_income = 0
+				WHERE planet_id = _row.planet
+					AND resource_name_id = _row.resource;
+		ELSE
+			PERFORM sys.gu_pmc_update_resource( _row );
+		END IF;
+
+	END LOOP;
+END;
+$process_planet_mining_updates$ LANGUAGE PLPGSQL;
+
+
+REVOKE EXECUTE
+	ON FUNCTION sys.process_planet_mining_updates( BIGINT )
+	FROM PUBLIC;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/030-get-extraction-factor.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/030-get-extraction-factor.sql
new file mode 100644
index 0000000..9a9bb34
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/030-get-extraction-factor.sql
@@ -0,0 +1,89 @@
+/*
+ * Test the verse.get_extraction_factor() function
+ */
+BEGIN;
+	/* Drop foreign keys on resource providers, then fill the table with
+	 * values which can be used to test some fundamental properties of
+	 * the computation, then create a view that returns computation results.
+	 */
+	ALTER TABLE verse.resource_providers
+		DROP CONSTRAINT fk_resprov_planet ,
+		DROP CONSTRAINT fk_resprov_resource;
+
+	CREATE FUNCTION _fill_providers( )
+			RETURNS VOID
+		AS $$
+	DECLARE
+		i	INT;
+		j	INT;
+	BEGIN
+		FOR i IN 0 .. 10
+		LOOP
+			FOR j IN 0 .. 10
+			LOOP
+				INSERT INTO verse.resource_providers(
+						planet_id , resource_name_id ,
+						resprov_quantity_max , resprov_quantity ,
+						resprov_difficulty , resprov_recovery
+					) VALUES (
+						i , j , 100 , j * 10 , i * 0.1 , 0.5
+					);
+			END LOOP;
+		END LOOP;
+	END;
+	$$ LANGUAGE PLPGSQL;
+	SELECT _fill_providers( );
+	DROP FUNCTION _fill_providers( );
+	
+	CREATE VIEW extraction_factor_view
+		AS SELECT planet_id , resource_name_id ,
+				verse.get_extraction_factor(
+					resprov_quantity / resprov_quantity_max ,
+					resprov_difficulty
+				) AS resprov_extraction
+			FROM verse.resource_providers;
+	
+	
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 6 );
+
+	SELECT diag_test_name( 'verse.get_extraction_factor() - Range');
+	SELECT is_empty($$
+		SELECT * FROM extraction_factor_view
+			WHERE resprov_extraction NOT BETWEEN 0 AND 1
+	$$);
+
+	SELECT diag_test_name( 'verse.get_extraction_factor() - Full provider with difficulty 0 => extraction is 1');
+	SELECT is( resprov_extraction , 1.0::DOUBLE PRECISION )
+		FROM extraction_factor_view
+		WHERE planet_id = 0 AND resource_name_id = 10;
+	SELECT diag_test_name( 'verse.get_extraction_factor() - Full provider with difficulty 1 => extraction is 0.5');
+	SELECT is( resprov_extraction , 0.5::DOUBLE PRECISION )
+		FROM extraction_factor_view
+		WHERE planet_id = 10 AND resource_name_id = 10;
+	SELECT diag_test_name( 'verse.get_extraction_factor() - Empty provider => extraction is 0');
+	SELECT is_empty($$
+		SELECT * FROM extraction_factor_view
+			WHERE resource_name_id = 0 AND resprov_extraction > 0
+	$$);
+
+	SELECT diag_test_name( 'verse.get_extraction_factor() - At same quantity ratio, higher difficulty => lower extraction');
+	SELECT is_empty($$
+		SELECT * FROM extraction_factor_view v1
+				INNER JOIN extraction_factor_view v2
+					ON v1.resource_name_id = v2.resource_name_id
+			WHERE v1.planet_id > v2.planet_id
+				AND v1.resprov_extraction > v2.resprov_extraction
+	$$);
+
+	SELECT diag_test_name( 'verse.get_extraction_factor() - At same difficulty, higher quantity ratio => higher extraction');
+	SELECT is_empty($$
+		SELECT * FROM extraction_factor_view v1
+				INNER JOIN extraction_factor_view v2
+					ON v1.planet_id = v2.planet_id
+			WHERE v1.resource_name_id > v2.resource_name_id
+				AND v1.resprov_extraction < v2.resprov_extraction
+	$$);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
new file mode 100644
index 0000000..d32e62f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
@@ -0,0 +1,44 @@
+/*
+ * Test sys.gu_pmc_weights_view
+ */
+BEGIN;
+	/* Create a table which will server as an alternate source for
+	 * emp.mining_settings_view ; the table is not temporary (PostgreSQL
+	 * won't allow replacing the view otherwise), but will be dropped
+	 * on rollback anyway. 
+	 */
+	CREATE TABLE fake_mining_settings(
+		planet_id			INT ,
+		resource_name_id	INT ,
+		mset_weight			INT ,
+		mset_specific		BOOLEAN
+	);
+
+	CREATE OR REPLACE VIEW emp.mining_settings_view
+		AS SELECT * FROM fake_mining_settings;
+
+	/* Insert fake records for each possible mining setting */
+	INSERT INTO fake_mining_settings VALUES
+		( 1 , 0 , 0 , FALSE ) ,
+		( 1 , 1 , 1 , FALSE ) ,
+		( 1 , 2 , 2 , FALSE ) ,
+		( 1 , 3 , 3 , FALSE ) ,
+		( 1 , 4 , 4 , FALSE );
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 2 );
+
+	SELECT diag_test_name( 'sys.gu_pmc_weights_view - Rows present' );
+	SELECT isnt( COUNT(*)::INT , 0 )
+		FROM sys.gu_pmc_weights_view;
+
+	SELECT diag_test_name( 'sys.gu_pmc_weights_view - weight = game.resources.weightBase ^ setting' );
+	SELECT sys.uoc_constant( 'game.resources.weightBase' , '(test)' , 'Resources' , 10.0 );
+	SELECT is_empty( $$
+		SELECT * FROM sys.gu_pmc_weights_view
+			WHERE pmc_weight IS NULL
+				OR pmc_weight <> POW( 10 , resource_name_id )
+	$$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
new file mode 100644
index 0000000..e1cfa4c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
@@ -0,0 +1,35 @@
+/*
+ * Test sys.gu_pmc_totals_view
+ */
+BEGIN;
+	/* Create a table which will server as an alternate source for
+	 * sys.gu_pmc_weights_view ; the table is not temporary (PostgreSQL
+	 * won't allow replacing the view otherwise), but will be dropped
+	 * on rollback anyway. 
+	 */
+	CREATE TABLE fake_mining_weights(
+		planet_id			INT ,
+		resource_name_id	INT ,
+		pmc_weight			DOUBLE PRECISION
+	);
+	
+	CREATE OR REPLACE VIEW sys.gu_pmc_weights_view
+		AS SELECT * FROM fake_mining_weights;
+
+	/* Insert fake records for two different planets */
+	INSERT INTO fake_mining_weights VALUES
+		( 1 , 0 , 1 ) ,
+		( 1 , 1 , 2 ) ,
+		( 2 , 0 , 4 ) ,
+		( 2 , 1 , 5 );
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 1 );
+	
+	SELECT set_eq(
+		$$ SELECT * FROM sys.gu_pmc_totals_view $$ ,
+		$$ VALUES ( 1 , 3.0 ) , ( 2 , 9.0 ) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
new file mode 100644
index 0000000..7765aa2
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
@@ -0,0 +1,89 @@
+/*
+ * Test the sys.gu_pmc_get_data() function
+ */
+BEGIN;
+	\i utils/common-setup/setup-gu-pmc-get-data-test.sql
+
+	/* Select results into a temporary table */
+	CREATE TEMPORARY TABLE test_results
+		AS SELECT * FROM sys.gu_pmc_get_data( 0 );
+
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT no_plan( );
+	
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Neutral planet without resource providers not included' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE planet = _get_map_name ( 'planet1' );
+	$$ );
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Neutral planet with resource providers - Rows included' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet2' )
+			AND difficulty = 0.2 AND empire IS NULL
+			AND happiness IS NULL AND weight IS NULL
+			AND total_weight IS NULL;
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Neutral planet with resource providers - No extra rows' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE planet = _get_map_name ( 'planet2' )
+				AND NOT ( difficulty = 0.2 AND empire IS NULL
+					AND happiness IS NULL AND weight IS NULL
+					AND total_weight IS NULL );
+	$$ );
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Planet using empire settings - Rows included' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet3' ) AND difficulty = 0.3
+			AND empire = _get_emp_name( 'empire1' )
+			AND happiness IS NOT NULL AND weight = 100
+			AND total_weight = 200;
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Planet using empire settings - No extra rows' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE planet = _get_map_name ( 'planet3' )
+				AND NOT ( difficulty = 0.3
+					AND empire = _get_emp_name( 'empire1' )
+					AND happiness IS NOT NULL
+					AND weight = 100 AND total_weight = 200 );
+	$$ );
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Planet using specific settings - Rows included' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet4' ) AND difficulty = 0.4
+			AND empire = _get_emp_name( 'empire2' )
+			AND happiness IS NOT NULL AND (
+				( resource = _get_string( 'resource1' ) AND weight = 10 )
+				OR ( resource = _get_string( 'resource2' ) AND weight = 1000 ) )
+			AND total_weight = 1010;
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Planet using specific settings - No extra rows' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE planet = _get_map_name ( 'planet4' )
+				AND NOT ( difficulty = 0.4
+					AND empire = _get_emp_name( 'empire2' )
+					AND happiness IS NOT NULL
+					AND total_weight = 1010 AND (
+						( resource = _get_string( 'resource1' ) AND weight = 10 )
+						OR ( resource = _get_string( 'resource2' ) AND weight = 1000 ) ) );
+	$$ );
+	
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Owned planet without resource providers not included' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE planet = _get_map_name ( 'planet5' );
+	$$ );
+	
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Planet matching all criterias but marked as processed not included' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE planet = _get_map_name ( 'planet6' );
+	$$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
new file mode 100644
index 0000000..c8181f2
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
@@ -0,0 +1,91 @@
+/*
+ * Test the sys.gu_pmc_update_resource() function
+ */
+BEGIN;
+	/*
+	 * We need to create a set of both resource providers and planet resource
+	 * records that will be updated by the function when it is called. We
+	 * disable foreign keys on both tables, as it makes things simpler. We
+	 * also replace verse.get_raw_production(), verse.get_extraction_factor( )
+	 * and verse.adjust_production() so that they return fixed values, and we
+	 * need to set the game.resources.extraction constant.
+	 */
+	ALTER TABLE verse.resource_providers
+		DROP CONSTRAINT fk_resprov_planet ,
+		DROP CONSTRAINT fk_resprov_resource;
+	ALTER TABLE verse.planet_resources
+		DROP CONSTRAINT fk_pres_planet ,
+		DROP CONSTRAINT fk_pres_resource;
+
+	INSERT INTO verse.resource_providers(
+			planet_id , resource_name_id , resprov_quantity_max, resprov_quantity ,
+			resprov_difficulty , resprov_recovery
+		) VALUES (
+			1 , 1 , 1000 , 1000 , 0 , 0.5
+		);
+
+	INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income , pres_upkeep
+		) VALUES (
+			1 , 1 , 0 , 0
+		);
+
+	CREATE OR REPLACE FUNCTION verse.get_raw_production( pid INT , pt building_output_type )
+		RETURNS REAL
+	AS $$
+		SELECT 1.0::REAL;
+	$$ LANGUAGE SQL;
+
+	CREATE OR REPLACE FUNCTION verse.adjust_production( prod REAL , happiness REAL )
+		RETURNS REAL
+	AS $$
+		SELECT 1.0::REAL;
+	$$ LANGUAGE SQL;
+
+	CREATE OR REPLACE FUNCTION verse.get_extraction_factor( _fill_ratio DOUBLE PRECISION , _difficulty DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
+	AS $$
+		SELECT ( CASE WHEN $1 = 1.0 AND $2 = 0 THEN 0.002 ELSE 0.0 END )::DOUBLE PRECISION;
+	$$ LANGUAGE SQL;
+
+	SELECT sys.uoc_constant( 'game.resources.extraction' , '(test)' , 'Resources' , 1440.0 );
+	ALTER FUNCTION sys.get_constant( TEXT ) VOLATILE;
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 4 );
+
+	SELECT sys.gu_pmc_update_resource( ROW(
+		1 , 1 , 1000.0 , 1000.0 , 0.0 , NULL , 1.0 , 0.5 , 1.0
+	) );
+	
+	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Provider udpated' );
+	SELECT is( resprov_quantity , 999.0::DOUBLE PRECISION )
+		FROM verse.resource_providers
+		WHERE planet_id = 1 AND resource_name_id = 1;
+	
+	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Planet resources income udpated' );
+	SELECT is( pres_income , 1.0::DOUBLE PRECISION )
+		FROM verse.planet_resources
+		WHERE planet_id = 1 AND resource_name_id = 1;
+
+	UPDATE verse.resource_providers SET resprov_quantity = resprov_quantity_max;
+	UPDATE sys.constant_definitions
+		SET c_value = 14400000.0
+		WHERE name = 'game.resources.extraction';
+	SELECT sys.gu_pmc_update_resource( ROW(
+		1 , 1 , 1000.0 , 1000.0 , 0.0 , NULL , 1.0 , 0.5 , 1.0
+	) );
+
+	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Bounded extraction quantity (1/2)' );
+	SELECT is( resprov_quantity , 0.0::DOUBLE PRECISION )
+		FROM verse.resource_providers
+		WHERE planet_id = 1 AND resource_name_id = 1;
+
+	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Bounded extraction quantity (2/2)' );
+	SELECT is( pres_income , 1000.0::DOUBLE PRECISION )
+		FROM verse.planet_resources
+		WHERE planet_id = 1 AND resource_name_id = 1;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
new file mode 100644
index 0000000..4c449b6
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
@@ -0,0 +1,58 @@
+/*
+ * Test the sys.process_planet_mining_updates() function
+ */
+BEGIN;
+	/*
+	 * Create a fake planet resource record which will be updated by the
+	 * function (dropping the foreign key is therefore needed).
+	 */
+	ALTER TABLE verse.planet_resources
+		DROP CONSTRAINT fk_pres_planet ,
+		DROP CONSTRAINT fk_pres_resource;
+
+	INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income , pres_upkeep
+		) VALUES (
+			1 , 1 , 42 , 0
+		);
+
+	/*
+	 * Create a table which contains the values which will be returned by
+	 * the fake sys.gu_pmc_get_data( ).
+	 */
+	CREATE TABLE _fake_update_data OF sys.gu_pmc_data_type;
+	INSERT INTO _fake_update_data VALUES (
+		1 , 1 , 1000.0 , 1000.0 , 0.5 , NULL , NULL , NULL , NULL ) ,
+		( 2 , 1 , 1000.0 , 1000.0 , 0.5 , 1 , 0.5 , 1.0 , 1.0 );
+	CREATE OR REPLACE FUNCTION sys.gu_pmc_get_data( _tick BIGINT )
+		RETURNS SETOF sys.gu_pmc_data_type
+	AS $$
+		SELECT * FROM _fake_update_data;
+	$$ LANGUAGE SQL;
+
+	/*
+	 * Replace sys.gu_pmc_update_resource() so it deletes records from the
+	 * table.
+	 */
+	CREATE OR REPLACE FUNCTION sys.gu_pmc_update_resource( _input sys.gu_pmc_data_type )
+		RETURNS VOID
+	AS $$
+		DELETE FROM _fake_update_data WHERE planet = $1.planet AND resource = $1.resource;
+	$$ LANGUAGE SQL;
+	
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT no_plan( );
+	
+	SELECT sys.process_planet_mining_updates( 0 );
+
+	SELECT diag_test_name( 'sys.process_planet_mining_updates() - Neutral planets updated' );
+	SELECT is( pres_income , 0::DOUBLE PRECISION ) FROM verse.planet_resources;
+
+	SELECT diag_test_name( 'sys.process_planet_mining_updates() - Owned planets updated' );
+	SELECT is_empty(
+		$$ SELECT * FROM _fake_update_data WHERE planet = 2 $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/dirty/010-mining-update-locks/prepare.sql b/legacyworlds-server-data/db-structure/tests/dirty/010-mining-update-locks/prepare.sql
new file mode 100644
index 0000000..674e320
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/dirty/010-mining-update-locks/prepare.sql
@@ -0,0 +1,8 @@
+/*
+ * Prepare for the locking tests on sys.gu_pmc_get_data() 
+ */
+BEGIN;
+	CREATE EXTENSION pageinspect;
+	\i utils/common-setup/setup-gu-pmc-get-data-test.sql
+	\i utils/locks-finder.sql
+COMMIT;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/dirty/010-mining-update-locks/run-test.sql b/legacyworlds-server-data/db-structure/tests/dirty/010-mining-update-locks/run-test.sql
new file mode 100644
index 0000000..1cb5b65
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/dirty/010-mining-update-locks/run-test.sql
@@ -0,0 +1,104 @@
+/*
+ * Test the locks set by sys.gu_pmc_get_data( )
+ */
+BEGIN;
+	SELECT * FROM sys.gu_pmc_get_data( 0 );
+	SELECT plan( 7 );
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Locks on planets' );
+	SELECT set_eq ( $$
+			SELECT name_id , shared , exclusive
+				FROM verse.planets pl
+					INNER JOIN ( SELECT * FROM _get_locks_on( 'verse.planets' ) ) p
+						ON p.ctid = pl.ctid;
+		$$ , $$
+			VALUES ( _get_map_name( 'planet2' ) , TRUE , FALSE ) ,
+				( _get_map_name( 'planet3' ) , TRUE , FALSE ) ,
+				( _get_map_name( 'planet4' ) , TRUE , FALSE );
+		$$
+	);
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Locks on resource providers' );
+	SELECT set_eq ( $$
+			SELECT planet_id , resource_name_id , shared , exclusive
+				FROM verse.resource_providers rp
+					INNER JOIN ( SELECT * FROM _get_locks_on( 'verse.resource_providers' ) ) p
+						ON p.ctid = rp.ctid;
+		$$ , $$
+			VALUES ( _get_map_name( 'planet2' ) , _get_string('resource1') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet2' ) , _get_string('resource2') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet3' ) , _get_string('resource1') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet3' ) , _get_string('resource2') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet4' ) , _get_string('resource1') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet4' ) , _get_string('resource2') , FALSE , TRUE );
+		$$
+	);
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Locks on planet resources' );
+	SELECT set_eq ( $$
+			SELECT planet_id , resource_name_id , shared , exclusive
+				FROM verse.planet_resources pr
+					INNER JOIN ( SELECT * FROM _get_locks_on( 'verse.planet_resources' ) ) p
+						ON p.ctid = pr.ctid;
+		$$ , $$
+			VALUES ( _get_map_name( 'planet2' ) , _get_string('resource1') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet2' ) , _get_string('resource2') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet3' ) , _get_string('resource1') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet3' ) , _get_string('resource2') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet4' ) , _get_string('resource1') , FALSE , TRUE ) ,
+				( _get_map_name( 'planet4' ) , _get_string('resource2') , FALSE , TRUE );
+		$$
+	);
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Locks on empire planets' );
+	SELECT set_eq ( $$
+			SELECT planet_id , empire_id , shared , exclusive
+				FROM emp.planets ep
+					INNER JOIN ( SELECT * FROM _get_locks_on( 'emp.planets' ) ) p
+						ON p.ctid = ep.ctid;
+		$$ , $$
+			VALUES ( _get_map_name( 'planet3' ) , _get_emp_name('empire1' ) , TRUE , FALSE ) ,
+				( _get_map_name( 'planet4' ) , _get_emp_name('empire2' ) , TRUE , FALSE );
+		$$
+	);
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Locks on mining settings' );
+	SELECT set_eq ( $$
+			SELECT empire_id , resource_name_id , shared , exclusive
+				FROM emp.mining_settings ms
+					INNER JOIN ( SELECT * FROM _get_locks_on( 'emp.mining_settings' ) ) p
+						ON p.ctid = ms.ctid;
+		$$ , $$
+			VALUES ( _get_emp_name('empire1' ) , _get_string( 'resource1' ) , TRUE , FALSE ) ,
+				( _get_emp_name('empire1' ) , _get_string( 'resource2' ) , TRUE , FALSE ) ,
+				( _get_emp_name('empire2' ) , _get_string( 'resource1' ) , TRUE , FALSE ) ,
+				( _get_emp_name('empire2' ) , _get_string( 'resource2' ) , TRUE , FALSE );
+		$$
+	);
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Locks on planet mining settings' );
+	SELECT set_eq ( $$
+			SELECT empire_id , planet_id , resource_name_id , shared , exclusive
+				FROM emp.planet_mining_settings ms
+					INNER JOIN ( SELECT * FROM _get_locks_on( 'emp.planet_mining_settings' ) ) p
+						ON p.ctid = ms.ctid;
+		$$ , $$
+			VALUES ( _get_emp_name('empire2' ) , _get_map_name( 'planet4' ) , _get_string( 'resource1' ) , TRUE , FALSE ) ,
+				( _get_emp_name('empire2' ) , _get_map_name( 'planet4' ) , _get_string( 'resource2' ) , TRUE , FALSE );
+		$$
+	);
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Locks on planet happiness records' );
+	SELECT set_eq ( $$
+			SELECT planet_id , shared , exclusive
+				FROM verse.planet_happiness pl
+					INNER JOIN ( SELECT * FROM _get_locks_on( 'verse.planet_happiness' ) ) p
+						ON p.ctid = pl.ctid;
+		$$ , $$
+			VALUES ( _get_map_name( 'planet3' ) , TRUE , FALSE ) ,
+				( _get_map_name( 'planet4' ) , TRUE , FALSE );
+		$$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/030-get-extraction-factor.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/030-get-extraction-factor.sql
new file mode 100644
index 0000000..880a55e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/030-get-extraction-factor.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on verse.get_extraction_factor()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'verse.get_extraction_factor() - Privileges' );
+	SELECT throws_ok( $$
+		SELECT verse.get_extraction_factor( 0.5 , 0.5 )
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
new file mode 100644
index 0000000..adb0975
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.gu_pmc_weights_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.gu_pmc_weights_view - Privileges' );
+	SELECT throws_ok( 'SELECT * FROM sys.gu_pmc_weights_view' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
new file mode 100644
index 0000000..f4d2b70
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.gu_pmc_totals_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.gu_pmc_totals_view - Privileges' );
+	SELECT throws_ok( 'SELECT * FROM sys.gu_pmc_totals_view' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/030-gu-pmc-get-data.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
new file mode 100644
index 0000000..9407cfb
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.gu_pmc_get_data( )
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.gu_pmc_get_data - Privileges' );
+	SELECT throws_ok( 'SELECT * FROM sys.gu_pmc_get_data( 0 )' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
new file mode 100644
index 0000000..7de5605
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on sys.gu_pmc_update_resource( )
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.gu_pmc_update_resource - Privileges' );
+	SELECT throws_ok( $$
+		SELECT * FROM sys.gu_pmc_update_resource( ROW( NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL ) );
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/050-process-planet-mining-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
new file mode 100644
index 0000000..03c1616
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.process_planet_mining_updates( )
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.process_planet_mining_updates() - Privileges' );
+	SELECT throws_ok( 'SELECT * FROM sys.process_planet_mining_updates( 0 )' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
new file mode 100644
index 0000000..97df95f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
@@ -0,0 +1,170 @@
+/*
+ * We need rows in quite a few tables to make sure locking works as
+ * advertised.
+ *
+ * - First we will need a planet with no resource providers. This one
+ *   shouldn't be selected at all.
+ * 
+ * - We need a planet with resource providers, but no owning empire. The
+ *   planet will be selected, but all fields that are obtained from the
+ *   empire's data will be NULL.
+ *
+ * - We need a planet owned by an empire for which empire-wide settings
+ *   will be used.
+ *
+ * - We need a planet owned by an empire but that uses planet-specific
+ *   settings.
+ *
+ * - We need a planet with no resource providers owned by an empire. This
+ *   one shouldn't be selected.
+ *
+ * - Finally, we need a planet that matches the criterias but isn't
+ *   scheduled for an update at the time. 
+ *
+ * FIXME: for now, locking is NOT tested. I simply have no idea how to do
+ *        it, at least not without creating an extra database and that
+ *        kind of stuff.
+ *
+ * FIXME: cannot test where the happiness column comes from until values
+ *		  all use DOUBLE PRECISION.
+ */
+\i utils/strings.sql
+\i utils/resources.sql
+\i utils/accounts.sql
+\i utils/naming.sql
+\i utils/universe.sql
+
+SELECT sys.uoc_constant( 'game.resources.weightBase' , '(test)' , 'Resources' , 10.0 );
+SELECT _create_natural_resources( 2 , 'resource' );
+SELECT _create_raw_planets( 6 , 'planet' );
+SELECT _create_emp_names( 4 , 'empire' );
+INSERT INTO emp.empires ( name_id , cash )
+	SELECT id , 0 FROM naming.empire_names;
+
+/* Planet #1 */
+INSERT INTO sys.updates( id , gu_type , status , last_tick )
+	VALUES ( 1 , 'PLANET_MINING' , 'PROCESSING' , 0 );
+INSERT INTO verse.updates( update_id , planet_id )
+	VALUES ( 1 , _get_map_name( 'planet1' ) );
+
+/* Planet #2 */
+INSERT INTO sys.updates( id , gu_type , status , last_tick )
+	VALUES ( 2 , 'PLANET_MINING' , 'PROCESSING' , 0 );
+INSERT INTO verse.updates( update_id , planet_id )
+	VALUES ( 2 , _get_map_name( 'planet2' ) );
+INSERT INTO verse.resource_providers (
+		planet_id , resource_name_id , resprov_quantity_max ,
+		resprov_quantity , resprov_difficulty , resprov_recovery
+	) VALUES (
+		_get_map_name( 'planet2' ) , _get_string( 'resource1' ) , 100 ,
+		100 , 0.2 , 0.5
+	) , (
+		_get_map_name( 'planet2' ) , _get_string( 'resource2' ) , 100 ,
+		100 , 0.2 , 0.5
+	);
+
+/* Planet #3 */
+INSERT INTO sys.updates( id , gu_type , status , last_tick )
+	VALUES ( 3 , 'PLANET_MINING' , 'PROCESSING' , 0 );
+INSERT INTO verse.updates( update_id , planet_id )
+	VALUES ( 3 , _get_map_name( 'planet3' ) );
+INSERT INTO verse.resource_providers (
+		planet_id , resource_name_id , resprov_quantity_max ,
+		resprov_quantity , resprov_difficulty , resprov_recovery
+	) VALUES (
+		_get_map_name( 'planet3' ) , _get_string( 'resource1' ) , 100 ,
+		100 , 0.3 , 0.5
+	) , (
+		_get_map_name( 'planet3' ) , _get_string( 'resource2' ) , 100 ,
+		100 , 0.3 , 0.5
+	);
+INSERT INTO verse.planet_happiness ( planet_id , current , target )
+	VALUES ( _get_map_name( 'planet3' ) , 0.3 , 0.3 );
+INSERT INTO emp.planets ( empire_id , planet_id )
+	VALUES ( _get_emp_name( 'empire1' ) , _get_map_name( 'planet3' ) );
+INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
+	VALUES (
+		_get_emp_name( 'empire1' ) , _get_string( 'resource1' ) , 2
+	) , (
+		_get_emp_name( 'empire1' ) , _get_string( 'resource2' ) , 2
+	);
+
+/* Planet #4 */
+INSERT INTO sys.updates( id , gu_type , status , last_tick )
+	VALUES ( 4 , 'PLANET_MINING' , 'PROCESSING' , 0 );
+INSERT INTO verse.updates( update_id , planet_id )
+	VALUES ( 4 , _get_map_name( 'planet4' ) );
+INSERT INTO verse.resource_providers (
+		planet_id , resource_name_id , resprov_quantity_max ,
+		resprov_quantity , resprov_difficulty , resprov_recovery
+	) VALUES (
+		_get_map_name( 'planet4' ) , _get_string( 'resource1' ) , 100 ,
+		100 , 0.4 , 0.5
+	) , (
+		_get_map_name( 'planet4' ) , _get_string( 'resource2' ) , 100 ,
+		100 , 0.4 , 0.5
+	);
+INSERT INTO verse.planet_happiness ( planet_id , current , target )
+	VALUES ( _get_map_name( 'planet4' ) , 0.4 , 0.4 );
+INSERT INTO emp.planets ( empire_id , planet_id )
+	VALUES ( _get_emp_name( 'empire2' ) , _get_map_name( 'planet4' ) );
+INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
+	VALUES (
+		_get_emp_name( 'empire2' ) , _get_string( 'resource1' ) , 2
+	) , (
+		_get_emp_name( 'empire2' ) , _get_string( 'resource2' ) , 2
+	);
+INSERT INTO emp.planet_mining_settings(
+		empire_id , planet_id , resource_name_id , emppmset_weight
+	) VALUES (
+			_get_emp_name( 'empire2' ) , _get_map_name( 'planet4' ) , _get_string( 'resource1' ) , 1
+		) , (
+			_get_emp_name( 'empire2' ) , _get_map_name( 'planet4' ) , _get_string( 'resource2' ) , 3
+		);
+
+/* Planet #5 */
+INSERT INTO sys.updates( id , gu_type , status , last_tick )
+	VALUES ( 5 , 'PLANET_MINING' , 'PROCESSING' , 0 );
+INSERT INTO verse.updates( update_id , planet_id )
+	VALUES ( 5 , _get_map_name( 'planet5' ) );
+INSERT INTO verse.planet_happiness ( planet_id , current , target )
+	VALUES ( _get_map_name( 'planet5' ) , 0.5 , 0.5 );
+INSERT INTO emp.planets ( empire_id , planet_id )
+	VALUES ( _get_emp_name( 'empire3' ) , _get_map_name( 'planet5' ) );
+INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
+	VALUES (
+		_get_emp_name( 'empire3' ) , _get_string( 'resource1' ) , 2
+	) , (
+		_get_emp_name( 'empire3' ) , _get_string( 'resource2' ) , 2
+	);
+
+/* Planet #6 */
+INSERT INTO sys.updates( id , gu_type , status , last_tick )
+	VALUES ( 6 , 'PLANET_MINING' , 'PROCESSED' , 0 );
+INSERT INTO verse.updates( update_id , planet_id )
+	VALUES ( 6 , _get_map_name( 'planet6' ) );
+INSERT INTO verse.resource_providers (
+		planet_id , resource_name_id , resprov_quantity_max ,
+		resprov_quantity , resprov_difficulty , resprov_recovery
+	) VALUES (
+		_get_map_name( 'planet6' ) , _get_string( 'resource1' ) , 100 ,
+		100 , 0.6 , 0.5
+	) , (
+		_get_map_name( 'planet6' ) , _get_string( 'resource2' ) , 100 ,
+		100 , 0.6 , 0.5
+	);
+INSERT INTO verse.planet_happiness ( planet_id , current , target )
+	VALUES ( _get_map_name( 'planet6' ) , 0.6 , 0.6 );
+INSERT INTO emp.planets ( empire_id , planet_id )
+	VALUES ( _get_emp_name( 'empire4' ) , _get_map_name( 'planet6' ) );
+INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
+	VALUES (
+		_get_emp_name( 'empire4' ) , _get_string( 'resource1' ) , 2
+	) , (
+		_get_emp_name( 'empire4' ) , _get_string( 'resource2' ) , 2
+	);
+
+/* Insert planet resource records */
+INSERT INTO verse.planet_resources ( planet_id , resource_name_id , pres_income , pres_upkeep )
+	SELECT p.name_id , r.resource_name_id , 42 , 0
+		FROM verse.planets p CROSS JOIN defs.resources r;
diff --git a/legacyworlds-server-data/db-structure/tests/utils/locks-finder.sql b/legacyworlds-server-data/db-structure/tests/utils/locks-finder.sql
new file mode 100644
index 0000000..bcbea18
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/utils/locks-finder.sql
@@ -0,0 +1,43 @@
+/*
+ * Utility functions used by unit tests 
+ *
+ * Row lock checks
+ */
+
+CREATE TYPE _locks_entry AS (
+	ctid		tid ,
+	shared		BOOLEAN ,
+	exclusive	BOOLEAN
+);
+
+CREATE OR REPLACE FUNCTION _get_locks_on(
+			IN _table		TEXT )
+		RETURNS SETOF _locks_entry
+		STRICT VOLATILE
+	AS $$
+
+DECLARE
+	_page		INT;
+	_pages		INT;
+	_record		RECORD;
+	_return		_locks_entry;
+
+BEGIN
+	SELECT INTO _pages pg_relation_size( _table ) / 8192;
+
+	FOR _page IN 0 .. ( _pages - 1 )
+	LOOP
+	
+		FOR _record IN SELECT t_ctid , t_infomask
+				FROM heap_page_items( get_raw_page( _table , _page ) )
+				WHERE t_xmax::text::int > ( txid_current( ) & x'ffffffff'::bigint )
+		LOOP
+			_return := ROW( _record.t_ctid ,
+				_record.t_infomask & x'80'::int <> 0 ,
+				_record.t_infomask & x'40'::int <> 0 );
+			RETURN NEXT _return;
+		END LOOP;
+
+	END LOOP;
+END;
+$$ LANGUAGE PLPGSQL;
\ No newline at end of file

From 24f40c55497bdab275503ecf587a65e62ab4b5be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 16 Jan 2012 12:50:10 +0100
Subject: [PATCH 22/94] Fix for dirty SQL tests

* Fixed an error in the main post-build script which caused dirty SQL
tests to be skipped
---
 build/post-build.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/build/post-build.sh b/build/post-build.sh
index 1f9c8fb..68e6fc9 100755
--- a/build/post-build.sh
+++ b/build/post-build.sh
@@ -53,6 +53,6 @@ fi
 if [ -d user ]; then
 	pg_prove -U $TEST_USER -d $TEST_DATABASE `find user/ -type f -name '*.sql'` || exit 1
 fi
-if [ -x dirty/run-dirty-tests.sql ]; then
-	( cd dirty; exec ./run-dirty-tests.sql ) || exit 1
+if [ -x dirty/run-dirty-tests.sh ]; then
+	( cd dirty; exec ./run-dirty-tests.sh ) || exit 1
 fi

From 30a1080e6eea4144d8f7db603ef2654262b958ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 16 Jan 2012 16:06:44 +0100
Subject: [PATCH 23/94] Maven projects fixes and improvements

* Fixed copy of the SQL files to the distribution

* Distribution packages will now execute an automatic clean-up before
assembling. While this will avoid having dirty, old files in the
distribution directory, it also has a negative consequence -
configuration files will be destroyed if they exist.
---
 legacyworlds-server-DIST/pom.xml        | 17 ++++++++++++++++-
 legacyworlds-server-DIST/src/server.xml |  3 ++-
 legacyworlds-web-DIST/pom.xml           | 16 +++++++++++++++-
 legacyworlds/pom.xml                    |  6 ++++++
 4 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/legacyworlds-server-DIST/pom.xml b/legacyworlds-server-DIST/pom.xml
index fc85884..39d7676 100644
--- a/legacyworlds-server-DIST/pom.xml
+++ b/legacyworlds-server-DIST/pom.xml
@@ -24,9 +24,23 @@
 		</dependency>
 	</dependencies>
 
-	<!-- Use the assembly plug-in to generate the distribution -->
 	<build>
 		<plugins>
+			<!-- Clean the target directory before generating the distribution -->
+			<plugin>
+				<artifactId>maven-clean-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>clean-before-assembly</id>
+						<phase>initialize</phase>
+						<goals>
+							<goal>clean</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+
+			<!-- Use the assembly plug-in to generate the distribution -->
 			<plugin>
 				<artifactId>maven-assembly-plugin</artifactId>
 				<executions>
@@ -48,5 +62,6 @@
 				</executions>
 			</plugin>
 		</plugins>
+
 	</build>
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server-DIST/src/server.xml b/legacyworlds-server-DIST/src/server.xml
index 875f9b7..cf10d97 100644
--- a/legacyworlds-server-DIST/src/server.xml
+++ b/legacyworlds-server-DIST/src/server.xml
@@ -46,7 +46,8 @@
 			<directory>../legacyworlds-server-data/db-structure</directory>
 			<outputDirectory>sql</outputDirectory>
 			<includes>
-				<include>**.sql</include>
+				<include>*.sql</include>
+				<include>parts/**</include>
 				<include>db-config.sample.txt</include>
 			</includes>
 		</fileSet>
diff --git a/legacyworlds-web-DIST/pom.xml b/legacyworlds-web-DIST/pom.xml
index e5d3890..a503c67 100644
--- a/legacyworlds-web-DIST/pom.xml
+++ b/legacyworlds-web-DIST/pom.xml
@@ -32,9 +32,23 @@
 		</dependency>
 	</dependencies>
 
-	<!-- Use the assembly plug-in to generate the distribution -->
 	<build>
 		<plugins>
+			<!-- Clean the target directory before generating the distribution -->
+			<plugin>
+				<artifactId>maven-clean-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>clean-before-assembly</id>
+						<phase>initialize</phase>
+						<goals>
+							<goal>clean</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+
+			<!-- Use the assembly plug-in to generate the distribution -->
 			<plugin>
 				<artifactId>maven-assembly-plugin</artifactId>
 				<executions>
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index 6cd929e..0bdde2c 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -53,6 +53,12 @@
 					<artifactId>tomcat-maven-plugin</artifactId>
 					<version>1.1</version>
 				</plugin>
+				
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-clean-plugin</artifactId>
+					<version>2.4.1</version>
+				</plugin>
 
 			</plugins>
 		</pluginManagement>

From a981d1653efda4836db5d71904e51b3ae7da018b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 16 Jan 2012 17:42:11 +0100
Subject: [PATCH 24/94] Universe generator fix

* A function called by the initial universe generator was changed, but
the call wasn't updated accordingly. This problem has been fixed.

* Variable / procedure name errors in some of the old, untested
functions.
---
 .../db-structure/parts/040-functions/060-universe.sql     | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
index 67e58c1..79a7f62 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
@@ -264,12 +264,12 @@ DECLARE
 	y		INT;
 	npics	INT;
 BEGIN
-	PERFORM verse.collect_resource_statistics( );
+	PERFORM verse.collect_resprov_statistics( );
 
 	npics := floor( sys.get_constant( 'game.universe.pictures' ) );
-	FOR x IN _area.x0 .. area.x1
+	FOR x IN _area.x0 .. _area.x1
 	LOOP
-		FOR y IN area.y0 .. area.y1
+		FOR y IN _area.y0 .. _area.y1
 		LOOP
 			PERFORM verse.create_system( x , y , ipop , npics );
 		END LOOP;
@@ -302,7 +302,7 @@ DECLARE
 BEGIN
 	sz := floor( sys.get_constant( 'game.universe.initialSize' ) );
 	pop := sys.get_constant( 'game.universe.initialPopulation' );
-	PERFORM verse.create_systems( -sz , -sz , sz , sz , pop );
+	PERFORM verse.create_systems( ROW( -sz , -sz , sz , sz ) , pop );
 END;
 $$ LANGUAGE plpgsql;
 

From 2adc754a2cfaa5981c56491970c06ba186c296c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 16 Jan 2012 18:09:45 +0100
Subject: [PATCH 25/94] Dependency upgrades

* Spring upgraded from 3.0.3 to 3.1.0 - As a consequence, all code that
used SimpleJdbcTemplate has been modified to use JdbcTemplate, as the
former has been deprecated.

* SLF4J upgraded from 1.5.11 to 1.6.4

* Apache Commons: DBCP upgraded from 1.2.2 to 1.4, Codec upgraded from
1.4 to 1.6

* CGLib upgraded from 2.2 to 2.2.2

* javax.mail upgraded from 1.4.1 to 1.4.4

* XStream upgraded from 1.3.1 to 1.4.2

* JUnit upgraded from 4.7 to 4.10

* FreeMarker upgraded from 2.3.16 to 2.3.18

* PostgreSQL JDBC glue upgraded from 8.4-701 to 9.1-901

* legacyworlds-server-tests no longer considered a dummy package
---
 .../lw/beans/acm/UserSessionDAOBean.java      |  8 +++---
 .../deepclone/lw/beans/acm/UsersDAOBean.java  | 12 ++++-----
 .../lw/beans/admin/AdminDAOBean.java          |  6 ++---
 .../deepclone/lw/beans/admin/BansDAOBean.java | 10 +++----
 .../prefs/PreferenceDefinitionsBean.java      |  6 ++---
 .../lw/beans/prefs/PreferencesDAOBean.java    |  6 ++---
 .../deepclone/lw/beans/bt/BugsDAOBean.java    |  7 +++--
 .../lw/beans/bt/EmpireSummaryBean.java        |  6 ++---
 .../lw/beans/eventlog/LogReaderBean.java      |  6 ++---
 .../com/deepclone/lw/beans/i18n/I18NData.java |  6 ++---
 .../lw/beans/i18n/LoaderTransaction.java      |  6 ++---
 .../lw/beans/naming/NamingDAOBean.java        |  6 ++---
 .../lw/beans/empire/AllianceDAOBean.java      |  6 ++---
 .../lw/beans/empire/EmpireDAOBean.java        |  6 ++---
 .../lw/beans/fleets/BattlesDAOBean.java       |  7 +++--
 .../lw/beans/fleets/FleetsDAOBean.java        |  6 ++---
 .../deepclone/lw/beans/map/PlanetDAOBean.java |  7 +++--
 .../lw/beans/map/UniverseDAOBean.java         |  8 +++---
 .../lw/beans/msgs/MessageBoxDAOBean.java      |  6 ++---
 .../lw/beans/msgs/MessageRecordsDAOBean.java  |  6 ++---
 .../lw/beans/msgs/NotificationsDAOBean.java   |  8 +++---
 .../deepclone/lw/beans/sys/ConstantsData.java |  6 ++---
 .../lw/beans/sys/SystemStatusBean.java        |  6 ++---
 .../lw/beans/sys/TickerManagerBean.java       |  6 ++---
 .../java/com/deepclone/lw/cli/ExportDB.java   |  6 ++---
 legacyworlds-server-tests/pom.xml             |  4 +--
 legacyworlds/doc/local-deployment.txt         |  2 +-
 legacyworlds/pom.xml                          | 27 ++++++++-----------
 28 files changed, 97 insertions(+), 105 deletions(-)

diff --git a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java
index cdb5fb0..c6cb78a 100644
--- a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java
+++ b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UserSessionDAOBean.java
@@ -11,8 +11,8 @@ import javax.sql.DataSource;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.admin.users.SessionTerminationType;
 import com.deepclone.lw.cmd.admin.users.UserSession;
@@ -25,7 +25,7 @@ public class UserSessionDAOBean
 		implements UserSessionDAO , InitializingBean
 {
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final RowMapper< UserSession > mSession;
 
@@ -66,7 +66,7 @@ public class UserSessionDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fSessionStart = new StoredProc( dataSource , "users" , "sessions_login" );
 		this.fSessionStart.addParameter( "credentials_id" , Types.INTEGER );
@@ -84,7 +84,7 @@ public class UserSessionDAOBean
 	@Override
 	public void afterPropertiesSet( )
 	{
-		this.dTemplate.getJdbcOperations( ).execute( "SELECT users.sessions_server_restart()" );
+		this.dTemplate.execute( "SELECT users.sessions_server_restart()" );
 	}
 
 
diff --git a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java
index 7e5d421..730a946 100644
--- a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java
+++ b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/acm/UsersDAOBean.java
@@ -11,8 +11,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.admin.users.AccountBanEntry;
 import com.deepclone.lw.cmd.admin.users.AccountListEntry;
@@ -55,7 +55,7 @@ public class UsersDAOBean
 	private final AccountListMapper< AccountListEntry > mAccountList;
 	private final AccountListMapper< AccountViewEntry > mAccountView;
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private StoredProc fCreateAccount;
 	private StoredProc fMailFailure;
@@ -181,7 +181,7 @@ public class UsersDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fCreateAccount = new StoredProc( dataSource , "users" , "create_credentials" );
 		this.fCreateAccount.addParameter( "address" , Types.VARCHAR );
@@ -426,7 +426,7 @@ public class UsersDAOBean
 	@Override
 	public void expireRequests( )
 	{
-		this.dTemplate.getJdbcOperations( ).execute( "SELECT users.expire_requests( )" );
+		this.dTemplate.execute( "SELECT users.expire_requests( )" );
 	}
 
 
@@ -469,7 +469,7 @@ public class UsersDAOBean
 	@Override
 	public void processVacations( )
 	{
-		this.dTemplate.getJdbcOperations( ).execute( "SELECT users.process_vacations( )" );
+		this.dTemplate.execute( "SELECT users.process_vacations( )" );
 	}
 
 
@@ -529,7 +529,7 @@ public class UsersDAOBean
 	@Override
 	public void deleteOldAccounts( )
 	{
-		this.dTemplate.getJdbcOperations( ).execute( "SELECT users.delete_old_accounts( )" );
+		this.dTemplate.execute( "SELECT users.delete_old_accounts( )" );
 	}
 
 
diff --git a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java
index 84a3a7a..12f4b06 100644
--- a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java
+++ b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/AdminDAOBean.java
@@ -13,8 +13,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.admin.adata.AdminOverview;
 import com.deepclone.lw.interfaces.admin.AdminDAO;
@@ -34,7 +34,7 @@ public class AdminDAOBean
 	private static final String sGetAdminByName = "SELECT * FROM admin.admins_view WHERE lower( name ) = ?";
 	private static final String sListAdministrators = "SELECT * FROM admin.admins_view ORDER BY active DESC , name";
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final RowMapper< AdminRecord > mAdminRecord;
 	private final RowMapper< AdminOverview > mAdminOverview;
@@ -89,7 +89,7 @@ public class AdminDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fCreateAdmin = new StoredProc( dataSource , "admin" , "create_admin" );
 		this.fCreateAdmin.addParameter( "address" , Types.VARCHAR );
diff --git a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java
index ec5f1a9..52b6653 100644
--- a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java
+++ b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/admin/BansDAOBean.java
@@ -12,8 +12,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.admin.bans.ArchivedBanRequest;
 import com.deepclone.lw.cmd.admin.bans.BanRequest;
@@ -51,7 +51,7 @@ public class BansDAOBean
 		}
 	}
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final BanRequestMapper< BanRequest > mPending;
 	private final BanRequestMapper< BanRequest > mArchived;
@@ -122,7 +122,7 @@ public class BansDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fRequestBanAddress = new StoredProc( dataSource , "admin" , "request_ban_on_address" );
 		this.fRequestBanAddress.addParameter( "admin_id" , Types.INTEGER );
@@ -251,14 +251,14 @@ public class BansDAOBean
 	@Override
 	public void expireBanRequests( )
 	{
-		this.dTemplate.getJdbcOperations( ).execute( "SELECT admin.expire_ban_requests( )" );
+		this.dTemplate.execute( "SELECT admin.expire_ban_requests( )" );
 	}
 
 
 	@Override
 	public void expireWarnings( )
 	{
-		this.dTemplate.getJdbcOperations( ).execute( "SELECT admin.expire_warnings( )" );
+		this.dTemplate.execute( "SELECT admin.expire_warnings( )" );
 	}
 
 
diff --git a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java
index 05a1603..ba69780 100644
--- a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java
+++ b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferenceDefinitionsBean.java
@@ -12,8 +12,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.annotation.Transactional;
@@ -70,7 +70,7 @@ public class PreferenceDefinitionsBean
 	private SystemLogger logger;
 	private Translator translator;
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private StoredProc uocGroup;
 	private StoredProc uocDef;
@@ -95,7 +95,7 @@ public class PreferenceDefinitionsBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.uocGroup = new StoredProc( dataSource , "defs" , "uoc_preference_group" );
 		this.uocGroup.addParameter( "name" , Types.VARCHAR );
diff --git a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java
index 6387587..b955ffa 100644
--- a/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java
+++ b/legacyworlds-server-beans-accounts/src/main/java/com/deepclone/lw/beans/prefs/PreferencesDAOBean.java
@@ -12,8 +12,8 @@ import java.util.Map;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.interfaces.prefs.AccountPreferences;
 import com.deepclone.lw.interfaces.prefs.Preference;
@@ -30,7 +30,7 @@ public class PreferencesDAOBean
 		implements PreferencesDAO
 {
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 	private PreferenceTypesRegistry registry;
 	private StoredProc fReset;
 	private StoredProc fSetPref;
@@ -39,7 +39,7 @@ public class PreferencesDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fReset = new StoredProc( dataSource , "users" , "reset_preferences" );
 		this.fReset.addParameter( "a_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java
index 8f5c37e..6483a57 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/BugsDAOBean.java
@@ -11,9 +11,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
-
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.bt.data.*;
 import com.deepclone.lw.interfaces.bt.BugsDAO;
@@ -25,7 +24,7 @@ public class BugsDAOBean
 		implements BugsDAO
 {
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final RowMapper< BugReport > mBugReport;
 	private final RowMapper< BugEvent > mBugEvent;
@@ -129,7 +128,7 @@ public class BugsDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fPostUserReport = new StoredProc( dataSource , "bugs" , "post_player_report" );
 		this.fPostUserReport.addParameter( "empire_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
index 07c8949..4b18651 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
@@ -9,8 +9,8 @@ import java.util.Map;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.beans.bt.esdata.*;
 import com.deepclone.lw.interfaces.bt.EmpireSummary;
@@ -22,7 +22,7 @@ public class EmpireSummaryBean
 		implements EmpireSummary
 {
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final XStream xStream;
 
@@ -188,7 +188,7 @@ public class EmpireSummaryBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 	}
 
 
diff --git a/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java
index 1476bab..e274a02 100644
--- a/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java
+++ b/legacyworlds-server-beans-eventlog/src/main/java/com/deepclone/lw/beans/eventlog/LogReaderBean.java
@@ -13,8 +13,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallback;
@@ -39,7 +39,7 @@ public class LogReaderBean
 		implements LogReader
 {
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 	private TransactionTemplate tTemplate;
 
 	private final RowMapper< LogEntry > mLogEntry;
@@ -88,7 +88,7 @@ public class LogReaderBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 	}
 
 
diff --git a/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
index c48b5f6..77bca8a 100644
--- a/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
+++ b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
@@ -12,7 +12,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import javax.sql.DataSource;
 
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
 import org.springframework.transaction.support.TransactionTemplate;
@@ -35,7 +35,7 @@ class I18NData
 	private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock( );
 
 	/** Database interface */
-	private final SimpleJdbcTemplate dTemplate;
+	private final JdbcTemplate dTemplate;
 
 	/** Transaction manager interface */
 	private final TransactionTemplate tTemplate;
@@ -53,7 +53,7 @@ class I18NData
 	/** Copies the required references then loads all data */
 	I18NData( DataSource dataSource , TransactionTemplate tTemplate )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 		this.tTemplate = tTemplate;
 
 		this.fUocLanguage = new StoredProc( dataSource , "defs" , "uoc_language" );
diff --git a/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java
index ffedd04..7df47fa 100644
--- a/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java
+++ b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/LoaderTransaction.java
@@ -6,8 +6,8 @@ import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.HashSet;
 
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
 
@@ -27,7 +27,7 @@ class LoaderTransaction
 {
 
 	/** Database interface */
-	private final SimpleJdbcTemplate dTemplate;
+	private final JdbcTemplate dTemplate;
 
 	/** String definition map being initialised */
 	private final HashSet< String > strings;
@@ -37,7 +37,7 @@ class LoaderTransaction
 
 
 	/** Copies the required references */
-	LoaderTransaction( SimpleJdbcTemplate dTemplate , HashSet< String > strings ,
+	LoaderTransaction( JdbcTemplate dTemplate , HashSet< String > strings ,
 			HashMap< String , LanguageStore > languages )
 	{
 		this.dTemplate = dTemplate;
diff --git a/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java b/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java
index 2c2be52..a672d84 100644
--- a/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java
+++ b/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/NamingDAOBean.java
@@ -11,8 +11,8 @@ import java.util.Map;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.admin.naming.Name;
 import com.deepclone.lw.cmd.admin.naming.NameType;
@@ -33,7 +33,7 @@ public class NamingDAOBean
 		long count;
 	}
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private StoredProc fGetEmpire;
 	private StoredProc fRenamePlanet;
@@ -75,7 +75,7 @@ public class NamingDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fGetEmpire = new StoredProc( dataSource , "emp" , "get_current" );
 		this.fGetEmpire.addParameter( "account_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java
index df2d7cc..8fc70b5 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/AllianceDAOBean.java
@@ -10,8 +10,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.alliance.AllianceLeaderData;
@@ -28,7 +28,7 @@ public class AllianceDAOBean
 		implements AllianceDAO
 {
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 	private StoredProc fCreateAlliance;
 	private StoredProc fJoinAlliance;
 	private StoredProc fCancelJoin;
@@ -42,7 +42,7 @@ public class AllianceDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fCreateAlliance = new StoredProc( dataSource , "emp" , "create_alliance" );
 		this.fCreateAlliance.addParameter( "empire_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
index daaa57b..43524db 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
@@ -12,8 +12,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
@@ -30,7 +30,7 @@ import com.deepclone.lw.utils.StoredProc;
 public class EmpireDAOBean
 		implements EmpireDAO
 {
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 	private StoredProc fImplementTech;
 	private StoredProc fAddEmpEnemy;
 	private StoredProc fAddAllEnemy;
@@ -44,7 +44,7 @@ public class EmpireDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fImplementTech = new StoredProc( dataSource , "emp" , "implement_tech" );
 		this.fImplementTech.addParameter( "empire_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java
index f7ce1a4..1b87bbe 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/BattlesDAOBean.java
@@ -12,9 +12,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
-
 import com.deepclone.lw.interfaces.game.BattlesDAO;
 import com.deepclone.lw.sqld.game.battle.*;
 
@@ -34,7 +33,7 @@ public class BattlesDAOBean
 	private static final String sGetAllBattles = "SELECT * FROM battles.battles_list WHERE empire = ? ORDER BY last_tick DESC NULLS FIRST , first_tick DESC , x , y , orbit";
 	private static final String sGetNewBattles = "SELECT * FROM battles.battles_list WHERE empire = ? AND ( last_update > ? OR NOT finished ) ORDER BY last_tick DESC NULLS FIRST , first_tick DESC , x , y , orbit";
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final RowMapper< EmpireBattleRecord > mGetBattleRecord;
 	private final RowMapper< PresenceRecord > mGetPresenceList;
@@ -173,7 +172,7 @@ public class BattlesDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 	}
 
 
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java
index 3fdbb24..6be2ef2 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/fleets/FleetsDAOBean.java
@@ -13,8 +13,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.PlanetRelationType;
@@ -42,7 +42,7 @@ public class FleetsDAOBean
 	private static String sqlSingleMoving = "SELECT * FROM fleets.moving_fleets WHERE empire = ? AND id = ?";
 	private static String sqlFleetShips = "SELECT * FROM fleets.ships_view WHERE empire = ? AND id = ?";
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private RowMapper< FleetLocation > mLocations;
 	private RowMapper< RawFleetOwner > mOwners;
@@ -209,7 +209,7 @@ public class FleetsDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fMoveFleets = new StoredProc( dataSource , "fleets" , "move_fleets" );
 		this.fMoveFleets.addParameter( "empire_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
index 8fe710b..8deb049 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
@@ -9,9 +9,8 @@ import java.util.List;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
-
 import com.deepclone.lw.cmd.player.gdata.planets.BuildableBuildingData;
 import com.deepclone.lw.cmd.player.gdata.planets.BuildableShipData;
 import com.deepclone.lw.cmd.player.gdata.planets.BuildingData;
@@ -30,7 +29,7 @@ public class PlanetDAOBean
 		implements PlanetDAO
 {
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private RowMapper< Basic > mPlanetBasics;
 	private RowMapper< Orbital > mPlanetOrbital;
@@ -195,7 +194,7 @@ public class PlanetDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fFlushBuildQueue = new StoredProc( dataSource , "verse" , "flush_build_queue" );
 		this.fFlushBuildQueue.addParameter( "planet_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java
index b7dfe92..b8e0c9b 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/UniverseDAOBean.java
@@ -8,8 +8,8 @@ import java.util.List;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.interfaces.game.UniverseDAO;
 import com.deepclone.lw.sqld.game.MapData;
@@ -19,20 +19,20 @@ import com.deepclone.lw.sqld.game.MapData;
 public class UniverseDAOBean
 		implements UniverseDAO
 {
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 	}
 
 
 	@Override
 	public void generate( )
 	{
-		this.dTemplate.getJdbcOperations( ).execute( "SELECT verse.generate( )" );
+		this.dTemplate.execute( "SELECT verse.generate( )" );
 	}
 
 
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java
index 3933d8d..462f40c 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageBoxDAOBean.java
@@ -13,8 +13,8 @@ import java.util.Map;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.cmd.msgdata.MessageType;
 import com.deepclone.lw.cmd.player.msgs.MessageBoxAction;
@@ -87,7 +87,7 @@ public class MessageBoxDAOBean
 
 	private final Map< CacheKey , CacheData > cache = new HashMap< CacheKey , CacheData >( );
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 	private final RowMapper< InboxRecord > listMapper;
 
 	private StoredProc fSendSpam;
@@ -135,7 +135,7 @@ public class MessageBoxDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fSendSpam = new StoredProc( dataSource , "msgs" , "deliver_admin_spam" );
 		this.fSendSpam.addParameter( "admin_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java
index 261d44a..92cfc6d 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/MessageRecordsDAOBean.java
@@ -10,8 +10,8 @@ import java.util.Map;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.interfaces.msg.MessageRecordsDAO;
 import com.deepclone.lw.sqld.msgs.*;
@@ -43,7 +43,7 @@ public class MessageRecordsDAOBean
 	private static final String sGetQueueLocations = "SELECT * FROM events.queue_locations_view WHERE event_id = ANY( ? ::BIGINT[] )";
 	private static final String sGetEventFleets = "SELECT * FROM events.fleet_lists WHERE event_id = ANY( ? ::BIGINT[] )";
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final RowMapper< TextMessageRecord > mTextMessage;
 	private final RowMapper< EventTypeRecord > mEventTypes;
@@ -219,7 +219,7 @@ public class MessageRecordsDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 	}
 
 
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java
index 9665362..777b143 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/msgs/NotificationsDAOBean.java
@@ -10,8 +10,8 @@ import java.util.List;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 
 import com.deepclone.lw.interfaces.msg.NotificationsDAO;
 import com.deepclone.lw.sqld.msgs.InboxRecord;
@@ -26,7 +26,7 @@ public class NotificationsDAOBean
 	private static final String sGetInstantNotifications = "SELECT * FROM msgs.get_mail_data() WHERE last_unmailed IS NOT NULL";
 	private static final String sGetRecapNotifications = "SELECT * FROM msgs.get_mail_data() WHERE last_unrecaped IS NOT NULL";
 
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final RowMapper< NotificationsRecord > mNotifications;
 	private RowMapper< InboxRecord > mList;
@@ -81,7 +81,7 @@ public class NotificationsDAOBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fMarkInstantMessages = new StoredProc( dataSource , "msgs" , "mark_instant_notifications" );
 		this.fMarkInstantMessages.addParameter( "empire_id" , Types.INTEGER );
@@ -163,6 +163,6 @@ public class NotificationsDAOBean
 	@Override
 	public void cleanupMessages( )
 	{
-		this.dTemplate.getJdbcOperations( ).execute( "SELECT msgs.cleanup( )" );
+		this.dTemplate.execute( "SELECT msgs.cleanup( )" );
 	}
 }
diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
index 4096acd..e63a517 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
@@ -14,10 +14,10 @@ import java.util.Set;
 
 import javax.sql.DataSource;
 
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.jdbc.core.SqlParameter;
 import org.springframework.jdbc.core.simple.SimpleJdbcCall;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
 import org.springframework.transaction.support.TransactionTemplate;
@@ -43,7 +43,7 @@ import com.deepclone.lw.utils.StoredProc;
 class ConstantsData
 {
 	/** Database interface */
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	/** Transaction manager interface */
 	private TransactionTemplate tTemplate;
@@ -85,7 +85,7 @@ class ConstantsData
 	 */
 	ConstantsData( DataSource dataSource , TransactionTemplate tTemplate , Logger logger )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.uocConstantNoBounds = new SimpleJdbcCall( dataSource );
 		this.uocConstantNoBounds.withCatalogName( "sys" ).withFunctionName( "uoc_constant" );
diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
index 18db00f..f3ddd7d 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
@@ -9,10 +9,10 @@ import java.util.Map;
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.jdbc.core.SqlOutParameter;
 import org.springframework.jdbc.core.simple.SimpleJdbcCall;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallback;
@@ -33,7 +33,7 @@ public class SystemStatusBean
 {
 
 	/** Database interface */
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	/** Transaction template */
 	private TransactionTemplate tTemplate;
@@ -55,7 +55,7 @@ public class SystemStatusBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fEnterMaintenanceMode = new StoredProc( dataSource , "sys" , "enter_maintenance_mode" );
 		this.fEnterMaintenanceMode.addParameter( "admin_id" , Types.INTEGER );
diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java
index 87a93ca..54c7756 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerManagerBean.java
@@ -17,8 +17,8 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallback;
@@ -42,7 +42,7 @@ public class TickerManagerBean
 	private final Set< Integer > registered = new HashSet< Integer >( );
 
 	private TransactionTemplate tTemplate;
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 	private final RowMapper< TickerTaskRecord > mTask;
 
@@ -80,7 +80,7 @@ public class TickerManagerBean
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
-		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+		this.dTemplate = new JdbcTemplate( dataSource );
 
 		this.fRegisterTask = new StoredProc( dataSource , "sys" , "register_ticker_task" );
 		this.fRegisterTask.addParameter( "task_name" , Types.VARCHAR );
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ExportDB.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ExportDB.java
index 488d63a..717823d 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ExportDB.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ExportDB.java
@@ -14,8 +14,8 @@ import javax.sql.DataSource;
 import org.springframework.context.support.AbstractApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 import org.springframework.context.support.FileSystemXmlApplicationContext;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallback;
@@ -67,7 +67,7 @@ public class ExportDB
 
 	private File file;
 	private TransactionTemplate tTemplate;
-	private SimpleJdbcTemplate dTemplate;
+	private JdbcTemplate dTemplate;
 
 
 	private ClassPathXmlApplicationContext createContext( )
@@ -93,7 +93,7 @@ public class ExportDB
 		PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
 
 		this.tTemplate = new TransactionTemplate( tManager );
-		this.dTemplate = new SimpleJdbcTemplate( dSource );
+		this.dTemplate = new JdbcTemplate( dSource );
 	}
 
 
diff --git a/legacyworlds-server-tests/pom.xml b/legacyworlds-server-tests/pom.xml
index c9cd3a4..a77ecca 100644
--- a/legacyworlds-server-tests/pom.xml
+++ b/legacyworlds-server-tests/pom.xml
@@ -10,8 +10,8 @@
 
 	<artifactId>legacyworlds-server-tests</artifactId>
 	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
-	<name>Legacy Worlds - Server - Tests (OH WAIT!)</name>
-	<description>This package regroups all tests for server capabilities.</description>
+	<name>Legacy Worlds - Server - Tests</name>
+	<description>This package contains all tests for server capabilities.</description>
 
 	<dependencies>
 		<dependency>
diff --git a/legacyworlds/doc/local-deployment.txt b/legacyworlds/doc/local-deployment.txt
index bddaa9b..4ca29a8 100644
--- a/legacyworlds/doc/local-deployment.txt
+++ b/legacyworlds/doc/local-deployment.txt
@@ -58,7 +58,7 @@ in another terminal.
 
 6) Create the server's super user. First, create an inactive user account:
 
-	java -jar legacyworlds-server-main-0.0-0.jar --run-tool CreateUser
+	java -jar legacyworlds-server-main-1.0.0-0.jar --run-tool CreateUser
 	
 The tool will prompt for an email address, password (which /will/ appear on the
 console) and language identifier ('en' or 'fr' are supported). Please note that
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index 0bdde2c..a9ff122 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -74,12 +74,7 @@
 		<repository>
 			<id>com.springsource.repository.bundles.release</id>
 			<name>Spring framework</name>
-			<url>http://repository.springsource.com/maven/bundles/release</url>
-		</repository>
-		<repository>
-			<id>com.springsource.repository.bundles.external</id>
-			<name>Spring framework - external</name>
-			<url>http://repository.springsource.com/maven/bundles/external</url>
+			<url>http://repo.springsource.org/release</url>
 		</repository>
 
 	</repositories>
@@ -106,18 +101,18 @@
 		<!--
 		   - DEPENDENCY VERSIONS
 		  -->
-		<dep.springframework>3.0.3.RELEASE</dep.springframework>
-		<dep.slf4j>1.5.11</dep.slf4j>
+		<dep.springframework>3.1.0.RELEASE</dep.springframework>
+		<dep.slf4j>1.6.4</dep.slf4j>
 		<dep.log4j>1.2.16</dep.log4j>
-		<dep.dbcp>1.2.2</dep.dbcp>
-		<dep.codecs>1.4</dep.codecs>
-		<dep.cglib>2.2</dep.cglib>
-		<dep.mail>1.4.1</dep.mail>
-		<dep.xstream>1.3.1</dep.xstream>
-		<dep.junit>4.7</dep.junit>
-		<dep.freemarker>2.3.16</dep.freemarker>
+		<dep.dbcp>1.4</dep.dbcp>
+		<dep.codecs>1.6</dep.codecs>
+		<dep.cglib>2.2.2</dep.cglib>
+		<dep.mail>1.4.4</dep.mail>
+		<dep.xstream>1.4.2</dep.xstream>
+		<dep.junit>4.10</dep.junit>
+		<dep.freemarker>2.3.18</dep.freemarker>
 		<dep.servlet>2.5</dep.servlet>
-		<dep.postgresql>8.4-701.jdbc4</dep.postgresql>
+		<dep.postgresql>9.1-901.jdbc4</dep.postgresql>
 	</properties>
 
 

From 426a1fdfd45cce89e5ddc3eb7c8b975944e57f6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 17 Jan 2012 10:50:51 +0100
Subject: [PATCH 26/94] XML dumps code clean-up

* Moved empire summary generator to a separate package, moved all empire
summary data classes to a sub-package of the former

* All row mappers for empire summary contents have been moved to
separate classes with default access

* Added comments to the component and to the mapper classes
---
 .../lw/beans/bt/EmpireSummaryBean.java        | 240 ------------------
 .../bt/es/BuildingsInformationMapper.java     |  44 ++++
 .../beans/bt/es/DebugInformationMapper.java   |  65 +++++
 .../lw/beans/bt/es/EmpireSummaryBean.java     | 161 ++++++++++++
 .../beans/bt/es/FleetInformationMapper.java   |  66 +++++
 .../beans/bt/es/PlanetInformationMapper.java  |  50 ++++
 .../bt/es/QueueItemInformationMapper.java     |  46 ++++
 .../bt/es/ResearchInformationMapper.java      |  44 ++++
 .../beans/bt/es/ShipsInformationMapper.java   |  46 ++++
 .../data}/AccountInformation.java             |   2 +-
 .../data}/AllianceInformation.java            |   2 +-
 .../data}/BuildingsInformation.java           |   2 +-
 .../{esdata => es/data}/DebugInformation.java |   2 +-
 .../data}/EmpireInformation.java              |   2 +-
 .../{esdata => es/data}/FleetInformation.java |   2 +-
 .../data}/MovementInformation.java            |   2 +-
 .../data}/PlanetInformation.java              |   2 +-
 .../{esdata => es/data}/QueueInformation.java |   2 +-
 .../data}/QueueItemInformation.java           |   2 +-
 .../data}/ResearchInformation.java            |   2 +-
 .../{esdata => es/data}/ShipsInformation.java |   2 +-
 .../data}/SystemInformation.java              |   2 +-
 .../configuration/bt/empire-summary-bean.xml  |   2 +-
 .../lw/interfaces/bt/EmpireSummary.java       |  21 ++
 24 files changed, 557 insertions(+), 254 deletions(-)
 delete mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/BuildingsInformationMapper.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/DebugInformationMapper.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/FleetInformationMapper.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/PlanetInformationMapper.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/QueueItemInformationMapper.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResearchInformationMapper.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ShipsInformationMapper.java
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/AccountInformation.java (96%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/AllianceInformation.java (95%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/BuildingsInformation.java (96%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/DebugInformation.java (97%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/EmpireInformation.java (95%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/FleetInformation.java (97%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/MovementInformation.java (97%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/PlanetInformation.java (97%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/QueueInformation.java (95%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/QueueItemInformation.java (96%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/ResearchInformation.java (96%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/ShipsInformation.java (96%)
 rename legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/{esdata => es/data}/SystemInformation.java (94%)

diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
deleted file mode 100644
index 4b18651..0000000
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package com.deepclone.lw.beans.bt;
-
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.sql.DataSource;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
-
-import com.deepclone.lw.beans.bt.esdata.*;
-import com.deepclone.lw.interfaces.bt.EmpireSummary;
-import com.thoughtworks.xstream.XStream;
-
-
-
-public class EmpireSummaryBean
-		implements EmpireSummary
-{
-
-	private JdbcTemplate dTemplate;
-
-	private final XStream xStream;
-
-	private final RowMapper< DebugInformation > mMainInfo;
-	private final RowMapper< ResearchInformation > mResearch;
-	private final RowMapper< PlanetInformation > mPlanet;
-	private final RowMapper< QueueItemInformation > mQueueItem;
-	private final RowMapper< BuildingsInformation > mBuildings;
-	private final RowMapper< FleetInformation > mFleet;
-	private final RowMapper< ShipsInformation > mShips;
-
-
-	public EmpireSummaryBean( )
-	{
-		this.xStream = new XStream( );
-		this.xStream.processAnnotations( new Class< ? >[] {
-				AccountInformation.class , AllianceInformation.class , BuildingsInformation.class ,
-				DebugInformation.class , EmpireInformation.class , FleetInformation.class , MovementInformation.class ,
-				PlanetInformation.class , QueueInformation.class , QueueItemInformation.class ,
-				ResearchInformation.class , ShipsInformation.class , SystemInformation.class
-		} );
-
-		this.mMainInfo = new RowMapper< DebugInformation >( ) {
-			@Override
-			public DebugInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				DebugInformation di = new DebugInformation( );
-
-				di.getSystem( ).setNextTick( rs.getLong( "next_tick" ) );
-				di.getSystem( ).setCurrentTick( (Long) rs.getObject( "current_tick" ) );
-
-				di.getAccount( ).setId( rs.getInt( "account_id" ) );
-				di.getAccount( ).setAddress( rs.getString( "account_address" ) );
-				di.getAccount( ).setGameCredits( rs.getInt( "game_credits" ) );
-				di.getAccount( ).setStatus( rs.getString( "account_status" ) );
-				di.getAccount( ).setLanguage( rs.getString( "account_language" ) );
-
-				di.getEmpire( ).setId( rs.getInt( "empire_id" ) );
-				di.getEmpire( ).setName( rs.getString( "empire_name" ) );
-				di.getEmpire( ).setCash( rs.getDouble( "cash" ) );
-
-				String allianceTag = rs.getString( "alliance_tag" );
-				if ( allianceTag != null ) {
-					AllianceInformation alliance = new AllianceInformation( );
-					alliance.setId( rs.getInt( "alliance_id" ) );
-					alliance.setTag( allianceTag );
-					alliance.setPending( rs.getBoolean( "alliance_pending" ) );
-					di.getEmpire( ).setAlliance( alliance );
-				}
-
-				return di;
-			}
-		};
-		this.mResearch = new RowMapper< ResearchInformation >( ) {
-			@Override
-			public ResearchInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				ResearchInformation ri = new ResearchInformation( );
-				ri.setId( rs.getInt( "line_id" ) );
-				ri.setCurrentLevel( rs.getInt( "level" ) );
-				ri.setLevelName( rs.getString( "name" ) );
-				ri.setAccumulated( rs.getDouble( "accumulated" ) );
-				return ri;
-			}
-		};
-		this.mPlanet = new RowMapper< PlanetInformation >( ) {
-			@Override
-			public PlanetInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				PlanetInformation pi = new PlanetInformation( );
-				pi.setId( rs.getInt( "planet_id" ) );
-				pi.setPopulation( rs.getDouble( "population" ) );
-				pi.setCurrentHappiness( rs.getDouble( "current_happiness" ) );
-				pi.setTargetHappiness( rs.getDouble( "target_happiness" ) );
-				pi.getCivilianQueue( ).setAccMoney( rs.getDouble( "civ_money" ) );
-				pi.getCivilianQueue( ).setAccWork( rs.getDouble( "civ_work" ) );
-				pi.getMilitaryQueue( ).setAccMoney( rs.getDouble( "mil_money" ) );
-				pi.getMilitaryQueue( ).setAccWork( rs.getDouble( "mil_work" ) );
-				return pi;
-			}
-		};
-		this.mQueueItem = new RowMapper< QueueItemInformation >( ) {
-			@Override
-			public QueueItemInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				QueueItemInformation qii = new QueueItemInformation( );
-				qii.setPlanetId( rs.getInt( "planet_id" ) );
-				qii.setMilitary( rs.getBoolean( "military" ) );
-				qii.setId( rs.getInt( "item_id" ) );
-				qii.setName( rs.getString( "item_name" ) );
-				qii.setDestroy( rs.getBoolean( "destroy" ) );
-				qii.setAmount( rs.getInt( "amount" ) );
-				return qii;
-			}
-		};
-		this.mBuildings = new RowMapper< BuildingsInformation >( ) {
-			@Override
-			public BuildingsInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				BuildingsInformation bi = new BuildingsInformation( );
-				bi.setPlanetId( rs.getInt( "planet_id" ) );
-				bi.setId( rs.getInt( "building_id" ) );
-				bi.setName( rs.getString( "building_name" ) );
-				bi.setAmount( rs.getInt( "amount" ) );
-				bi.setDamage( rs.getDouble( "damage" ) );
-				return bi;
-			}
-		};
-		this.mFleet = new RowMapper< FleetInformation >( ) {
-			@Override
-			public FleetInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				FleetInformation fi = new FleetInformation( );
-				fi.setId( rs.getLong( "fleet_id" ) );
-				fi.setName( rs.getString( "fleet_name" ) );
-				fi.setStatus( rs.getString( "status" ) );
-				fi.setAttacking( rs.getBoolean( "attacking" ) );
-				fi.setLocationId( rs.getInt( "location_id" ) );
-				fi.setLocationName( rs.getString( "location_name" ) );
-
-				Integer sourceId = (Integer) rs.getObject( "source_id" );
-				if ( sourceId != null ) {
-					MovementInformation mi = new MovementInformation( );
-					mi.setSourceId( sourceId );
-					mi.setSourceName( rs.getString( "source_name" ) );
-					mi.setTimeLeft( rs.getInt( "time_left" ) );
-					mi.setStateTimeLeft( rs.getInt( "state_time_left" ) );
-					mi.setNearId( (Integer) rs.getObject( "ref_point_id" ) );
-					mi.setNearName( rs.getString( "ref_point_name" ) );
-					mi.setOutwards( (Boolean) rs.getObject( "outwards" ) );
-					mi.setPastRefPoint( (Boolean) rs.getObject( "past_ref_point" ) );
-					mi.setStartX( (Float) rs.getObject( "start_x" ) );
-					mi.setStartY( (Float) rs.getObject( "start_y" ) );
-					fi.setMovement( mi );
-				}
-
-				return fi;
-			}
-		};
-		this.mShips = new RowMapper< ShipsInformation >( ) {
-			@Override
-			public ShipsInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				ShipsInformation si = new ShipsInformation( );
-				si.setFleetId( rs.getLong( "fleet_id" ) );
-				si.setId( rs.getInt( "ship_id" ) );
-				si.setName( rs.getString( "ship_name" ) );
-				si.setAmount( rs.getInt( "amount" ) );
-				si.setDamage( rs.getDouble( "damage" ) );
-				return si;
-			}
-		};
-	}
-
-
-	@Autowired( required = true )
-	public void setDataSource( DataSource dataSource )
-	{
-		this.dTemplate = new JdbcTemplate( dataSource );
-	}
-
-
-	@Override
-	public String getSummary( int empireId )
-	{
-		String sql = "SELECT * FROM bugs.dump_main_view WHERE empire_id = ?";
-		DebugInformation di = this.dTemplate.queryForObject( sql , this.mMainInfo , empireId );
-
-		sql = "SELECT * FROM bugs.dump_research_view WHERE empire_id = ?";
-		for ( ResearchInformation ri : this.dTemplate.query( sql , this.mResearch , empireId ) ) {
-			di.getResearch( ).add( ri );
-		}
-
-		sql = "SELECT * FROM bugs.dump_planets_view WHERE empire_id = ?";
-		Map< Integer , PlanetInformation > planets = new HashMap< Integer , PlanetInformation >( );
-		for ( PlanetInformation pi : this.dTemplate.query( sql , this.mPlanet , empireId ) ) {
-			di.getPlanets( ).add( pi );
-			planets.put( pi.getId( ) , pi );
-		}
-
-		sql = "SELECT * FROM bugs.dump_queues_view WHERE empire_id = ? ORDER BY queue_order";
-		for ( QueueItemInformation qii : this.dTemplate.query( sql , this.mQueueItem , empireId ) ) {
-			PlanetInformation pi = planets.get( qii.getPlanetId( ) );
-			QueueInformation qi = ( qii.isMilitary( ) ? pi.getMilitaryQueue( ) : pi.getCivilianQueue( ) );
-			qi.getItems( ).add( qii );
-		}
-
-		sql = "SELECT * FROM bugs.dump_buildings_view WHERE empire_id = ?";
-		for ( BuildingsInformation bi : this.dTemplate.query( sql , this.mBuildings , empireId ) ) {
-			planets.get( bi.getPlanetId( ) ).getBuildings( ).add( bi );
-		}
-
-		sql = "SELECT * FROM bugs.dump_fleets_view WHERE empire_id = ?";
-		Map< Long , FleetInformation > fleets = new HashMap< Long , FleetInformation >( );
-		for ( FleetInformation fi : this.dTemplate.query( sql , this.mFleet , empireId ) ) {
-			di.getFleets( ).add( fi );
-			fleets.put( fi.getId( ) , fi );
-		}
-
-		sql = "SELECT * FROM bugs.dump_ships_view WHERE empire_id = ?";
-		for ( ShipsInformation si : this.dTemplate.query( sql , this.mShips , empireId ) ) {
-			fleets.get( si.getFleetId( ) ).getShips( ).add( si );
-		}
-
-		return this.xStream.toXML( di );
-	}
-
-}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/BuildingsInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/BuildingsInformationMapper.java
new file mode 100644
index 0000000..3a233c9
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/BuildingsInformationMapper.java
@@ -0,0 +1,44 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.BuildingsInformation;
+
+
+
+/**
+ * Building information row mapper
+ * 
+ * <p>
+ * Map rows from <code>bugs.dump_buildings_view</code> into {@link BuildingsInformation} instances.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+final class BuildingsInformationMapper
+		implements RowMapper< BuildingsInformation >
+{
+
+	/**
+	 * Map a row from <code>bugs.dump_buildings_view</code>
+	 * 
+	 * <p>
+	 * Generate and initialise a {@link BuildingsInformation} instance using the row's fields.
+	 */
+	@Override
+	public BuildingsInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		BuildingsInformation bi = new BuildingsInformation( );
+		bi.setPlanetId( rs.getInt( "planet_id" ) );
+		bi.setId( rs.getInt( "building_id" ) );
+		bi.setName( rs.getString( "building_name" ) );
+		bi.setAmount( rs.getInt( "amount" ) );
+		bi.setDamage( rs.getDouble( "damage" ) );
+		return bi;
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/DebugInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/DebugInformationMapper.java
new file mode 100644
index 0000000..b8a5399
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/DebugInformationMapper.java
@@ -0,0 +1,65 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.AllianceInformation;
+import com.deepclone.lw.beans.bt.es.data.DebugInformation;
+
+
+
+/**
+ * Top-level empire summary row mapper
+ * 
+ * <p>
+ * This class maps an entry from the database's main empire summary view,
+ * <code>bugs.dump_main_view</code>, into a top-level {@link DebugInformation} instance.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+final class DebugInformationMapper
+		implements RowMapper< DebugInformation >
+{
+	/**
+	 * Map a row from <code>bugs.dump_main_view</code> into a {@link DebugInformation} instance
+	 * 
+	 * <p>
+	 * Converts the row into a {@link DebugInformation} instance containing system information,
+	 * account information and empire information. If the row contains alliance information, add an
+	 * {@link AllianceInformation} record to the empire information.
+	 */
+	@Override
+	public DebugInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		DebugInformation di = new DebugInformation( );
+
+		di.getSystem( ).setNextTick( rs.getLong( "next_tick" ) );
+		di.getSystem( ).setCurrentTick( (Long) rs.getObject( "current_tick" ) );
+
+		di.getAccount( ).setId( rs.getInt( "account_id" ) );
+		di.getAccount( ).setAddress( rs.getString( "account_address" ) );
+		di.getAccount( ).setGameCredits( rs.getInt( "game_credits" ) );
+		di.getAccount( ).setStatus( rs.getString( "account_status" ) );
+		di.getAccount( ).setLanguage( rs.getString( "account_language" ) );
+
+		di.getEmpire( ).setId( rs.getInt( "empire_id" ) );
+		di.getEmpire( ).setName( rs.getString( "empire_name" ) );
+		di.getEmpire( ).setCash( rs.getDouble( "cash" ) );
+
+		String allianceTag = rs.getString( "alliance_tag" );
+		if ( allianceTag != null ) {
+			AllianceInformation alliance = new AllianceInformation( );
+			alliance.setId( rs.getInt( "alliance_id" ) );
+			alliance.setTag( allianceTag );
+			alliance.setPending( rs.getBoolean( "alliance_pending" ) );
+			di.getEmpire( ).setAlliance( alliance );
+		}
+
+		return di;
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
new file mode 100644
index 0000000..d61469b
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
@@ -0,0 +1,161 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import com.deepclone.lw.beans.bt.es.data.AccountInformation;
+import com.deepclone.lw.beans.bt.es.data.AllianceInformation;
+import com.deepclone.lw.beans.bt.es.data.BuildingsInformation;
+import com.deepclone.lw.beans.bt.es.data.DebugInformation;
+import com.deepclone.lw.beans.bt.es.data.EmpireInformation;
+import com.deepclone.lw.beans.bt.es.data.FleetInformation;
+import com.deepclone.lw.beans.bt.es.data.MovementInformation;
+import com.deepclone.lw.beans.bt.es.data.PlanetInformation;
+import com.deepclone.lw.beans.bt.es.data.QueueInformation;
+import com.deepclone.lw.beans.bt.es.data.QueueItemInformation;
+import com.deepclone.lw.beans.bt.es.data.ResearchInformation;
+import com.deepclone.lw.beans.bt.es.data.ShipsInformation;
+import com.deepclone.lw.beans.bt.es.data.SystemInformation;
+import com.deepclone.lw.interfaces.bt.EmpireSummary;
+import com.thoughtworks.xstream.XStream;
+
+
+
+/**
+ * Empire XML summary generator
+ * 
+ * <p>
+ * This component is used by the bug tracking system to generate XML dumps of an empire's state. It
+ * reads the necessary data from the database, convert it to XML dump record classes then serialises
+ * the whole thing through XStream.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class EmpireSummaryBean
+		implements EmpireSummary
+{
+	/** JDBC access interface */
+	private JdbcTemplate dTemplate;
+
+	/** XStream instance used to serialise XML dumps */
+	private final XStream xStream;
+
+	/** Top-level row mapper */
+	private final DebugInformationMapper mMainInfo;
+
+	/** Empire research row mapper */
+	private final ResearchInformationMapper mResearch;
+
+	/** Empire-owned planet row mapper */
+	private final PlanetInformationMapper mPlanet;
+
+	/** Planet construction queue item row mapper */
+	private final QueueItemInformationMapper mQueueItem;
+
+	/** Planet buildings information mapper */
+	private final BuildingsInformationMapper mBuildings;
+
+	/** Fleet information row mapper */
+	private final FleetInformationMapper mFleet;
+
+	/** Fleet ships row mapper */
+	private final ShipsInformationMapper mShips;
+
+
+	/**
+	 * Set up the {@link XStream} instance that generates the dumps as well as the necessary row
+	 * mappers.
+	 */
+	public EmpireSummaryBean( )
+	{
+		this.xStream = new XStream( );
+		this.xStream.processAnnotations( new Class< ? >[] {
+				AccountInformation.class , AllianceInformation.class , BuildingsInformation.class ,
+				DebugInformation.class , EmpireInformation.class , FleetInformation.class , MovementInformation.class ,
+				PlanetInformation.class , QueueInformation.class , QueueItemInformation.class ,
+				ResearchInformation.class , ShipsInformation.class , SystemInformation.class
+		} );
+
+		this.mMainInfo = new DebugInformationMapper( );
+		this.mResearch = new ResearchInformationMapper( );
+		this.mPlanet = new PlanetInformationMapper( );
+		this.mQueueItem = new QueueItemInformationMapper( );
+		this.mBuildings = new BuildingsInformationMapper( );
+		this.mFleet = new FleetInformationMapper( );
+		this.mShips = new ShipsInformationMapper( );
+	}
+
+
+	/**
+	 * Dependency injector that sets the data source
+	 * 
+	 * @param dataSource
+	 *            the data source
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.dTemplate = new JdbcTemplate( dataSource );
+	}
+
+
+	/**
+	 * Generate an empire's XML summary
+	 * 
+	 * <p>
+	 * Retrieve all information from the database, convert that information into XML dump record
+	 * instances through row mappers, then convert the top-level record into XML using the
+	 * component's {@link XStream} instance.
+	 */
+	@Override
+	public String getSummary( int empireId )
+	{
+		String sql = "SELECT * FROM bugs.dump_main_view WHERE empire_id = ?";
+		DebugInformation di = this.dTemplate.queryForObject( sql , this.mMainInfo , empireId );
+
+		sql = "SELECT * FROM bugs.dump_research_view WHERE empire_id = ?";
+		for ( ResearchInformation ri : this.dTemplate.query( sql , this.mResearch , empireId ) ) {
+			di.getResearch( ).add( ri );
+		}
+
+		sql = "SELECT * FROM bugs.dump_planets_view WHERE empire_id = ?";
+		Map< Integer , PlanetInformation > planets = new HashMap< Integer , PlanetInformation >( );
+		for ( PlanetInformation pi : this.dTemplate.query( sql , this.mPlanet , empireId ) ) {
+			di.getPlanets( ).add( pi );
+			planets.put( pi.getId( ) , pi );
+		}
+
+		sql = "SELECT * FROM bugs.dump_queues_view WHERE empire_id = ? ORDER BY queue_order";
+		for ( QueueItemInformation qii : this.dTemplate.query( sql , this.mQueueItem , empireId ) ) {
+			PlanetInformation pi = planets.get( qii.getPlanetId( ) );
+			QueueInformation qi = ( qii.isMilitary( ) ? pi.getMilitaryQueue( ) : pi.getCivilianQueue( ) );
+			qi.getItems( ).add( qii );
+		}
+
+		sql = "SELECT * FROM bugs.dump_buildings_view WHERE empire_id = ?";
+		for ( BuildingsInformation bi : this.dTemplate.query( sql , this.mBuildings , empireId ) ) {
+			planets.get( bi.getPlanetId( ) ).getBuildings( ).add( bi );
+		}
+
+		sql = "SELECT * FROM bugs.dump_fleets_view WHERE empire_id = ?";
+		Map< Long , FleetInformation > fleets = new HashMap< Long , FleetInformation >( );
+		for ( FleetInformation fi : this.dTemplate.query( sql , this.mFleet , empireId ) ) {
+			di.getFleets( ).add( fi );
+			fleets.put( fi.getId( ) , fi );
+		}
+
+		sql = "SELECT * FROM bugs.dump_ships_view WHERE empire_id = ?";
+		for ( ShipsInformation si : this.dTemplate.query( sql , this.mShips , empireId ) ) {
+			fleets.get( si.getFleetId( ) ).getShips( ).add( si );
+		}
+
+		return this.xStream.toXML( di );
+	}
+
+}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/FleetInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/FleetInformationMapper.java
new file mode 100644
index 0000000..46b433f
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/FleetInformationMapper.java
@@ -0,0 +1,66 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.FleetInformation;
+import com.deepclone.lw.beans.bt.es.data.MovementInformation;
+
+
+
+/**
+ * Fleet information row mapper
+ * 
+ * <p>
+ * This class is responsible for mapping rows from <code>bugs.dump_fleets_view</code> into
+ * {@link FleetInformation} instances.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+final class FleetInformationMapper
+		implements RowMapper< FleetInformation >
+{
+
+	/**
+	 * Map a row from <code>bugs.dump_fleets_view</code>
+	 * 
+	 * <p>
+	 * Generate a {@link FleetInformation} instance from the row's contents. If the row indicates
+	 * that the fleet is moving, add a {@link MovementInformation} instance to the fleet information
+	 * record.
+	 */
+	@Override
+	public FleetInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		FleetInformation fi = new FleetInformation( );
+		fi.setId( rs.getLong( "fleet_id" ) );
+		fi.setName( rs.getString( "fleet_name" ) );
+		fi.setStatus( rs.getString( "status" ) );
+		fi.setAttacking( rs.getBoolean( "attacking" ) );
+		fi.setLocationId( rs.getInt( "location_id" ) );
+		fi.setLocationName( rs.getString( "location_name" ) );
+
+		Integer sourceId = (Integer) rs.getObject( "source_id" );
+		if ( sourceId != null ) {
+			MovementInformation mi = new MovementInformation( );
+			mi.setSourceId( sourceId );
+			mi.setSourceName( rs.getString( "source_name" ) );
+			mi.setTimeLeft( rs.getInt( "time_left" ) );
+			mi.setStateTimeLeft( rs.getInt( "state_time_left" ) );
+			mi.setNearId( (Integer) rs.getObject( "ref_point_id" ) );
+			mi.setNearName( rs.getString( "ref_point_name" ) );
+			mi.setOutwards( (Boolean) rs.getObject( "outwards" ) );
+			mi.setPastRefPoint( (Boolean) rs.getObject( "past_ref_point" ) );
+			mi.setStartX( (Float) rs.getObject( "start_x" ) );
+			mi.setStartY( (Float) rs.getObject( "start_y" ) );
+			fi.setMovement( mi );
+		}
+
+		return fi;
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/PlanetInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/PlanetInformationMapper.java
new file mode 100644
index 0000000..87ebd06
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/PlanetInformationMapper.java
@@ -0,0 +1,50 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.PlanetInformation;
+
+
+
+/**
+ * Top-level planet information row mapper
+ * 
+ * <p>
+ * This class maps rows from <code>bugs.dump_planets_view</code> into {@link PlanetInformation}
+ * instances.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+final class PlanetInformationMapper
+		implements RowMapper< PlanetInformation >
+{
+
+	/**
+	 * Map a row from <code>bugs.dump_planets_view</code>
+	 * 
+	 * <p>
+	 * Create a new {@link PlanetInformation} instance from the row's contents. The instance's
+	 * civilian and military construction queues, while left mostly uninitialised, will have their
+	 * money and work accumulators set.
+	 */
+	@Override
+	public PlanetInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		PlanetInformation pi = new PlanetInformation( );
+		pi.setId( rs.getInt( "planet_id" ) );
+		pi.setPopulation( rs.getDouble( "population" ) );
+		pi.setCurrentHappiness( rs.getDouble( "current_happiness" ) );
+		pi.setTargetHappiness( rs.getDouble( "target_happiness" ) );
+		pi.getCivilianQueue( ).setAccMoney( rs.getDouble( "civ_money" ) );
+		pi.getCivilianQueue( ).setAccWork( rs.getDouble( "civ_work" ) );
+		pi.getMilitaryQueue( ).setAccMoney( rs.getDouble( "mil_money" ) );
+		pi.getMilitaryQueue( ).setAccWork( rs.getDouble( "mil_work" ) );
+		return pi;
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/QueueItemInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/QueueItemInformationMapper.java
new file mode 100644
index 0000000..0d21815
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/QueueItemInformationMapper.java
@@ -0,0 +1,46 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.QueueItemInformation;
+
+
+
+/**
+ * Generic row mapper for construction queue items
+ * 
+ * <p>
+ * This class is responsible for mapping rows from <code>bugs.dump_queues_view</code> into
+ * {@link QueueItemInformation} instances. These instances will be added to the right queue later.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+final class QueueItemInformationMapper
+		implements RowMapper< QueueItemInformation >
+{
+
+	/**
+	 * Map a row from <code>bugs.dump_queues_view</code>
+	 * 
+	 * <p>
+	 * Create a {@link QueueItemInformation} instance from the row's contents.
+	 */
+	@Override
+	public QueueItemInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		QueueItemInformation qii = new QueueItemInformation( );
+		qii.setPlanetId( rs.getInt( "planet_id" ) );
+		qii.setMilitary( rs.getBoolean( "military" ) );
+		qii.setId( rs.getInt( "item_id" ) );
+		qii.setName( rs.getString( "item_name" ) );
+		qii.setDestroy( rs.getBoolean( "destroy" ) );
+		qii.setAmount( rs.getInt( "amount" ) );
+		return qii;
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResearchInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResearchInformationMapper.java
new file mode 100644
index 0000000..c2cc5da
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResearchInformationMapper.java
@@ -0,0 +1,44 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.ResearchInformation;
+
+
+
+/**
+ * Research information row mapper
+ * 
+ * <p>
+ * This class maps rows from <code>bugs.dump_research_view</code> into {@link ResearchInformation}
+ * instances.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+final class ResearchInformationMapper
+		implements RowMapper< ResearchInformation >
+{
+
+	/**
+	 * Map a <code>bugs.dump_research_view</code> row
+	 * 
+	 * <p>
+	 * Create a {@link ResearchInformation} instance from the row's contents.
+	 */
+	@Override
+	public ResearchInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		ResearchInformation ri = new ResearchInformation( );
+		ri.setId( rs.getInt( "line_id" ) );
+		ri.setCurrentLevel( rs.getInt( "level" ) );
+		ri.setLevelName( rs.getString( "name" ) );
+		ri.setAccumulated( rs.getDouble( "accumulated" ) );
+		return ri;
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ShipsInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ShipsInformationMapper.java
new file mode 100644
index 0000000..730d054
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ShipsInformationMapper.java
@@ -0,0 +1,46 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.ShipsInformation;
+
+
+
+/**
+ * Fleet ships information row mapper
+ * 
+ * <p>
+ * This class is responsible for creating {@link ShipsInformation} instances from the contents of
+ * <code>bugs.dump_ships_view</code>.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+final class ShipsInformationMapper
+		implements RowMapper< ShipsInformation >
+{
+
+	/**
+	 * Map a row from <code>bugs.dump_ships_view</code>
+	 * 
+	 * <p>
+	 * Generate a {@link ShipsInformation} instance and initialise its fields based on the row's
+	 * contents.
+	 */
+	@Override
+	public ShipsInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		ShipsInformation si = new ShipsInformation( );
+		si.setFleetId( rs.getLong( "fleet_id" ) );
+		si.setId( rs.getInt( "ship_id" ) );
+		si.setName( rs.getString( "ship_name" ) );
+		si.setAmount( rs.getInt( "amount" ) );
+		si.setDamage( rs.getDouble( "damage" ) );
+		return si;
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AccountInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/AccountInformation.java
similarity index 96%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AccountInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/AccountInformation.java
index d6496e8..0fae3b3 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AccountInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/AccountInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AllianceInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/AllianceInformation.java
similarity index 95%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AllianceInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/AllianceInformation.java
index e156b4b..56368fb 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/AllianceInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/AllianceInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/BuildingsInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/BuildingsInformation.java
similarity index 96%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/BuildingsInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/BuildingsInformation.java
index 61a9415..75416fc 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/BuildingsInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/BuildingsInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
similarity index 97%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
index 847fcb1..76074b8 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/EmpireInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireInformation.java
similarity index 95%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/EmpireInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireInformation.java
index 94825fe..ff5f984 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/EmpireInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/FleetInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/FleetInformation.java
similarity index 97%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/FleetInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/FleetInformation.java
index d049007..3030e5a 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/FleetInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/FleetInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/MovementInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/MovementInformation.java
similarity index 97%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/MovementInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/MovementInformation.java
index b3f20a0..36e9f69 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/MovementInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/MovementInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/PlanetInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
similarity index 97%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/PlanetInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
index e2c10a9..de9c6b6 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/PlanetInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/QueueInformation.java
similarity index 95%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/QueueInformation.java
index 3aacd7a..a4e7401 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/QueueInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueItemInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/QueueItemInformation.java
similarity index 96%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueItemInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/QueueItemInformation.java
index a96a3d2..9e432e8 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/QueueItemInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/QueueItemInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchInformation.java
similarity index 96%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchInformation.java
index 69d9f63..fc4746c 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ShipsInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ShipsInformation.java
similarity index 96%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ShipsInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ShipsInformation.java
index 047ddfd..8a7229c 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ShipsInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ShipsInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/SystemInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/SystemInformation.java
similarity index 94%
rename from legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/SystemInformation.java
rename to legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/SystemInformation.java
index 1d51bca..4a98a10 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/SystemInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/SystemInformation.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.bt.esdata;
+package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
diff --git a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
index bb9f28c..a268802 100644
--- a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
+++ b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
@@ -3,6 +3,6 @@
 	xsi:schemaLocation="
                         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 
-	<bean id="empireSummary" class="com.deepclone.lw.beans.bt.EmpireSummaryBean" />
+	<bean id="empireSummary" class="com.deepclone.lw.beans.bt.es.EmpireSummaryBean" />
 
 </beans>
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java
index bd09629..8efd0c6 100644
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/bt/EmpireSummary.java
@@ -1,9 +1,30 @@
 package com.deepclone.lw.interfaces.bt;
 
 
+/**
+ * Empire XML summary generator
+ * 
+ * <p>
+ * This interface corresponds to the component which generates XML dumps of an empire's state to be
+ * included in bug reports.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
 public interface EmpireSummary
 {
 
+	/**
+	 * Generate an empire's XML summary
+	 * 
+	 * <p>
+	 * This method retrieves all necessary data from the database and converts it into an XML dump
+	 * to be used in the bug report.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * 
+	 * @return the XML summary of the empire's current state
+	 */
 	public String getSummary( int empireId );
 
 }

From 95e6019c1212c3d1a94700aa706f42f3c9c18da1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 17 Jan 2012 16:47:05 +0100
Subject: [PATCH 27/94] Mock result set class

* Added a mock result set class which can be used to write unit tests
for e.g. RowMapper-based classes. The class is incomplete and should be
extended as necessary.

* Added unit test for BuildingsInformationMapper as a proof of concept
---
 .../bt/es/TestBuildingsInformationMapper.java |  81 ++++++
 .../deepclone/lw/testing/MockResultSet.java   | 249 ++++++++++++++++++
 2 files changed, 330 insertions(+)
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestBuildingsInformationMapper.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockResultSet.java

diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestBuildingsInformationMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestBuildingsInformationMapper.java
new file mode 100644
index 0000000..c32bf40
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestBuildingsInformationMapper.java
@@ -0,0 +1,81 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.beans.bt.es.data.BuildingsInformation;
+import com.deepclone.lw.testing.MockResultSet;
+
+
+
+/**
+ * Tests for the {@link BuildingsInformationMapper} class.
+ */
+public class TestBuildingsInformationMapper
+{
+
+	/** Planet identifier found in the "result" */
+	private static final int PLANET_ID = 1;
+
+	/** Building identifier found in the "result" */
+	private static final int BUILDING_ID = 2;
+
+	/** Name found in the "result" */
+	private static final String BUILDING_NAME = "Test";
+
+	/** Quantity of buildings found in the "result" */
+	private static final int QUANTITY = 3;
+
+	/** Damage value found in the "result" */
+	private static final double DAMAGE = 4.0;
+
+	/** The fake result set used in the tests */
+	private ResultSet resultSet;
+
+	/** The mapper used in the tests */
+	private BuildingsInformationMapper mapper;
+
+
+	/**
+	 * Create the contents of the fake result set
+	 */
+	@Before
+	@SuppressWarnings( "unchecked" )
+	public void setUp( )
+	{
+		HashMap< String , Object > row = new HashMap< String , Object >( );
+		row.put( "planet_id" , PLANET_ID );
+		row.put( "building_id" , BUILDING_ID );
+		row.put( "building_name" , BUILDING_NAME );
+		row.put( "amount" , QUANTITY );
+		row.put( "damage" , DAMAGE );
+		this.resultSet = MockResultSet.create( new HashMap[] {
+			row
+		} );
+		this.mapper = new BuildingsInformationMapper( );
+	}
+
+
+	/**
+	 * The row mapper returns an instance that contains the necessary data.
+	 */
+	@Test
+	public void testRowMapper( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 1 );
+		BuildingsInformation info = this.mapper.mapRow( this.resultSet , 0 );
+		assertNotNull( info );
+		assertEquals( PLANET_ID , info.getPlanetId( ) );
+		assertEquals( BUILDING_ID , info.getId( ) );
+		assertEquals( BUILDING_NAME , info.getName( ) );
+		assertEquals( QUANTITY , info.getAmount( ) );
+		assertEquals( DAMAGE , info.getDamage( ) , 0 );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockResultSet.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockResultSet.java
new file mode 100644
index 0000000..da5bd52
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockResultSet.java
@@ -0,0 +1,249 @@
+package com.deepclone.lw.testing;
+
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+
+/**
+ * Mock result set
+ * 
+ * <p>
+ * This class uses a proxy to mock a {@link ResultSet} instance. It can be used in unit tests to
+ * fake SQL operation results.
+ * 
+ * <p>
+ * FIXME: for now, this class is mostly empty. It will be filled as necessary when writing tests.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class MockResultSet
+		implements InvocationHandler
+{
+	/** The rows in this result set */
+	private final HashMap< String , Object >[] rows;
+
+	/** The current row's index */
+	private int index = -1;
+
+	/** Last read column was <code>null</code>? */
+	private boolean nullFound = false;
+
+
+	/**
+	 * Initialise the result set's rows.
+	 * 
+	 * @param rows
+	 *            an array of hash tables that will be returned when trying to access the result set
+	 */
+	private MockResultSet( HashMap< String , Object >[] rows )
+	{
+		this.rows = rows;
+	}
+
+
+	@Override
+	public Object invoke( Object proxy , Method method , Object[] args )
+			throws Throwable
+	{
+		Method m = MockResultSet.class.getMethod( method.getName( ) , method.getParameterTypes( ) );
+		if ( m == null ) {
+			throw new SQLException( "Unsupported method " + method.getName( ) );
+		}
+
+		return m.invoke( this , args );
+	}
+
+
+	/**
+	 * Create a mock result set
+	 * 
+	 * @param rows
+	 *            the values that will be returned by the result set
+	 * 
+	 * @return a mock result set
+	 */
+	public static ResultSet create( HashMap< String , Object >[] rows )
+	{
+		return (ResultSet) Proxy.newProxyInstance( ResultSet.class.getClassLoader( ) , new Class[] {
+			ResultSet.class
+		} , new MockResultSet( rows ) );
+	}
+
+
+	/**
+	 * Move the cursor to some row
+	 * 
+	 * @param row
+	 *            the row to select
+	 * 
+	 * @return <code>true</code> if the cursor is inside the fake result, <code>false</code> if it
+	 *         isn't.
+	 */
+	public boolean absolute( int row )
+	{
+		if ( row < 0 ) {
+			row = this.rows.length + row;
+		} else {
+			row--;
+		}
+
+		if ( row < 0 ) {
+			this.index = -1;
+			return false;
+		}
+
+		if ( row >= this.rows.length ) {
+			this.index = this.rows.length - 1;
+			return false;
+		}
+
+		this.index = row;
+		return true;
+	}
+
+
+	/**
+	 * Access the next "row"
+	 * 
+	 * <p>
+	 * If there are more "rows" available, move to the next one.
+	 * 
+	 * @return <code>true</code> if there are more rows, <code>false</code> if there aren't.
+	 */
+	public boolean next( )
+	{
+		if ( this.index < this.rows.length ) {
+			this.index++;
+		}
+		return this.index < this.rows.length;
+	}
+
+
+	/**
+	 * Check if the last value accessed contained NULL
+	 * 
+	 * @return <code>true</code> if it did, <code>false</code> if it didn't
+	 */
+	public boolean wasNull( )
+	{
+		return this.nullFound;
+	}
+
+
+	/**
+	 * Return the object in some column of the current "row"
+	 * 
+	 * @param columnName
+	 *            the column's name
+	 * 
+	 * @return the object from the fake results
+	 * 
+	 * @throws SQLException
+	 *             if no row is selected
+	 */
+	public Object getObject( String columnName )
+			throws SQLException
+	{
+		if ( this.index < 0 || this.index >= this.rows.length ) {
+			throw new SQLException( "Current row is out of bounds" );
+		}
+		Object object = this.rows[ this.index ].get( columnName );
+		this.nullFound = ( object == null );
+		return object;
+	}
+
+
+	/**
+	 * Return the object in some column of the current "row" as a string
+	 * 
+	 * @param columnName
+	 *            the column's name
+	 * 
+	 * @return the object from the fake results, as a string
+	 * 
+	 * @throws SQLException
+	 *             if no row is selected
+	 */
+	public String getString( String columnName )
+			throws SQLException
+	{
+		Object object = this.getObject( columnName );
+		if ( object != null ) {
+			return object.toString( );
+		}
+		return null;
+	}
+
+
+	/**
+	 * Return the object in some column of the current "row" as a boolean
+	 * 
+	 * @param columnName
+	 *            the column's name
+	 * 
+	 * @return the object from the fake results, as a boolean
+	 * 
+	 * @throws SQLException
+	 *             if no row is selected
+	 */
+	public boolean getBoolean( String columnName )
+			throws SQLException
+	{
+		Object object = this.getObject( columnName );
+		if ( object != null ) {
+			return (Boolean) object;
+		}
+		return false;
+	}
+
+
+	/**
+	 * Return the object in some column of the current "row" as an integer
+	 * 
+	 * @param columnName
+	 *            the column's name
+	 * 
+	 * @return the object from the fake results, as an integer
+	 * 
+	 * @throws SQLException
+	 *             if no row is selected
+	 */
+	public int getInt( String columnName )
+			throws SQLException
+	{
+		Object object = this.getObject( columnName );
+		if ( object != null ) {
+			return (Integer) object;
+		}
+		return 0;
+	}
+
+
+	/**
+	 * Return the object in some column of the current "row" as a double
+	 * 
+	 * @param columnName
+	 *            the column's name
+	 * 
+	 * @return the object from the fake results, as a double
+	 * 
+	 * @throws SQLException
+	 *             if no row is selected
+	 */
+	public double getDouble( String columnName )
+			throws SQLException
+	{
+		Object object = this.getObject( columnName );
+		if ( object != null ) {
+			return (Double) object;
+		}
+		return 0;
+	}
+
+}

From ce6d86d344de78bf4b631c1f6d4b4cc3222ba2f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Wed, 18 Jan 2012 09:28:35 +0100
Subject: [PATCH 28/94] Planet resources and resource providers in XML dumps

* Dump version bumped up to 2

* Added SQL view that shows resource delta and provider information for
all empire-owned planets

* Added new storage classes for resource providers and resource deltas

* Added row mapper which extracts all planet resources information
(providers and deltas)

* Modified dump generator to include planet resources / resource
providers
---
 .../lw/beans/bt/es/EmpireSummaryBean.java     | 219 +++++++++++++++---
 .../lw/beans/bt/es/PlanetResourceRow.java     |  96 ++++++++
 .../lw/beans/bt/es/ResourceRowMapper.java     |  58 +++++
 .../lw/beans/bt/es/data/DebugInformation.java |   2 +-
 .../es/data/InvalidDumpContentsException.java |  60 +++++
 .../beans/bt/es/data/PlanetInformation.java   | 130 +++++++++--
 .../bt/es/data/ResourceDeltaInformation.java  | 109 +++++++++
 .../es/data/ResourceProviderInformation.java  | 152 ++++++++++++
 .../parts/040-functions/200-bugs.sql          |  48 ++++
 .../010-dump-planet-resources-view.sql        |  73 ++++++
 .../010-dump-planet-resources-view.sql        |  11 +
 .../bt/es/TestBuildingsInformationMapper.java |   2 +
 .../lw/beans/bt/es/TestPlanetResourceRow.java | 102 ++++++++
 .../lw/beans/bt/es/TestResourceRowMapper.java | 129 +++++++++++
 .../TestInvalidDumpContentsException.java     |  85 +++++++
 .../es/data/TestResourceDeltaInformation.java | 174 ++++++++++++++
 .../data/TestResourceProviderInformation.java | 207 +++++++++++++++++
 17 files changed, 1607 insertions(+), 50 deletions(-)
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/PlanetResourceRow.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResourceRowMapper.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/InvalidDumpContentsException.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceDeltaInformation.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceProviderInformation.java
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/010-dump-planet-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/200-bugs/010-dump-planet-resources-view.sql
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestPlanetResourceRow.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestResourceRowMapper.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestInvalidDumpContentsException.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceDeltaInformation.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceProviderInformation.java

diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
index d61469b..bc2fa7d 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
@@ -20,6 +20,8 @@ import com.deepclone.lw.beans.bt.es.data.PlanetInformation;
 import com.deepclone.lw.beans.bt.es.data.QueueInformation;
 import com.deepclone.lw.beans.bt.es.data.QueueItemInformation;
 import com.deepclone.lw.beans.bt.es.data.ResearchInformation;
+import com.deepclone.lw.beans.bt.es.data.ResourceDeltaInformation;
+import com.deepclone.lw.beans.bt.es.data.ResourceProviderInformation;
 import com.deepclone.lw.beans.bt.es.data.ShipsInformation;
 import com.deepclone.lw.beans.bt.es.data.SystemInformation;
 import com.deepclone.lw.interfaces.bt.EmpireSummary;
@@ -40,6 +42,36 @@ import com.thoughtworks.xstream.XStream;
 public class EmpireSummaryBean
 		implements EmpireSummary
 {
+	/** Beginning of all dump SQL queries */
+	private static final String SQL_START = "SELECT * FROM bugs.dump_";
+
+	/** Ending of (almost) all dump SQL queries */
+	private static final String SQL_END = "_view WHERE empire_id = ?";
+
+	/** SQL query that accesses the main empire dump view */
+	private static final String Q_EMPIRE = SQL_START + "main" + SQL_END;
+
+	/** SQL query that accesses the research dump view */
+	private static final String Q_RESEARCH = SQL_START + "research" + SQL_END;
+
+	/** SQL query that accesses the empire's planets dump view */
+	private static final String Q_PLANETS = SQL_START + "planets" + SQL_END;
+
+	/** SQL query that accesses the planetary resources dump view */
+	private static final String Q_PLANET_RESOURCES = SQL_START + "planet_resources" + SQL_END;
+
+	/** SQL query that accesses the build queues dump view */
+	private static final String Q_BUILD_QUEUES = SQL_START + "queues" + SQL_END + " ORDER BY queue_order";
+
+	/** SQL query that accesses the buildings dump view */
+	private static final String Q_BUILDINGS = SQL_START + "buildings" + SQL_END;
+
+	/** SQL query that accesses the fleets dump view */
+	private static final String Q_FLEETS = SQL_START + "fleets" + SQL_END;
+
+	/** SQL query that accesses the ships dump view */
+	private static final String Q_SHIPS = SQL_START + "ships" + SQL_END;
+
 	/** JDBC access interface */
 	private JdbcTemplate dTemplate;
 
@@ -55,6 +87,9 @@ public class EmpireSummaryBean
 	/** Empire-owned planet row mapper */
 	private final PlanetInformationMapper mPlanet;
 
+	/** Planet resources row mapper */
+	private final ResourceRowMapper mPlanetResources;
+
 	/** Planet construction queue item row mapper */
 	private final QueueItemInformationMapper mQueueItem;
 
@@ -79,12 +114,14 @@ public class EmpireSummaryBean
 				AccountInformation.class , AllianceInformation.class , BuildingsInformation.class ,
 				DebugInformation.class , EmpireInformation.class , FleetInformation.class , MovementInformation.class ,
 				PlanetInformation.class , QueueInformation.class , QueueItemInformation.class ,
-				ResearchInformation.class , ShipsInformation.class , SystemInformation.class
+				ResearchInformation.class , ResourceDeltaInformation.class , ResourceProviderInformation.class ,
+				ShipsInformation.class , SystemInformation.class
 		} );
 
 		this.mMainInfo = new DebugInformationMapper( );
 		this.mResearch = new ResearchInformationMapper( );
 		this.mPlanet = new PlanetInformationMapper( );
+		this.mPlanetResources = new ResourceRowMapper( );
 		this.mQueueItem = new QueueItemInformationMapper( );
 		this.mBuildings = new BuildingsInformationMapper( );
 		this.mFleet = new FleetInformationMapper( );
@@ -116,46 +153,174 @@ public class EmpireSummaryBean
 	@Override
 	public String getSummary( int empireId )
 	{
-		String sql = "SELECT * FROM bugs.dump_main_view WHERE empire_id = ?";
-		DebugInformation di = this.dTemplate.queryForObject( sql , this.mMainInfo , empireId );
+		DebugInformation empireDump = this.dTemplate.queryForObject( Q_EMPIRE , this.mMainInfo , empireId );
+		this.getResearch( empireId , empireDump );
 
-		sql = "SELECT * FROM bugs.dump_research_view WHERE empire_id = ?";
-		for ( ResearchInformation ri : this.dTemplate.query( sql , this.mResearch , empireId ) ) {
-			di.getResearch( ).add( ri );
+		this.getPlanets( empireId , empireDump );
+		this.getFleets( empireId , empireDump );
+
+		return this.xStream.toXML( empireDump );
+	}
+
+
+	/**
+	 * Get research information
+	 * 
+	 * <p>
+	 * Read all research-related information from the appropriate dump view, and add corresponding
+	 * entries to the top-level instance.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param empireDump
+	 *            the top-level instance
+	 */
+	private void getResearch( int empireId , DebugInformation empireDump )
+	{
+		for ( ResearchInformation ri : this.dTemplate.query( Q_RESEARCH , this.mResearch , empireId ) ) {
+			empireDump.getResearch( ).add( ri );
+		}
+	}
+
+
+	/**
+	 * Get empire planets and related information
+	 * 
+	 * <p>
+	 * Read the planet list and basic planet information from the appropriate dump view, then read
+	 * all extra information (resources, build queues, buildings).
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param empireDump
+	 *            the top-level instance
+	 */
+	private void getPlanets( int empireId , DebugInformation empireDump )
+	{
+		HashMap< Integer , PlanetInformation > planets = new HashMap< Integer , PlanetInformation >( );
+		for ( PlanetInformation planet : this.dTemplate.query( Q_PLANETS , this.mPlanet , empireId ) ) {
+			empireDump.getPlanets( ).add( planet );
+			planets.put( planet.getId( ) , planet );
 		}
 
-		sql = "SELECT * FROM bugs.dump_planets_view WHERE empire_id = ?";
-		Map< Integer , PlanetInformation > planets = new HashMap< Integer , PlanetInformation >( );
-		for ( PlanetInformation pi : this.dTemplate.query( sql , this.mPlanet , empireId ) ) {
-			di.getPlanets( ).add( pi );
-			planets.put( pi.getId( ) , pi );
-		}
+		this.getPlanetResources( empireId , planets );
+		this.getBuildQueues( empireId , planets );
+		this.getBuildings( empireId , planets );
+	}
 
-		sql = "SELECT * FROM bugs.dump_queues_view WHERE empire_id = ? ORDER BY queue_order";
-		for ( QueueItemInformation qii : this.dTemplate.query( sql , this.mQueueItem , empireId ) ) {
-			PlanetInformation pi = planets.get( qii.getPlanetId( ) );
-			QueueInformation qi = ( qii.isMilitary( ) ? pi.getMilitaryQueue( ) : pi.getCivilianQueue( ) );
-			qi.getItems( ).add( qii );
-		}
 
-		sql = "SELECT * FROM bugs.dump_buildings_view WHERE empire_id = ?";
-		for ( BuildingsInformation bi : this.dTemplate.query( sql , this.mBuildings , empireId ) ) {
+	/**
+	 * Add resources information to planets
+	 * 
+	 * <p>
+	 * Access resources information for the empire's planets and add the records to each planet.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param planets
+	 *            a map associating planet records with their identifier
+	 */
+	private void getPlanetResources( int empireId , Map< Integer , PlanetInformation > planets )
+	{
+		for ( PlanetResourceRow resRow : this.dTemplate.query( Q_PLANET_RESOURCES , this.mPlanetResources , empireId ) ) {
+			PlanetInformation planet = planets.get( resRow.getPlanetId( ) );
+			planet.getResourceDeltas( ).add( resRow.getDelta( ) );
+
+			ResourceProviderInformation resProv = resRow.getProvider( );
+			if ( resProv != null ) {
+				planet.getResourceProviders( ).add( resProv );
+			}
+		}
+	}
+
+
+	/**
+	 * Add build queue information to planet records
+	 * 
+	 * <p>
+	 * Access the contents of build queues and add the records to the corresponding planets.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param planets
+	 *            the map of planets by identifier
+	 */
+	private void getBuildQueues( int empireId , Map< Integer , PlanetInformation > planets )
+	{
+		for ( QueueItemInformation queueItem : this.dTemplate.query( Q_BUILD_QUEUES , this.mQueueItem , empireId ) ) {
+			PlanetInformation planet = planets.get( queueItem.getPlanetId( ) );
+
+			QueueInformation queue;
+			if ( queueItem.isMilitary( ) ) {
+				queue = planet.getMilitaryQueue( );
+			} else {
+				queue = planet.getCivilianQueue( );
+			}
+
+			queue.getItems( ).add( queueItem );
+		}
+	}
+
+
+	/**
+	 * Add buildings information to planet records
+	 * 
+	 * <p>
+	 * Access the contents of the buildings dump view and add the building records to the
+	 * corresponding planets.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param planets
+	 *            the map of planets by identifier
+	 */
+	private void getBuildings( int empireId , Map< Integer , PlanetInformation > planets )
+	{
+		for ( BuildingsInformation bi : this.dTemplate.query( Q_BUILDINGS , this.mBuildings , empireId ) ) {
 			planets.get( bi.getPlanetId( ) ).getBuildings( ).add( bi );
 		}
+	}
 
-		sql = "SELECT * FROM bugs.dump_fleets_view WHERE empire_id = ?";
+
+	/**
+	 * Retrieve information about the empire's fleets
+	 * 
+	 * <p>
+	 * Access the list of fleets, add the records to the top-level instance, then retrieve all
+	 * necessary information.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param empireDump
+	 *            the top-level empire dump
+	 */
+	private void getFleets( int empireId , DebugInformation empireDump )
+	{
 		Map< Long , FleetInformation > fleets = new HashMap< Long , FleetInformation >( );
-		for ( FleetInformation fi : this.dTemplate.query( sql , this.mFleet , empireId ) ) {
-			di.getFleets( ).add( fi );
+		for ( FleetInformation fi : this.dTemplate.query( Q_FLEETS , this.mFleet , empireId ) ) {
+			empireDump.getFleets( ).add( fi );
 			fleets.put( fi.getId( ) , fi );
 		}
+		this.getShips( empireId , fleets );
+	}
 
-		sql = "SELECT * FROM bugs.dump_ships_view WHERE empire_id = ?";
-		for ( ShipsInformation si : this.dTemplate.query( sql , this.mShips , empireId ) ) {
+
+	/**
+	 * Add ships information to fleet records
+	 * 
+	 * <p>
+	 * Retrieve information about ships and add the records to the corresponding fleets.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param fleets
+	 *            the map of fleets by identifier
+	 */
+	private void getShips( int empireId , Map< Long , FleetInformation > fleets )
+	{
+		for ( ShipsInformation si : this.dTemplate.query( Q_SHIPS , this.mShips , empireId ) ) {
 			fleets.get( si.getFleetId( ) ).getShips( ).add( si );
 		}
-
-		return this.xStream.toXML( di );
 	}
 
 }
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/PlanetResourceRow.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/PlanetResourceRow.java
new file mode 100644
index 0000000..3d6a7e2
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/PlanetResourceRow.java
@@ -0,0 +1,96 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import com.deepclone.lw.beans.bt.es.data.ResourceDeltaInformation;
+import com.deepclone.lw.beans.bt.es.data.ResourceProviderInformation;
+
+
+
+/**
+ * Planet resource row information
+ * 
+ * <p>
+ * Planet resources and resource providers are both extracted from the same
+ * <code>bugs.planet_resources_view</code> rows. Because of that, this class is used to transmit the
+ * information (as a {@link ResourceDeltaInformation} instance and an optional
+ * {@link ResourceProviderInformation} instance) from the row mapper to the summary creation
+ * component.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class PlanetResourceRow
+{
+
+	/** Identifier of the planet */
+	private final int planetId;
+
+	/** Resource delta information */
+	private final ResourceDeltaInformation delta = new ResourceDeltaInformation( );
+
+	/** Resource provider information */
+	private ResourceProviderInformation provider;
+
+
+	/**
+	 * Initialise the instance by setting the planet identifier
+	 * 
+	 * @param planetId
+	 *            the planet's identifier
+	 */
+	public PlanetResourceRow( int planetId )
+	{
+		this.planetId = planetId;
+	}
+
+
+	/** @return the planet's identifier */
+	public int getPlanetId( )
+	{
+		return this.planetId;
+	}
+
+
+	/** @return the resource delta information record */
+	public ResourceDeltaInformation getDelta( )
+	{
+		return this.delta;
+	}
+
+
+	/** @return the resource provider information record */
+	public ResourceProviderInformation getProvider( )
+	{
+		return this.provider;
+	}
+
+
+	/**
+	 * Set the resource provider information record
+	 * 
+	 * @param provider
+	 *            the resource provider information record
+	 */
+	public void setProvider( ResourceProviderInformation provider )
+	{
+		this.provider = provider;
+	}
+
+
+	/**
+	 * Set the name of the resource
+	 * 
+	 * <p>
+	 * This method should be called once the rest of the record(s) has been initialised. It will set
+	 * the resource name on both the delta and the provider (if the latter exists).
+	 * 
+	 * @param resource
+	 *            the name of the resource
+	 */
+	public void setResource( String resource )
+	{
+		this.delta.setResource( resource );
+		if ( this.provider != null ) {
+			this.provider.setResource( resource );
+		}
+	}
+}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResourceRowMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResourceRowMapper.java
new file mode 100644
index 0000000..5925cfd
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResourceRowMapper.java
@@ -0,0 +1,58 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.ResourceProviderInformation;
+
+
+
+/**
+ * Mapper which extract planet resources and resource providers information
+ * 
+ * <p>
+ * This class maps rows from <code>bugs.dump_planet_resources_view</code> into
+ * {@link PlanetResourceRow} instances.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+class ResourceRowMapper
+		implements RowMapper< PlanetResourceRow >
+{
+
+	/**
+	 * Map a row from <code>bugs.dump_planet_resources_view</code>
+	 * 
+	 * <p>
+	 * Generate the {@link PlanetResourceRow} instance with the correct planet identifier, resource
+	 * name, income and upkeep. If there is also a resource provider, attach a
+	 * {@link ResourceProviderInformation} instance to the result.
+	 */
+	@Override
+	public PlanetResourceRow mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		PlanetResourceRow row = new PlanetResourceRow( rs.getInt( "planet_id" ) );
+
+		row.getDelta( ).setIncome( rs.getDouble( "pres_income" ) );
+		row.getDelta( ).setUpkeep( rs.getDouble( "pres_upkeep" ) );
+
+		double pCapacity = rs.getDouble( "resprov_quantity_max" );
+		if ( !rs.wasNull( ) ) {
+			ResourceProviderInformation rpi = new ResourceProviderInformation( );
+			rpi.setMaximalQuantity( pCapacity );
+			rpi.setCurrentQuantity( rs.getDouble( "resprov_quantity" ) );
+			rpi.setDifficulty( rs.getDouble( "resprov_difficulty" ) );
+			rpi.setRecovery( rs.getDouble( "resprov_recovery" ) );
+			row.setProvider( rpi );
+		}
+
+		row.setResource( rs.getString( "resource_name" ) );
+		return row;
+	}
+
+}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
index 76074b8..2959182 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
@@ -19,7 +19,7 @@ public class DebugInformation
 
 	@XStreamAsAttribute
 	@XStreamAlias( "dump-version" )
-	private int version = 1;
+	private int version = 2;
 
 	private SystemInformation system = new SystemInformation( );
 
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/InvalidDumpContentsException.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/InvalidDumpContentsException.java
new file mode 100644
index 0000000..2044984
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/InvalidDumpContentsException.java
@@ -0,0 +1,60 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import java.io.Serializable;
+
+
+
+/**
+ * Invalid dump contents exception
+ * 
+ * <p>
+ * This runtime exception is thrown by the debugging data record classes when one of the fields is
+ * set to an incorrect value.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public final class InvalidDumpContentsException
+		extends RuntimeException
+{
+	/** Serialisation identifier */
+	private static final long serialVersionUID = 1L;
+
+	/** Class in which the error was caused */
+	private final Class< ? extends Serializable > recordType;
+
+	/** Name of the field to which invalid contents were assigned */
+	private final String field;
+
+
+	/**
+	 * Initialise the exception by setting the record and field
+	 * 
+	 * @param type
+	 *            the type of the XML dump record
+	 * @param field
+	 *            the field to which invalid contents were assigned
+	 */
+	InvalidDumpContentsException( Class< ? extends Serializable > type , String field )
+	{
+		super( "Invalid contents for field " + field + " of record " + type.getSimpleName( ) );
+		this.recordType = type;
+		this.field = field;
+	}
+
+
+	/** @return the type of the XML dump record */
+	public Class< ? extends Serializable > getRecordType( )
+	{
+		return this.recordType;
+	}
+
+
+	/** @return the field to which invalid contents were assigned */
+	public String getField( )
+	{
+		return this.field;
+	}
+
+}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
index de9c6b6..bd60545 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
@@ -7,118 +7,204 @@ import java.util.List;
 
 import com.thoughtworks.xstream.annotations.XStreamAlias;
 import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
 
 
 
+/**
+ * Planet XML record
+ * 
+ * <p>
+ * This class is used in XML dumps to represent the information about a planet. It includes a few
+ * basic values, as well as information about buildings, queues and resources.
+ * 
+ * <p>
+ * This record exists since version 1 (B6M1) of XML dumps. However, the following changes were made
+ * later.
+ * <ul>
+ * <li>Version 2 (B6M2):
+ * <ul>
+ * <li>resource providers added,
+ * </ul>
+ * </ul>
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
 @XStreamAlias( "planet" )
+@SuppressWarnings( "serial" )
 public class PlanetInformation
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
-
+	/** Planet identifier */
 	@XStreamAsAttribute
-	@XStreamAlias( "id" )
-	private int id;
+	private Integer id;
 
+	/** Planet name */
 	@XStreamAsAttribute
-	@XStreamAlias( "id" )
 	private String name;
 
+	/** Current population of the planet */
 	@XStreamAlias( "population" )
-	private double population;
+	private Double population;
 
+	/** Current happiness of the planet's population */
 	@XStreamAlias( "current-happiness" )
-	private double currentHappiness;
+	private Double currentHappiness;
 
+	/** Target happiness of the planet's population */
 	@XStreamAlias( "target-happiness" )
 	private double targetHappiness;
 
+	/** List of buildings */
 	@XStreamAlias( "buildings" )
-	private List< BuildingsInformation > buildings = new LinkedList< BuildingsInformation >( );
+	private final List< BuildingsInformation > buildings = new LinkedList< BuildingsInformation >( );
 
+	/** Civilian construction queue */
 	@XStreamAlias( "civ-queue" )
-	private QueueInformation civilianQueue = new QueueInformation( );
+	private final QueueInformation civilianQueue = new QueueInformation( );
 
+	/** Military construction queue */
 	@XStreamAlias( "mil-queue" )
-	private QueueInformation militaryQueue = new QueueInformation( );
+	private final QueueInformation militaryQueue = new QueueInformation( );
+
+	/** Planet resource deltas */
+	@XStreamImplicit
+	private final LinkedList< ResourceDeltaInformation > resourceDeltas = new LinkedList< ResourceDeltaInformation >( );
+
+	/** List of resource providers */
+	@XStreamImplicit( itemFieldName = "resource-provider" )
+	private final LinkedList< ResourceProviderInformation > resourceProviders = new LinkedList< ResourceProviderInformation >( );
 
 
-	public int getId( )
+	/** @return the planet's identifier */
+	public Integer getId( )
 	{
-		return id;
+		return this.id;
 	}
 
 
+	/**
+	 * Set the planet's identifier
+	 * 
+	 * @param id
+	 *            the planet's identifier
+	 */
 	public void setId( int id )
 	{
 		this.id = id;
 	}
 
 
+	/** @return the planet's name */
 	public String getName( )
 	{
-		return name;
+		return this.name;
 	}
 
 
+	/**
+	 * Set the planet's name
+	 * 
+	 * @param name
+	 *            the planet's name
+	 */
 	public void setName( String name )
 	{
 		this.name = name;
 	}
 
 
-	public double getPopulation( )
+	/** @return the planet's current population */
+	public Double getPopulation( )
 	{
-		return population;
+		return this.population;
 	}
 
 
+	/**
+	 * Set the planet's current population
+	 * 
+	 * @param population
+	 *            the planet's current population
+	 */
 	public void setPopulation( double population )
 	{
 		this.population = population;
 	}
 
 
-	public double getCurrentHappiness( )
+	/** @return the current happiness of the planet's population */
+	public Double getCurrentHappiness( )
 	{
-		return currentHappiness;
+		return this.currentHappiness;
 	}
 
 
+	/**
+	 * Set the current happiness of the planet's population
+	 * 
+	 * @param currentHappiness
+	 *            the current happiness of the planet's population
+	 */
 	public void setCurrentHappiness( double currentHappiness )
 	{
 		this.currentHappiness = currentHappiness;
 	}
 
 
-	public double getTargetHappiness( )
+	/** @return the target happiness of the planet's population */
+	public Double getTargetHappiness( )
 	{
-		return targetHappiness;
+		return this.targetHappiness;
 	}
 
 
+	/**
+	 * Set the target happiness of the planet's population
+	 * 
+	 * @param targetHappiness
+	 *            the target happiness of the planet's population
+	 */
 	public void setTargetHappiness( double targetHappiness )
 	{
 		this.targetHappiness = targetHappiness;
 	}
 
 
+	/** @return the list of buildings constructed on the planet */
 	public List< BuildingsInformation > getBuildings( )
 	{
-		return buildings;
+		return this.buildings;
 	}
 
 
+	/** @return the civilian construction queue */
 	public QueueInformation getCivilianQueue( )
 	{
-		return civilianQueue;
+		return this.civilianQueue;
 	}
 
 
+	/** @return the military construction queue */
 	public QueueInformation getMilitaryQueue( )
 	{
-		return militaryQueue;
+		return this.militaryQueue;
+	}
+
+
+	/** @return the list of resource delta records */
+	public LinkedList< ResourceDeltaInformation > getResourceDeltas( )
+	{
+		return this.resourceDeltas;
+	}
+
+
+	/** @return the list of resource provider records */
+	public LinkedList< ResourceProviderInformation > getResourceProviders( )
+	{
+		return this.resourceProviders;
 	}
 
 }
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceDeltaInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceDeltaInformation.java
new file mode 100644
index 0000000..679b077
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceDeltaInformation.java
@@ -0,0 +1,109 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * Resource delta information
+ * 
+ * <p>
+ * This class represents records which contain a resource's delta - that is, the income and upkeep
+ * for some type of resource.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "resource-delta" )
+public class ResourceDeltaInformation
+		implements Serializable
+{
+
+	/** Identifier of the resource */
+	@XStreamAlias( "id" )
+	@XStreamAsAttribute
+	private String resource;
+
+	/** Income for that type of resource */
+	@XStreamAsAttribute
+	private Double income;
+
+	/** Upkeep for that type of resource */
+	@XStreamAsAttribute
+	private Double upkeep;
+
+
+	/** @return the resource's identifier */
+	public String getResource( )
+	{
+		return this.resource;
+	}
+
+
+	/**
+	 * Set the resource's identifier
+	 * 
+	 * @param resource
+	 *            the resource's identifier
+	 * 
+	 * @throws InvalidDumpContentsException
+	 *             if the specified resource type is <code>null</code>
+	 */
+	public void setResource( String resource )
+			throws InvalidDumpContentsException
+	{
+		if ( resource == null ) {
+			throw new InvalidDumpContentsException( this.getClass( ) , "resource" );
+		}
+		this.resource = resource;
+	}
+
+
+	/**
+	 * @return the income for that type of resource, or <code>null</code> if no income information
+	 *         is available
+	 */
+	public Double getIncome( )
+	{
+		return this.income;
+	}
+
+
+	/**
+	 * Set the income for that type of resource
+	 * 
+	 * @param income
+	 *            the income for that type of resource
+	 */
+	public void setIncome( double income )
+	{
+		this.income = income;
+	}
+
+
+	/**
+	 * @return the upkeep for that type of resource, or <code>null</code> if no upkeep information
+	 *         is available
+	 */
+	public Double getUpkeep( )
+	{
+		return this.upkeep;
+	}
+
+
+	/**
+	 * Set the upkeep for that type of resource
+	 * 
+	 * @param upkeep
+	 *            the upkeep for that type of resource
+	 */
+	public void setUpkeep( double upkeep )
+	{
+		this.upkeep = upkeep;
+	}
+
+}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceProviderInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceProviderInformation.java
new file mode 100644
index 0000000..c29054c
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceProviderInformation.java
@@ -0,0 +1,152 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * Resource provider XML record
+ * 
+ * <p>
+ * This class represents the information about a resource provider that will be serialised to the
+ * debugging XML dump when a player posts a bug report.
+ * 
+ * <p>
+ * This record exists since version 2 (B6M2) of XML dumps.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+public class ResourceProviderInformation
+		implements Serializable
+{
+
+	/** Identifier of the resource */
+	@XStreamAsAttribute
+	private String resource;
+
+	/** Maximal quantity in the provider */
+	@XStreamAsAttribute
+	@XStreamAlias( "max" )
+	private Double maximalQuantity;
+
+	/** Current quantity in the provider */
+	@XStreamAsAttribute
+	@XStreamAlias( "current" )
+	private Double currentQuantity;
+
+	/** Extraction difficulty */
+	@XStreamAsAttribute
+	private Double difficulty;
+
+	/** Recovery rate */
+	@XStreamAsAttribute
+	private Double recovery;
+
+
+	/** @return the resource's identifier */
+	public String getResource( )
+	{
+		return this.resource;
+	}
+
+
+	/**
+	 * Set the resource's identifier
+	 * 
+	 * @param resource
+	 *            the resource's identifier
+	 * 
+	 * @throws InvalidDumpContentsException
+	 *             if the resource's identifier is <code>null</code>
+	 */
+	public void setResource( String resource )
+			throws InvalidDumpContentsException
+	{
+		if ( resource == null ) {
+			throw new InvalidDumpContentsException( this.getClass( ) , "resource" );
+		}
+		this.resource = resource;
+	}
+
+
+	/** @return the provider's total capacity */
+	public Double getMaximalQuantity( )
+	{
+		return this.maximalQuantity;
+	}
+
+
+	/**
+	 * Set the maximal quantity of resources in the provider
+	 * 
+	 * @param maximalQuantity
+	 *            the provider's total capacity
+	 */
+	public void setMaximalQuantity( double maximalQuantity )
+	{
+		this.maximalQuantity = maximalQuantity;
+	}
+
+
+	/** @return the current quantity of resources in the provider */
+	public Double getCurrentQuantity( )
+	{
+		return this.currentQuantity;
+	}
+
+
+	/**
+	 * Set the current quantity of resources in the provider
+	 * 
+	 * @param currentQuantity
+	 *            the current quantity of resources in the provider
+	 */
+	public void setCurrentQuantity( double currentQuantity )
+	{
+		this.currentQuantity = currentQuantity;
+	}
+
+
+	/** @return the provider's extraction difficulty */
+	public Double getDifficulty( )
+	{
+		return this.difficulty;
+	}
+
+
+	/**
+	 * Set the provider's extraction difficulty
+	 * 
+	 * @param difficulty
+	 *            the provider's extraction difficulty
+	 */
+	public void setDifficulty( double difficulty )
+	{
+		this.difficulty = difficulty;
+	}
+
+
+	/** @return the provider's recovery rate */
+	public Double getRecovery( )
+	{
+		return this.recovery;
+	}
+
+
+	/**
+	 * Set the provider's recovery rate
+	 * 
+	 * @param recovery
+	 *            the provider's recovery rate
+	 */
+	public void setRecovery( double recovery )
+	{
+		this.recovery = recovery;
+	}
+
+}
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
index f8d7282..1d9a23d 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
@@ -1220,6 +1220,54 @@ CREATE VIEW bugs.dump_planets_view
 GRANT SELECT ON bugs.dump_planets_view TO :dbuser;
 
 
+
+/*
+ * Planet resources view for XML dumps
+ * ------------------------------------
+ * 
+ * This view combines both planet resources and resource providers for all
+ * empire-owned planets. It is meant to be used in the XML dump generator.
+ * 
+ * 
+ * Columns:
+ *		empire_id				The empire's identifier
+ *		planet_id				The planet's identifier
+ *		resource_name			The string identifying the resource
+ *		pres_income				The planet's income for that resource type 
+ *		pres_upkeep				The planet's upkeep for that resource type
+ *		resprov_quantity_max	The resource provider's capacity, or NULL
+ *									if there is no resource provider of that
+ *									type on the planet
+ *		resprov_quantity		The resource provider's current quantity, or
+ *									NULL if there is no resource provider of
+ *									that type on the planet
+ *		resprov_difficulty		The resource provider's extraction difficulty,
+ *									or NULL if there is no resource provider
+ *									of that type on the planet
+ *		resprov_recovery		The resource provider's recovery rate, or NULL
+ *									if there is no resource provider of that
+ *									 type on the planet
+ */
+CREATE VIEW bugs.dump_planet_resources_view
+	AS SELECT empire_id , planet_id ,
+				name AS resource_name ,
+				pres_income , pres_upkeep ,
+				resprov_quantity_max , resprov_quantity ,
+				resprov_difficulty , resprov_recovery
+			FROM emp.planets
+				INNER JOIN verse.planet_resources
+					USING ( planet_id )
+				INNER JOIN defs.strings
+					ON resource_name_id = id
+				LEFT OUTER JOIN verse.resource_providers
+					USING ( planet_id , resource_name_id )
+			ORDER BY name;
+
+GRANT SELECT
+	ON bugs.dump_planet_resources_view
+	TO :dbuser;
+
+
 CREATE VIEW bugs.dump_queues_view
 	AS SELECT ep.empire_id , ep.planet_id , FALSE AS military , q.queue_order ,
 				q.building_id AS item_id , qin.name AS item_name ,
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/010-dump-planet-resources-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/010-dump-planet-resources-view.sql
new file mode 100644
index 0000000..23c8aa5
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/010-dump-planet-resources-view.sql
@@ -0,0 +1,73 @@
+/*
+ * Tests for bugs.dump_planet_resources_view
+ */
+BEGIN;
+	/*
+	 * We need a couple of resources (one natural, one basic), three planets
+	 * with valid planet resource records (two of the planets will have a
+	 * resource provider), two empires (owning a planet with and without
+	 * resource providers, respectively). 
+	 */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 1 , 'natRes' );
+	SELECT _create_resources( 1 , 'basicRes' );
+	SELECT _create_raw_planets( 3 , 'planet' );
+	INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income , pres_upkeep
+		) VALUES (
+			_get_map_name( 'planet1' ) , _get_string( 'basicRes1' ) , 1 , 2
+		) , (
+			_get_map_name( 'planet1' ) , _get_string( 'natRes1' ) , 3 , 4
+		) , (
+			_get_map_name( 'planet2' ) , _get_string( 'basicRes1' ) , 5 , 6
+		) , (
+			_get_map_name( 'planet2' ) , _get_string( 'natRes1' ) , 7 , 8
+		) , (
+			_get_map_name( 'planet3' ) , _get_string( 'basicRes1' ) , 9 , 10
+		) , (
+			_get_map_name( 'planet3' ) , _get_string( 'natRes1' ) , 11 , 12
+		);
+	SELECT _create_resource_provider( 'planet1' , 'natRes1' );
+	SELECT _create_resource_provider( 'planet3' , 'natRes1' );
+
+	SELECT _create_emp_names( 2 , 'empire' );
+	SELECT emp.create_empire( _get_emp_name( 'empire1' ) ,
+				_get_map_name( 'planet1' ) ,
+				200.0 );
+	SELECT emp.create_empire( _get_emp_name( 'empire2' ) ,
+				_get_map_name( 'planet2' ) ,
+				200.0 );
+
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 2 );
+
+	SELECT diag_test_name( 'bugs.dump_planet_resources_view - Records without resource providers' );
+	SELECT set_eq( $$
+		SELECT empire_id , planet_id , resource_name , pres_income , pres_upkeep
+			FROM bugs.dump_planet_resources_view
+			WHERE resprov_quantity IS NULL
+	$$ , $$ VALUES (
+		_get_emp_name( 'empire1' ) , _get_map_name( 'planet1' ) , 'basicRes1' , 1 , 2
+	) , (
+		_get_emp_name( 'empire2' ) , _get_map_name( 'planet2' ) , 'basicRes1' , 5 , 6
+	) , (
+		_get_emp_name( 'empire2' ) , _get_map_name( 'planet2' ) , 'natRes1' , 7 , 8
+	) $$ );
+
+	SELECT diag_test_name( 'bugs.dump_planet_resources_view - Records with resource providers' );
+	SELECT set_eq( $$
+		SELECT empire_id , planet_id , resource_name , pres_income , pres_upkeep
+			FROM bugs.dump_planet_resources_view
+			WHERE resprov_quantity IS NOT NULL
+	$$ , $$ VALUES (
+		_get_emp_name( 'empire1' ) , _get_map_name( 'planet1' ) , 'natRes1' , 3 , 4
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/200-bugs/010-dump-planet-resources-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/200-bugs/010-dump-planet-resources-view.sql
new file mode 100644
index 0000000..5100491
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/200-bugs/010-dump-planet-resources-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on bugs.dump_planet_resources_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'bugs.dump_planet_resources_view - Privileges' );
+	SELECT lives_ok( 'SELECT * FROM bugs.dump_planet_resources_view' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestBuildingsInformationMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestBuildingsInformationMapper.java
index c32bf40..cb5209a 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestBuildingsInformationMapper.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestBuildingsInformationMapper.java
@@ -16,6 +16,8 @@ import com.deepclone.lw.testing.MockResultSet;
 
 /**
  * Tests for the {@link BuildingsInformationMapper} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
  */
 public class TestBuildingsInformationMapper
 {
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestPlanetResourceRow.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestPlanetResourceRow.java
new file mode 100644
index 0000000..f02a3a9
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestPlanetResourceRow.java
@@ -0,0 +1,102 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.beans.bt.es.data.ResourceProviderInformation;
+
+
+
+/**
+ * Tests for the {@link PlanetResourceRow} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestPlanetResourceRow
+{
+
+	/** Identifier of the planet in the test object */
+	private static final int TEST_IDENTIFIER = 42;
+
+	/** Resource name used in the tests */
+	private static final String TEST_NAME = "Test";
+
+	/** The planet resource row instance used in tests */
+	private PlanetResourceRow prr;
+
+
+	/**
+	 * Create a planet resource row instance with the planet identifier specified by
+	 * {@link #TEST_IDENTIFIER}.
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.prr = new PlanetResourceRow( TEST_IDENTIFIER );
+	}
+
+
+	/**
+	 * Planet identifier has been initialised correctly.
+	 */
+	@Test
+	public void testPlanetIdentifier( )
+	{
+		assertEquals( TEST_IDENTIFIER , this.prr.getPlanetId( ) );
+	}
+
+
+	/**
+	 * By default, the resource delta exists, the provider doesn't.
+	 */
+	@Test
+	public void testDefaultValues( )
+	{
+		assertNotNull( this.prr.getDelta( ) );
+		assertNull( this.prr.getProvider( ) );
+	}
+
+
+	/**
+	 * Setting and reading the resource provider record.
+	 */
+	@Test
+	public void testSetProvider( )
+	{
+		ResourceProviderInformation rpi = new ResourceProviderInformation( );
+		rpi.setResource( TEST_NAME );
+		this.prr.setProvider( rpi );
+		assertNotNull( this.prr.getProvider( ) );
+		assertEquals( TEST_NAME , this.prr.getProvider( ).getResource( ) );
+	}
+
+
+	/**
+	 * Setting the resource name when there is no resource provider record.
+	 */
+	@Test
+	public void testSetNameDeltaOnly( )
+	{
+		this.prr.setResource( TEST_NAME );
+		assertEquals( TEST_NAME , this.prr.getDelta( ).getResource( ) );
+		assertNull( this.prr.getProvider( ) );
+	}
+
+
+	/**
+	 * Setting the resource name when there are both a resource delta record and a resource provider
+	 * record.
+	 */
+	@Test
+	public void testSetNameFull( )
+	{
+		this.prr.setProvider( new ResourceProviderInformation( ) );
+		this.prr.setResource( TEST_NAME );
+		assertEquals( TEST_NAME , this.prr.getDelta( ).getResource( ) );
+		assertNotNull( this.prr.getProvider( ) );
+		assertEquals( TEST_NAME , this.prr.getProvider( ).getResource( ) );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestResourceRowMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestResourceRowMapper.java
new file mode 100644
index 0000000..2235fbd
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestResourceRowMapper.java
@@ -0,0 +1,129 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.testing.MockResultSet;
+
+
+
+/**
+ * Tests for the {@link ResourceRowMapper} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestResourceRowMapper
+{
+	/** Planet identifiers found in the "results" */
+	private static final int[] PLANET_IDS = new int[] {
+			1 , 2
+	};
+
+	/** Resource names found in the two "results", respectively */
+	private static final String[] RESOURCE_NAMES = new String[] {
+			"Test1" , "Test2"
+	};
+
+	/** Income values found in the two results */
+	private static final double[] INCOME_VALUES = new double[] {
+			3.0 , 4.0
+	};
+
+	/** Upkeep values found in the two results */
+	private static final double[] UPKEEP_VALUES = new double[] {
+			5.0 , 6.0
+	};
+
+	/** Resource provider quantity for the second row */
+	private static final double RP_QUANTITY = 7.0;
+
+	/** Resource provider capacity for the second row */
+	private static final double RP_CAPACITY = 8.0;
+
+	/** Resource provider extraction difficulty for the second row */
+	private static final double RP_DIFFICULTY = 9.0;
+
+	/** Resource provider recovery rate for the second row */
+	private static final double RP_RECOVERY = 10.0;
+
+	/** The fake result set used in the tests */
+	private ResultSet resultSet;
+
+	/** The mapper used in the tests */
+	private ResourceRowMapper mapper;
+
+
+	/** Create the mapper and the contents of the fake result set */
+	@Before
+	public void setUp( )
+	{
+		this.mapper = new ResourceRowMapper( );
+
+		@SuppressWarnings( "unchecked" )
+		HashMap< String , Object > rows[] = new HashMap[ 2 ];
+		for ( int i = 0 ; i < 2 ; i++ ) {
+			HashMap< String , Object > row = new HashMap< String , Object >( );
+			row.put( "planet_id" , PLANET_IDS[ i ] );
+			row.put( "resource_name" , RESOURCE_NAMES[ i ] );
+			row.put( "pres_income" , INCOME_VALUES[ i ] );
+			row.put( "pres_upkeep" , UPKEEP_VALUES[ i ] );
+			if ( i == 1 ) {
+				row.put( "resprov_quantity_max" , RP_CAPACITY );
+				row.put( "resprov_quantity" , RP_QUANTITY );
+				row.put( "resprov_difficulty" , RP_DIFFICULTY );
+				row.put( "resprov_recovery" , RP_RECOVERY );
+			}
+			rows[ i ] = row;
+		}
+
+		this.resultSet = MockResultSet.create( rows );
+	}
+
+
+	/**
+	 * Mapping a row with no provider information
+	 */
+	@Test
+	public void testMapNoProvider( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 1 );
+
+		PlanetResourceRow row = this.mapper.mapRow( this.resultSet , 0 );
+		assertNotNull( row );
+		assertEquals( PLANET_IDS[ 0 ] , row.getPlanetId( ) );
+		assertEquals( RESOURCE_NAMES[ 0 ] , row.getDelta( ).getResource( ) );
+		assertEquals( INCOME_VALUES[ 0 ] , row.getDelta( ).getIncome( ) , 0 );
+		assertEquals( UPKEEP_VALUES[ 0 ] , row.getDelta( ).getUpkeep( ) , 0 );
+		assertNull( row.getProvider( ) );
+	}
+
+
+	/**
+	 * Mapping a row with a provider information record
+	 */
+	@Test
+	public void testMapWithProvider( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 2 );
+
+		PlanetResourceRow row = this.mapper.mapRow( this.resultSet , 0 );
+		assertNotNull( row );
+		assertEquals( PLANET_IDS[ 1 ] , row.getPlanetId( ) );
+		assertEquals( RESOURCE_NAMES[ 1 ] , row.getDelta( ).getResource( ) );
+		assertEquals( INCOME_VALUES[ 1 ] , row.getDelta( ).getIncome( ) , 0 );
+		assertEquals( UPKEEP_VALUES[ 1 ] , row.getDelta( ).getUpkeep( ) , 0 );
+		assertNotNull( row.getProvider( ) );
+		assertEquals( RP_CAPACITY , row.getProvider( ).getMaximalQuantity( ) , 0 );
+		assertEquals( RP_QUANTITY , row.getProvider( ).getCurrentQuantity( ) , 0 );
+		assertEquals( RP_DIFFICULTY , row.getProvider( ).getDifficulty( ) , 0 );
+		assertEquals( RP_RECOVERY , row.getProvider( ).getRecovery( ) , 0 );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestInvalidDumpContentsException.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestInvalidDumpContentsException.java
new file mode 100644
index 0000000..a88992d
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestInvalidDumpContentsException.java
@@ -0,0 +1,85 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.Serializable;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.beans.bt.es.data.InvalidDumpContentsException;
+
+
+
+/**
+ * Tests for the {@link InvalidDumpContentsException} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestInvalidDumpContentsException
+{
+
+	/**
+	 * A dummy class used to test the {@link InvalidDumpContentsException} class.
+	 * 
+	 * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+	 */
+	@SuppressWarnings( "serial" )
+	private static final class TestRecord
+			implements Serializable
+	{
+		// EMPTY
+	}
+
+	/** Test string used as the "field" parameter */
+	private static final String TEST_STRING = "Test";
+
+	/** Exception instance used to run tests */
+	private InvalidDumpContentsException exception;
+
+
+	/**
+	 * Set up the test by creating an exception instance using {@link TestRecord} as the target
+	 * class and {@link #TEST_STRING} as the field name.
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.exception = new InvalidDumpContentsException( TestRecord.class , TEST_STRING );
+	}
+
+
+	/**
+	 * The constructor must set the record's class and field name.
+	 */
+	@Test
+	public void testExceptionData( )
+	{
+		assertEquals( TestRecord.class , this.exception.getRecordType( ) );
+		assertEquals( TEST_STRING , this.exception.getField( ) );
+		assertNotNull( this.exception.getMessage( ) );
+	}
+
+
+	/**
+	 * The exception's message must end with the name of the class.
+	 */
+	@Test
+	public void testClassNameInMessage( )
+	{
+		assertTrue( this.exception.getMessage( ).endsWith( " " + TestRecord.class.getSimpleName( ) ) );
+	}
+
+
+	/**
+	 * The exception's message must contain the name of the field.
+	 */
+	@Test
+	public void testFieldNameInMessage( )
+	{
+		assertTrue( this.exception.getMessage( ).contains( " " + TEST_STRING + " " ) );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceDeltaInformation.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceDeltaInformation.java
new file mode 100644
index 0000000..2d899f6
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceDeltaInformation.java
@@ -0,0 +1,174 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.thoughtworks.xstream.XStream;
+
+
+
+/**
+ * Tests for the {@link ResourceDeltaInformation} XML dump storage class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestResourceDeltaInformation
+{
+
+	/** A string used in tests */
+	private static final String TEST_STRING = "This is a test";
+
+	/** A real number used in tests */
+	private static final Double TEST_DOUBLE = 4.2;
+
+	/** A record used in the tests */
+	private ResourceDeltaInformation rdi;
+
+
+	/**
+	 * Set up the test by creating a new resource delta information record.
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.rdi = new ResourceDeltaInformation( );
+	}
+
+
+	/**
+	 * All fields are set to <code>null</code> by default
+	 */
+	@Test
+	public void testDefaultValues( )
+	{
+		assertNull( this.rdi.getResource( ) );
+		assertNull( this.rdi.getIncome( ) );
+		assertNull( this.rdi.getUpkeep( ) );
+	}
+
+
+	/**
+	 * Setting and reading the resource's name
+	 */
+	@Test
+	public void testResourceName( )
+	{
+		this.rdi.setResource( TEST_STRING );
+		assertEquals( TEST_STRING , this.rdi.getResource( ) );
+	}
+
+
+	/**
+	 * Setting the resource name to <code>null</code> throws {@link InvalidDumpContentsException}
+	 */
+	@Test
+	public void testNullResourceName( )
+	{
+		try {
+			this.rdi.setResource( null );
+			fail( "No InvalidDumpContentsException thrown" );
+		} catch ( InvalidDumpContentsException exception ) {
+			assertEquals( ResourceDeltaInformation.class , exception.getRecordType( ) );
+			assertEquals( "resource" , exception.getField( ) );
+		}
+	}
+
+
+	/**
+	 * Setting and reading the income
+	 */
+	@Test
+	public void testIncome( )
+	{
+		this.rdi.setIncome( TEST_DOUBLE );
+		assertEquals( TEST_DOUBLE , this.rdi.getIncome( ) );
+	}
+
+
+	/**
+	 * Setting and reading the upkeep
+	 */
+	@Test
+	public void testUpkeep( )
+	{
+		this.rdi.setUpkeep( TEST_DOUBLE );
+		assertEquals( TEST_DOUBLE , this.rdi.getUpkeep( ) );
+	}
+
+
+	/**
+	 * Serialising the instance to XML
+	 */
+	@Test
+	public void testXMLSerialisation( )
+	{
+		this.rdi.setResource( TEST_STRING );
+		this.rdi.setIncome( 0.1 );
+		this.rdi.setUpkeep( 0.2 );
+
+		XStream xstream = this.createXStreamInstance( );
+		String serialised = xstream.toXML( this.rdi );
+		assertNotNull( serialised );
+		assertTrue( serialised.startsWith( "<resource-delta " ) );
+		assertTrue( serialised.endsWith( "/>" ) );
+		assertTrue( serialised.contains( " id=\"" + TEST_STRING + "\"" ) );
+		assertTrue( serialised.contains( " income=\"0.1\"" ) );
+		assertTrue( serialised.contains( " upkeep=\"0.2\"" ) );
+	}
+
+
+	/**
+	 * Deserialising an instance that contains data
+	 */
+	@Test
+	public void testXMLDeserialisation( )
+	{
+		String xml = "<resource-delta id=\"Test\" income=\"0.1\" upkeep=\"0.2\" />";
+		XStream xstream = this.createXStreamInstance( );
+		Object deserialised = xstream.fromXML( xml );
+
+		assertNotNull( deserialised );
+		assertEquals( ResourceDeltaInformation.class , deserialised.getClass( ) );
+		this.rdi = (ResourceDeltaInformation) deserialised;
+
+		assertEquals( "Test" , this.rdi.getResource( ) );
+		assertEquals( (Double) 0.1 , this.rdi.getIncome( ) );
+		assertEquals( (Double) 0.2 , this.rdi.getUpkeep( ) );
+	}
+
+
+	/**
+	 * Deserialising an instance that contains no data
+	 */
+	@Test
+	public void testXMLDeserialisationNoData( )
+	{
+		String xml = "<resource-delta />";
+		XStream xstream = this.createXStreamInstance( );
+		Object deserialised = xstream.fromXML( xml );
+
+		assertNotNull( deserialised );
+		assertEquals( ResourceDeltaInformation.class , deserialised.getClass( ) );
+		this.rdi = (ResourceDeltaInformation) deserialised;
+
+		assertNull( this.rdi.getResource( ) );
+		assertNull( this.rdi.getIncome( ) );
+		assertNull( this.rdi.getUpkeep( ) );
+	}
+
+
+	/**
+	 * Create and set up the {@link XStream} instance used in the serialisation tests
+	 * 
+	 * @return the {@link XStream} instance to use
+	 */
+	private XStream createXStreamInstance( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( ResourceDeltaInformation.class );
+		return xstream;
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceProviderInformation.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceProviderInformation.java
new file mode 100644
index 0000000..d545624
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceProviderInformation.java
@@ -0,0 +1,207 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.thoughtworks.xstream.XStream;
+
+
+
+/**
+ * Tests for the {@link ResourceProviderInformation} XML dump storage class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestResourceProviderInformation
+{
+	/** A string used in tests */
+	private static final String TEST_STRING = "This is a test";
+
+	/** A real number used in tests */
+	private static final Double TEST_DOUBLE = 4.2;
+
+	/** A resource provider information record */
+	private ResourceProviderInformation rpi;
+
+
+	@Before
+	public void setUp( )
+	{
+		this.rpi = new ResourceProviderInformation( );
+	}
+
+
+	/**
+	 * A new record's fields are all set to <code>null</code>.
+	 */
+	@Test
+	public void testDefaultValues( )
+	{
+		assertNull( this.rpi.getResource( ) );
+		assertNull( this.rpi.getCurrentQuantity( ) );
+		assertNull( this.rpi.getMaximalQuantity( ) );
+		assertNull( this.rpi.getDifficulty( ) );
+		assertNull( this.rpi.getRecovery( ) );
+	}
+
+
+	/**
+	 * Setting and reading the resource's name
+	 */
+	@Test
+	public void testResourceName( )
+	{
+		this.rpi.setResource( TEST_STRING );
+		assertEquals( TEST_STRING , this.rpi.getResource( ) );
+	}
+
+
+	/**
+	 * Setting the resource name to <code>null</code> throws {@link InvalidDumpContentsException}
+	 */
+	@Test
+	public void testNullResourceName( )
+	{
+		try {
+			this.rpi.setResource( null );
+			fail( "No InvalidDumpContentsException thrown" );
+		} catch ( InvalidDumpContentsException exception ) {
+			assertEquals( ResourceProviderInformation.class , exception.getRecordType( ) );
+			assertEquals( "resource" , exception.getField( ) );
+		}
+	}
+
+
+	/**
+	 * Setting and reading the current quantity
+	 */
+	@Test
+	public void testCurrentQuantity( )
+	{
+		this.rpi.setCurrentQuantity( TEST_DOUBLE );
+		assertEquals( TEST_DOUBLE , this.rpi.getCurrentQuantity( ) );
+	}
+
+
+	/**
+	 * Setting and reading the maximal quantity
+	 */
+	@Test
+	public void testMaximalQuantity( )
+	{
+		this.rpi.setMaximalQuantity( TEST_DOUBLE );
+		assertEquals( TEST_DOUBLE , this.rpi.getMaximalQuantity( ) );
+	}
+
+
+	/**
+	 * Setting and reading the extraction difficulty
+	 */
+	@Test
+	public void testDifficulty( )
+	{
+		this.rpi.setDifficulty( TEST_DOUBLE );
+		assertEquals( TEST_DOUBLE , this.rpi.getDifficulty( ) );
+	}
+
+
+	/**
+	 * Setting and reading the recovery rate
+	 */
+	@Test
+	public void testRecovery( )
+	{
+		this.rpi.setRecovery( TEST_DOUBLE );
+		assertEquals( TEST_DOUBLE , this.rpi.getRecovery( ) );
+	}
+
+
+	/**
+	 * Serialising the instance to XML
+	 */
+	@Test
+	public void testXMLSerialisation( )
+	{
+		this.rpi.setResource( TEST_STRING );
+		this.rpi.setCurrentQuantity( 0.1 );
+		this.rpi.setMaximalQuantity( 0.2 );
+		this.rpi.setDifficulty( 0.3 );
+		this.rpi.setRecovery( 0.4 );
+
+		XStream xstream = this.createXStreamInstance( );
+		String serialised = xstream.toXML( this.rpi );
+		assertNotNull( serialised );
+		assertTrue( serialised.startsWith( "<resource-provider " ) );
+		assertTrue( serialised.endsWith( "/>" ) );
+		assertTrue( serialised.contains( " resource=\"" + TEST_STRING + "\"" ) );
+		assertTrue( serialised.contains( " current=\"0.1\"" ) );
+		assertTrue( serialised.contains( " max=\"0.2\"" ) );
+		assertTrue( serialised.contains( " difficulty=\"0.3\"" ) );
+		assertTrue( serialised.contains( " recovery=\"0.4\"" ) );
+	}
+
+
+	/**
+	 * Deserialising an instance that contains data
+	 */
+	@Test
+	public void testXMLDeserialisation( )
+	{
+		String xml = "<resource-provider resource=\"Test\" current=\"0.1\" max=\"0.2\" difficulty=\"0.3\" recovery=\"0.4\" />";
+		XStream xstream = this.createXStreamInstance( );
+		Object deserialised = xstream.fromXML( xml );
+
+		assertNotNull( deserialised );
+		assertEquals( ResourceProviderInformation.class , deserialised.getClass( ) );
+		this.rpi = (ResourceProviderInformation) deserialised;
+
+		assertEquals( "Test" , this.rpi.getResource( ) );
+		assertEquals( (Double) 0.1 , this.rpi.getCurrentQuantity( ) );
+		assertEquals( (Double) 0.2 , this.rpi.getMaximalQuantity( ) );
+		assertEquals( (Double) 0.3 , this.rpi.getDifficulty( ) );
+		assertEquals( (Double) 0.4 , this.rpi.getRecovery( ) );
+	}
+
+
+	/**
+	 * Deserialising an instance that contains no data
+	 */
+	@Test
+	public void testXMLDeserialisationNoData( )
+	{
+		String xml = "<resource-provider />";
+		XStream xstream = this.createXStreamInstance( );
+		Object deserialised = xstream.fromXML( xml );
+
+		assertNotNull( deserialised );
+		assertEquals( ResourceProviderInformation.class , deserialised.getClass( ) );
+		this.rpi = (ResourceProviderInformation) deserialised;
+
+		assertNull( this.rpi.getResource( ) );
+		assertNull( this.rpi.getCurrentQuantity( ) );
+		assertNull( this.rpi.getMaximalQuantity( ) );
+		assertNull( this.rpi.getDifficulty( ) );
+		assertNull( this.rpi.getRecovery( ) );
+	}
+
+
+	/**
+	 * Create and set up the {@link XStream} instance used in the serialisation tests
+	 * 
+	 * @return the {@link XStream} instance to use
+	 */
+	private XStream createXStreamInstance( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( ResourceProviderInformation.class );
+		xstream.alias( "resource-provider" , ResourceProviderInformation.class );
+		return xstream;
+	}
+}

From 9b346a80c2b0608bcf826947be89cb0d3f8d2a09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 19 Jan 2012 09:35:12 +0100
Subject: [PATCH 29/94] Empire resources in XML dumps

* Added dump view for empire resources

* Added empire resource information storage class and associated row
mapper

* Integrated empire resource information into the summary generator
---
 .../es/EmpireResourceInformationMapper.java   |  46 ++++++
 .../lw/beans/bt/es/EmpireSummaryBean.java     |  31 ++++
 .../beans/bt/es/data/EmpireInformation.java   |  91 ++++++++++--
 .../bt/es/data/EmpireResourceInformation.java | 103 +++++++++++++
 .../parts/040-functions/200-bugs.sql          |  25 ++++
 .../200-bugs/005-dump-emp-resources-view.sql  |  36 +++++
 .../200-bugs/005-dump-emp-resources-view.sql  |  11 ++
 .../TestEmpireResourceInformationMapper.java  |  74 +++++++++
 .../data/TestEmpireResourceInformation.java   | 140 ++++++++++++++++++
 9 files changed, 542 insertions(+), 15 deletions(-)
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireResourceInformationMapper.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireResourceInformation.java
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/005-dump-emp-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/200-bugs/005-dump-emp-resources-view.sql
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestEmpireResourceInformationMapper.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestEmpireResourceInformation.java

diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireResourceInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireResourceInformationMapper.java
new file mode 100644
index 0000000..aefb01d
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireResourceInformationMapper.java
@@ -0,0 +1,46 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.beans.bt.es.data.EmpireResourceInformation;
+
+
+
+/**
+ * Empire resource row mapper
+ * 
+ * <p>
+ * This class is responsible for transforming rows from <code>bugs.dump_emp_resources_view</code>
+ * into {@link EmpireResourceInformation} instances.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+final class EmpireResourceInformationMapper
+		implements RowMapper< EmpireResourceInformation >
+{
+
+	/**
+	 * Map a row from <code>bugs.dump_emp_resources_view</code>
+	 * 
+	 * <p>
+	 * Create a new {@link EmpireResourceInformation} instance and set its fields using the row's
+	 * contents.
+	 */
+	@Override
+	public EmpireResourceInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		EmpireResourceInformation empRes = new EmpireResourceInformation( );
+
+		empRes.setResource( rs.getString( "resource_name" ) );
+		empRes.setOwed( rs.getDouble( "empres_owed" ) );
+		empRes.setPossessed( rs.getDouble( "empres_possessed" ) );
+
+		return empRes;
+	}
+
+}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
index bc2fa7d..a6ec5f8 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.beans.bt.es;
 
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.sql.DataSource;
@@ -14,6 +15,7 @@ import com.deepclone.lw.beans.bt.es.data.AllianceInformation;
 import com.deepclone.lw.beans.bt.es.data.BuildingsInformation;
 import com.deepclone.lw.beans.bt.es.data.DebugInformation;
 import com.deepclone.lw.beans.bt.es.data.EmpireInformation;
+import com.deepclone.lw.beans.bt.es.data.EmpireResourceInformation;
 import com.deepclone.lw.beans.bt.es.data.FleetInformation;
 import com.deepclone.lw.beans.bt.es.data.MovementInformation;
 import com.deepclone.lw.beans.bt.es.data.PlanetInformation;
@@ -51,6 +53,9 @@ public class EmpireSummaryBean
 	/** SQL query that accesses the main empire dump view */
 	private static final String Q_EMPIRE = SQL_START + "main" + SQL_END;
 
+	/** SQL query that accesses the resources dump view */
+	private static final String Q_RESOURCES = SQL_START + "emp_resources" + SQL_END;
+
 	/** SQL query that accesses the research dump view */
 	private static final String Q_RESEARCH = SQL_START + "research" + SQL_END;
 
@@ -81,6 +86,9 @@ public class EmpireSummaryBean
 	/** Top-level row mapper */
 	private final DebugInformationMapper mMainInfo;
 
+	/** Empire resources row mapper */
+	private final EmpireResourceInformationMapper mResources;
+
 	/** Empire research row mapper */
 	private final ResearchInformationMapper mResearch;
 
@@ -119,6 +127,7 @@ public class EmpireSummaryBean
 		} );
 
 		this.mMainInfo = new DebugInformationMapper( );
+		this.mResources = new EmpireResourceInformationMapper( );
 		this.mResearch = new ResearchInformationMapper( );
 		this.mPlanet = new PlanetInformationMapper( );
 		this.mPlanetResources = new ResourceRowMapper( );
@@ -154,6 +163,7 @@ public class EmpireSummaryBean
 	public String getSummary( int empireId )
 	{
 		DebugInformation empireDump = this.dTemplate.queryForObject( Q_EMPIRE , this.mMainInfo , empireId );
+		this.getResources( empireId , empireDump );
 		this.getResearch( empireId , empireDump );
 
 		this.getPlanets( empireId , empireDump );
@@ -163,6 +173,27 @@ public class EmpireSummaryBean
 	}
 
 
+	/**
+	 * Extract resources information
+	 * 
+	 * <p>
+	 * Read the list of empire resources from the appropriate view and add the extracted entries to
+	 * the empire's list of resources.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param empireDump
+	 *            the top-level instance
+	 */
+	private void getResources( int empireId , DebugInformation empireDump )
+	{
+		List< EmpireResourceInformation > resources = empireDump.getEmpire( ).getResources( );
+		for ( EmpireResourceInformation empRes : this.dTemplate.query( Q_RESOURCES , this.mResources , empireId ) ) {
+			resources.add( empRes );
+		}
+	}
+
+
 	/**
 	 * Get research information
 	 * 
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireInformation.java
index ff5f984..0bee8fb 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireInformation.java
@@ -2,79 +2,140 @@ package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
-import com.thoughtworks.xstream.annotations.XStreamAlias;
 import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
 
 
 
-@XStreamAlias( "empire" )
+/**
+ * Empire information record for XML dumps
+ * 
+ * <p>
+ * This class regroups all "main" empire information in XML dumps. This includes the empire's name
+ * and identifier, details about the alliance, and the list of resources.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
 public class EmpireInformation
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
-
+	/** The empire's numeric identifier */
 	@XStreamAsAttribute
-	@XStreamAlias( "id" )
-	private int id;
+	private Integer id;
 
+	/** The empire's name */
 	@XStreamAsAttribute
-	@XStreamAlias( "name" )
 	private String name;
 
+	/** The empire's cash */
 	@XStreamAsAttribute
-	@XStreamAlias( "cash" )
-	private double cash;
+	private Double cash;
 
+	/**
+	 * The alliance the empire belongs to or has requested membership of (or <code>null</code> if
+	 * the empire is neither in an alliance nor requesting to join one)
+	 */
 	private AllianceInformation alliance;
 
+	/** The empire's resources */
+	private final ArrayList< EmpireResourceInformation > resources = new ArrayList< EmpireResourceInformation >( );
 
-	public int getId( )
+
+	/** @return the empire's numeric identifier */
+	public Integer getId( )
 	{
-		return id;
+		return this.id;
 	}
 
 
+	/**
+	 * Set the empire's numeric identifier
+	 * 
+	 * @param id
+	 *            the empire's numeric identifier
+	 */
 	public void setId( int id )
 	{
 		this.id = id;
 	}
 
 
+	/** @return the empire's name */
 	public String getName( )
 	{
-		return name;
+		return this.name;
 	}
 
 
+	/**
+	 * Set the empire's name
+	 * 
+	 * @param name
+	 *            the empire's name
+	 */
 	public void setName( String name )
 	{
 		this.name = name;
 	}
 
 
-	public double getCash( )
+	/** @return the amount of cash possessed by the empire */
+	public Double getCash( )
 	{
-		return cash;
+		return this.cash;
 	}
 
 
+	/**
+	 * Set the amount of cash possessed by the empire
+	 * 
+	 * @param cash
+	 *            the amount of cash possessed by the empire
+	 */
 	public void setCash( double cash )
 	{
 		this.cash = cash;
 	}
 
 
+	/**
+	 * @return the alliance the empire belongs to or has requested membership of, or
+	 *         <code>null</code> if the empire is neither in an alliance nor requesting to join one
+	 */
 	public AllianceInformation getAlliance( )
 	{
-		return alliance;
+		return this.alliance;
 	}
 
 
+	/**
+	 * Set the information about the alliance
+	 * 
+	 * @param alliance
+	 *            the information about the alliance
+	 */
 	public void setAlliance( AllianceInformation alliance )
 	{
 		this.alliance = alliance;
 	}
 
+
+	/**
+	 * Access the list of resources
+	 * 
+	 * @return the list of resources, even if none was set in the file the instance was loaded from.
+	 */
+	public List< EmpireResourceInformation > getResources( )
+	{
+		if ( this.resources == null ) {
+			return Collections.emptyList( );
+		}
+		return this.resources;
+	}
+
 }
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireResourceInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireResourceInformation.java
new file mode 100644
index 0000000..d6b06f0
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireResourceInformation.java
@@ -0,0 +1,103 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * Empire resources XML dump record
+ * 
+ * <p>
+ * This class is used to store information about an empire's resources in debugging XML dumps. Each
+ * instance indicates the amount possessed or owed for a given type of resources.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@XStreamAlias( "resource" )
+@SuppressWarnings( "serial" )
+public class EmpireResourceInformation
+		implements Serializable
+{
+
+	/** The type of resources */
+	@XStreamAlias( "id" )
+	@XStreamAsAttribute
+	private String resource;
+
+	/** The amount of resources possessed */
+	@XStreamAsAttribute
+	private Double possessed;
+
+	/** The amount of resources owed */
+	@XStreamAsAttribute
+	private Double owed;
+
+
+	/** @return the type of resources */
+	public String getResource( )
+	{
+		return this.resource;
+	}
+
+
+	/**
+	 * Set the type of resources
+	 * 
+	 * @param resource
+	 *            the type of resources
+	 * 
+	 * @throws InvalidDumpContentsException
+	 *             if the specified resource type is <code>null</code>
+	 */
+	public void setResource( String resource )
+			throws InvalidDumpContentsException
+	{
+		if ( resource == null ) {
+			throw new InvalidDumpContentsException( this.getClass( ) , "resource" );
+		}
+		this.resource = resource;
+	}
+
+
+	/** @return the amount of resources possessed */
+	public Double getPossessed( )
+	{
+		return this.possessed;
+	}
+
+
+	/**
+	 * Set the amount of resources possessed
+	 * 
+	 * @param possessed
+	 *            the amount of resources possessed
+	 */
+	public void setPossessed( Double possessed )
+	{
+		this.possessed = possessed;
+	}
+
+
+	/** @return the amount of resources owed */
+	public Double getOwed( )
+	{
+		return this.owed;
+	}
+
+
+	/**
+	 * Set the amount of resources owed
+	 * 
+	 * @param owed
+	 *            the amount of resources owed
+	 */
+	public void setOwed( Double owed )
+	{
+		this.owed = owed;
+	}
+
+}
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
index 1d9a23d..dfe0211 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
@@ -1205,6 +1205,30 @@ CREATE VIEW bugs.dump_research_view
 GRANT SELECT ON bugs.dump_research_view TO :dbuser;
 
 
+/*
+ * Empire resources view
+ * ----------------------
+ * 
+ * This view contains the details about empires' resources as they are needed
+ * by the XML dump generator.
+ * 
+ * Columns:
+ *		empire_id			Identifier of the empire
+ *		resource_name		Text-based identifier of the resource type
+ *		empres_possessed	Amount of resources possessed by the empire
+ *		empres_owed			Amount of resources owed by the empire
+ */
+DROP VIEW IF EXISTS bugs.dump_emp_resources_view CASCADE;
+CREATE VIEW bugs.dump_emp_resources_view
+	AS SELECT empire_id , name AS resource_name , empres_possessed , empres_owed
+		FROM emp.resources
+			INNER JOIN defs.strings ON id = resource_name_id;
+
+GRANT SELECT
+	ON bugs.dump_emp_resources_view
+	TO :dbuser;
+
+
 CREATE VIEW bugs.dump_planets_view
 	AS SELECT ep.empire_id , ep.planet_id , p.population ,
 				( ph.current / p.population )::REAL AS current_happiness , ph.target AS target_happiness ,
@@ -1248,6 +1272,7 @@ GRANT SELECT ON bugs.dump_planets_view TO :dbuser;
  *									if there is no resource provider of that
  *									 type on the planet
  */
+DROP VIEW IF EXISTS bugs.dump_planet_resources_view CASCADE;
 CREATE VIEW bugs.dump_planet_resources_view
 	AS SELECT empire_id , planet_id ,
 				name AS resource_name ,
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/005-dump-emp-resources-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/005-dump-emp-resources-view.sql
new file mode 100644
index 0000000..104366e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/005-dump-emp-resources-view.sql
@@ -0,0 +1,36 @@
+/*
+ * Tests for bugs.dump_emp_resources_view
+ */
+BEGIN;
+	/*
+	 * We need a resource type, an empire and the associated resource record.
+	 */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_resources( 1 , 'resource' );
+	SELECT _create_emp_names( 1 , 'empire' );
+	INSERT INTO emp.empires( name_id , cash )
+		VALUES ( _get_emp_name( 'empire1' ) , 0 );
+	INSERT INTO emp.resources(
+			empire_id , resource_name_id , empres_possessed , empres_owed
+		) VALUES (
+			_get_emp_name( 'empire1' ) , _get_string( 'resource1' ) , 1 , 2
+		);
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'bugs.dump_emp_resources_view - Contents' );
+	SELECT set_eq( $$
+		SELECT empire_id , resource_name , empres_possessed , empres_owed
+			FROM bugs.dump_emp_resources_view
+	$$ , $$ VALUES (
+		_get_emp_name( 'empire1' ) , 'resource1' , 1 , 2
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/200-bugs/005-dump-emp-resources-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/200-bugs/005-dump-emp-resources-view.sql
new file mode 100644
index 0000000..a1eb0f8
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/200-bugs/005-dump-emp-resources-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on bugs.dump_emp_resources_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'bugs.dump_emp_resources_view - Privileges' );
+	SELECT lives_ok( 'SELECT * FROM bugs.dump_emp_resources_view' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestEmpireResourceInformationMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestEmpireResourceInformationMapper.java
new file mode 100644
index 0000000..9f8f96d
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestEmpireResourceInformationMapper.java
@@ -0,0 +1,74 @@
+package com.deepclone.lw.beans.bt.es;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.beans.bt.es.data.EmpireResourceInformation;
+import com.deepclone.lw.testing.MockResultSet;
+
+
+
+/**
+ * Tests for the {@link EmpireResourceInformationMapper} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestEmpireResourceInformationMapper
+{
+
+	/** Resource identifier used in tests */
+	private static final String TEST_ID = "Test";
+
+	/** "Owed" value used in tests */
+	private static final Double TEST_OWED = 1.0;
+
+	/** "Possessed" value used in tests */
+	private static final Double TEST_POSSESSED = 2.0;
+
+	/** The fake result set used in the tests */
+	private ResultSet resultSet;
+
+	/** The mapper used in the tests */
+	private EmpireResourceInformationMapper mapper;
+
+
+	/** Create the mapper and the contents of the fake result set */
+	@Before
+	@SuppressWarnings( "unchecked" )
+	public void setUp( )
+	{
+		this.mapper = new EmpireResourceInformationMapper( );
+
+		HashMap< String , Object > row = new HashMap< String , Object >( );
+		row.put( "resource_name" , TEST_ID );
+		row.put( "empres_possessed" , TEST_POSSESSED );
+		row.put( "empres_owed" , TEST_OWED );
+
+		this.resultSet = MockResultSet.create( new HashMap[] {
+			row
+		} );
+	}
+
+
+	/** Mapping a row */
+	@Test
+	public void testMapRow( )
+			throws SQLException
+	{
+		EmpireResourceInformation empRes;
+
+		this.resultSet.absolute( 1 );
+		empRes = this.mapper.mapRow( this.resultSet , 1 );
+
+		assertEquals( TEST_ID , empRes.getResource( ) );
+		assertEquals( TEST_POSSESSED , empRes.getPossessed( ) );
+		assertEquals( TEST_OWED , empRes.getOwed( ) );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestEmpireResourceInformation.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestEmpireResourceInformation.java
new file mode 100644
index 0000000..2f03667
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestEmpireResourceInformation.java
@@ -0,0 +1,140 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.thoughtworks.xstream.XStream;
+
+
+
+/**
+ * Tests for the {@link EmpireResourceInformation} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestEmpireResourceInformation
+{
+
+	/** Resource identifier used in tests */
+	private static final String TEST_ID = "Test";
+
+	/** "Owed" value used in tests */
+	private static final Double TEST_OWED = 1.0;
+
+	/** "Possessed" value used in tests */
+	private static final Double TEST_POSSESSED = 2.0;
+
+	/** Empire resource information instance used in tests */
+	private EmpireResourceInformation empRes;
+
+
+	/** Create the instance to be used in actual tests */
+	@Before
+	public void setUp( )
+	{
+		this.empRes = new EmpireResourceInformation( );
+	}
+
+
+	/** Default values are <code>null</code> */
+	@Test
+	public void testDefaultValues( )
+	{
+		assertNull( this.empRes.getResource( ) );
+		assertNull( this.empRes.getOwed( ) );
+		assertNull( this.empRes.getPossessed( ) );
+	}
+
+
+	/** Setting and reading the resource type */
+	@Test
+	public void testResource( )
+	{
+		this.empRes.setResource( TEST_ID );
+		assertEquals( TEST_ID , this.empRes.getResource( ) );
+	}
+
+
+	/** Setting the resource type to <code>null</code> */
+	@Test
+	public void testNullResource( )
+	{
+		try {
+			this.empRes.setResource( null );
+			fail( "InvalidDumpContentsException expected" );
+		} catch ( InvalidDumpContentsException exception ) {
+			assertEquals( EmpireResourceInformation.class , exception.getRecordType( ) );
+			assertEquals( "resource" , exception.getField( ) );
+		}
+	}
+
+
+	/** Setting and reading the amount of resources possessed */
+	@Test
+	public void testPossessed( )
+	{
+		this.empRes.setPossessed( TEST_POSSESSED );
+		assertEquals( TEST_POSSESSED , this.empRes.getPossessed( ) );
+	}
+
+
+	/** Setting and reading the amount of resources owed */
+	@Test
+	public void testOwed( )
+	{
+		this.empRes.setOwed( TEST_OWED );
+		assertEquals( TEST_OWED , this.empRes.getOwed( ) );
+	}
+
+
+	/** Serialising the instance to XML */
+	@Test
+	public void testXMLSerialisation( )
+	{
+		this.empRes.setResource( TEST_ID );
+		this.empRes.setOwed( TEST_OWED );
+		this.empRes.setPossessed( TEST_POSSESSED );
+
+		String serialised = this.createXStreamInstance( ).toXML( this.empRes );
+		assertNotNull( serialised );
+		assertTrue( serialised.startsWith( "<resource " ) );
+		assertTrue( serialised.endsWith( "/>" ) );
+		assertTrue( serialised.contains( " id=\"" + TEST_ID + "\"" ) );
+		assertTrue( serialised.contains( " owed=\"" + TEST_OWED + "\"" ) );
+		assertTrue( serialised.contains( " possessed=\"" + TEST_POSSESSED + "\"" ) );
+	}
+
+
+	/** Deserialising an instance from XML */
+	@Test
+	public void testXMLDeserialisation( )
+	{
+		String xml = "<resource id=\"" + TEST_ID + "\" owed=\"" + TEST_OWED.toString( ) + "\" possessed=\""
+				+ TEST_POSSESSED.toString( ) + "\" />";
+		Object deserialised = this.createXStreamInstance( ).fromXML( xml );
+
+		assertNotNull( deserialised );
+		assertEquals( EmpireResourceInformation.class , deserialised.getClass( ) );
+		this.empRes = (EmpireResourceInformation) deserialised;
+
+		assertEquals( TEST_ID , this.empRes.getResource( ) );
+		assertEquals( TEST_POSSESSED , this.empRes.getPossessed( ) );
+		assertEquals( TEST_OWED , this.empRes.getOwed( ) );
+	}
+
+
+	/**
+	 * Create and set up the {@link XStream} instance used in the serialisation tests
+	 * 
+	 * @return the {@link XStream} instance to use
+	 */
+	private XStream createXStreamInstance( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( EmpireResourceInformation.class );
+		return xstream;
+	}
+}

From 3637b6e1d13541a208455653354dc7d22295a145 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 19 Jan 2012 10:28:12 +0100
Subject: [PATCH 30/94] Mining settings in XML dumps

* Empire mining settings have been included in the empire's resource
information records

* Planet-specific mining settings have been included in the resource
provider information records
---
 .../es/EmpireResourceInformationMapper.java   |  8 +-
 .../lw/beans/bt/es/ResourceRowMapper.java     |  9 +-
 .../bt/es/data/EmpireResourceInformation.java | 33 ++++++-
 .../es/data/ResourceProviderInformation.java  | 27 ++++++
 .../parts/040-functions/200-bugs.sql          | 20 +++-
 .../200-bugs/005-dump-emp-resources-view.sql  | 26 +++++-
 .../010-dump-planet-resources-view.sql        | 47 ++++++++--
 .../TestEmpireResourceInformationMapper.java  | 60 +++++++++---
 .../lw/beans/bt/es/TestResourceRowMapper.java | 93 +++++++++++++------
 .../data/TestEmpireResourceInformation.java   | 58 +++++++++++-
 .../data/TestResourceProviderInformation.java | 69 ++++++++++++--
 11 files changed, 380 insertions(+), 70 deletions(-)

diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireResourceInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireResourceInformationMapper.java
index aefb01d..3140781 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireResourceInformationMapper.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireResourceInformationMapper.java
@@ -28,7 +28,8 @@ final class EmpireResourceInformationMapper
 	 * 
 	 * <p>
 	 * Create a new {@link EmpireResourceInformation} instance and set its fields using the row's
-	 * contents.
+	 * contents. If a mining priority is present, set it, otherwise leave the field to its default
+	 * <code>null</code> value.
 	 */
 	@Override
 	public EmpireResourceInformation mapRow( ResultSet rs , int rowNum )
@@ -40,6 +41,11 @@ final class EmpireResourceInformationMapper
 		empRes.setOwed( rs.getDouble( "empres_owed" ) );
 		empRes.setPossessed( rs.getDouble( "empres_possessed" ) );
 
+		int priority = rs.getInt( "mining_priority" );
+		if ( !rs.wasNull( ) ) {
+			empRes.setMiningPriority( priority );
+		}
+
 		return empRes;
 	}
 
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResourceRowMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResourceRowMapper.java
index 5925cfd..8f2ceed 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResourceRowMapper.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResourceRowMapper.java
@@ -30,7 +30,8 @@ class ResourceRowMapper
 	 * <p>
 	 * Generate the {@link PlanetResourceRow} instance with the correct planet identifier, resource
 	 * name, income and upkeep. If there is also a resource provider, attach a
-	 * {@link ResourceProviderInformation} instance to the result.
+	 * {@link ResourceProviderInformation} instance to the result, and set its mining priority if
+	 * one exists.
 	 */
 	@Override
 	public PlanetResourceRow mapRow( ResultSet rs , int rowNum )
@@ -48,6 +49,12 @@ class ResourceRowMapper
 			rpi.setCurrentQuantity( rs.getDouble( "resprov_quantity" ) );
 			rpi.setDifficulty( rs.getDouble( "resprov_difficulty" ) );
 			rpi.setRecovery( rs.getDouble( "resprov_recovery" ) );
+
+			int miningPriority = rs.getInt( "mining_priority" );
+			if ( !rs.wasNull( ) ) {
+				rpi.setMiningPriority( miningPriority );
+			}
+
 			row.setProvider( rpi );
 		}
 
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireResourceInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireResourceInformation.java
index d6b06f0..52e74ad 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireResourceInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/EmpireResourceInformation.java
@@ -13,7 +13,8 @@ import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
  * 
  * <p>
  * This class is used to store information about an empire's resources in debugging XML dumps. Each
- * instance indicates the amount possessed or owed for a given type of resources.
+ * instance indicates the amount possessed or owed for a given type of resources, as well as the
+ * mining priority if it is a natural resource.
  * 
  * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
  */
@@ -36,6 +37,14 @@ public class EmpireResourceInformation
 	@XStreamAsAttribute
 	private Double owed;
 
+	/**
+	 * The mining priority for this type of resource, or <code>null</code> if it is not a natural
+	 * resource
+	 */
+	@XStreamAlias( "mining-priority" )
+	@XStreamAsAttribute
+	private Integer miningPriority;
+
 
 	/** @return the type of resources */
 	public String getResource( )
@@ -100,4 +109,26 @@ public class EmpireResourceInformation
 		this.owed = owed;
 	}
 
+
+	/**
+	 * @return the mining priority for this type of resource, or <code>null</code> if it is not a
+	 *         natural resource
+	 */
+	public Integer getMiningPriority( )
+	{
+		return this.miningPriority;
+	}
+
+
+	/**
+	 * Set the mining priority for this type of resource
+	 * 
+	 * @param miningPriority
+	 *            the mining priority for this type of resource
+	 */
+	public void setMiningPriority( int miningPriority )
+	{
+		this.miningPriority = miningPriority;
+	}
+
 }
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceProviderInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceProviderInformation.java
index c29054c..29a9968 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceProviderInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResourceProviderInformation.java
@@ -47,6 +47,11 @@ public class ResourceProviderInformation
 	@XStreamAsAttribute
 	private Double recovery;
 
+	/** Planet-specific mining priority */
+	@XStreamAlias( "mining-priority" )
+	@XStreamAsAttribute
+	private Integer miningPriority;
+
 
 	/** @return the resource's identifier */
 	public String getResource( )
@@ -149,4 +154,26 @@ public class ResourceProviderInformation
 		this.recovery = recovery;
 	}
 
+
+	/**
+	 * @return the planet-specific mining priority, or <code>null</code> if the empire's global
+	 *         settings are in used
+	 */
+	public Integer getMiningPriority( )
+	{
+		return this.miningPriority;
+	}
+
+
+	/**
+	 * Set the planet-specific mining priority
+	 * 
+	 * @param miningPriority
+	 *            the planet-specific mining priority
+	 */
+	public void setMiningPriority( int miningPriority )
+	{
+		this.miningPriority = miningPriority;
+	}
+
 }
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
index dfe0211..773e718 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
@@ -1217,12 +1217,19 @@ GRANT SELECT ON bugs.dump_research_view TO :dbuser;
  *		resource_name		Text-based identifier of the resource type
  *		empres_possessed	Amount of resources possessed by the empire
  *		empres_owed			Amount of resources owed by the empire
+ *		mining_priority		Mining priority for this resource, or NULL if it
+ *								is not a natural resource
  */
 DROP VIEW IF EXISTS bugs.dump_emp_resources_view CASCADE;
 CREATE VIEW bugs.dump_emp_resources_view
-	AS SELECT empire_id , name AS resource_name , empres_possessed , empres_owed
+	AS SELECT empire_id , name AS resource_name ,
+			empres_possessed , empres_owed ,
+			empmset_weight AS mining_priority
 		FROM emp.resources
-			INNER JOIN defs.strings ON id = resource_name_id;
+			INNER JOIN defs.strings
+				ON id = resource_name_id
+			LEFT OUTER JOIN emp.mining_settings
+				USING ( empire_id , resource_name_id );
 
 GRANT SELECT
 	ON bugs.dump_emp_resources_view
@@ -1271,6 +1278,10 @@ GRANT SELECT ON bugs.dump_planets_view TO :dbuser;
  *		resprov_recovery		The resource provider's recovery rate, or NULL
  *									if there is no resource provider of that
  *									 type on the planet
+ *		mining_priority			The planet-specific mining priority for the
+ *									current resource type, or NULL if there
+ *									are no planet-specific settings or no
+ *									provider of this type.
  */
 DROP VIEW IF EXISTS bugs.dump_planet_resources_view CASCADE;
 CREATE VIEW bugs.dump_planet_resources_view
@@ -1278,7 +1289,8 @@ CREATE VIEW bugs.dump_planet_resources_view
 				name AS resource_name ,
 				pres_income , pres_upkeep ,
 				resprov_quantity_max , resprov_quantity ,
-				resprov_difficulty , resprov_recovery
+				resprov_difficulty , resprov_recovery ,
+				emppmset_weight AS mining_priority
 			FROM emp.planets
 				INNER JOIN verse.planet_resources
 					USING ( planet_id )
@@ -1286,6 +1298,8 @@ CREATE VIEW bugs.dump_planet_resources_view
 					ON resource_name_id = id
 				LEFT OUTER JOIN verse.resource_providers
 					USING ( planet_id , resource_name_id )
+				LEFT OUTER JOIN emp.planet_mining_settings
+					USING ( empire_id , planet_id , resource_name_id )
 			ORDER BY name;
 
 GRANT SELECT
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/005-dump-emp-resources-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/005-dump-emp-resources-view.sql
index 104366e..cc2bd6d 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/005-dump-emp-resources-view.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/005-dump-emp-resources-view.sql
@@ -3,7 +3,8 @@
  */
 BEGIN;
 	/*
-	 * We need a resource type, an empire and the associated resource record.
+	 * We need a basic resource type, a natural resource type, an empire and
+	 * the associated resources and mining settings records.
 	 */
 	\i utils/strings.sql
 	\i utils/resources.sql
@@ -11,6 +12,7 @@ BEGIN;
 	\i utils/naming.sql
 	\i utils/universe.sql
 	SELECT _create_resources( 1 , 'resource' );
+	SELECT _create_natural_resources( 1 , 'natRes' );
 	SELECT _create_emp_names( 1 , 'empire' );
 	INSERT INTO emp.empires( name_id , cash )
 		VALUES ( _get_emp_name( 'empire1' ) , 0 );
@@ -18,19 +20,37 @@ BEGIN;
 			empire_id , resource_name_id , empres_possessed , empres_owed
 		) VALUES (
 			_get_emp_name( 'empire1' ) , _get_string( 'resource1' ) , 1 , 2
+		) , (
+			_get_emp_name( 'empire1' ) , _get_string( 'natRes1' ) , 3 , 4
+		);
+	INSERT INTO emp.mining_settings (
+			empire_id , resource_name_id , empmset_weight
+		) VALUES (
+			_get_emp_name( 'empire1' ) , _get_string( 'natRes1' ) , 0
 		);
 
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 1 );
+	SELECT plan( 2 );
 
-	SELECT diag_test_name( 'bugs.dump_emp_resources_view - Contents' );
+	SELECT diag_test_name( 'bugs.dump_emp_resources_view - Basic resources' );
 	SELECT set_eq( $$
 		SELECT empire_id , resource_name , empres_possessed , empres_owed
 			FROM bugs.dump_emp_resources_view
+			WHERE mining_priority IS NULL
 	$$ , $$ VALUES (
 		_get_emp_name( 'empire1' ) , 'resource1' , 1 , 2
 	) $$ );
 
+	SELECT diag_test_name( 'bugs.dump_emp_resources_view - Natural resources' );
+	SELECT set_eq( $$
+		SELECT empire_id , resource_name , empres_possessed , empres_owed ,
+				mining_priority
+			FROM bugs.dump_emp_resources_view
+			WHERE mining_priority IS NOT NULL
+	$$ , $$ VALUES (
+		_get_emp_name( 'empire1' ) , 'natRes1' , 3 , 4 , 0
+	) $$ );
+
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/010-dump-planet-resources-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/010-dump-planet-resources-view.sql
index 23c8aa5..011de9f 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/010-dump-planet-resources-view.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/200-bugs/010-dump-planet-resources-view.sql
@@ -3,10 +3,12 @@
  */
 BEGIN;
 	/*
-	 * We need a couple of resources (one natural, one basic), three planets
-	 * with valid planet resource records (two of the planets will have a
-	 * resource provider), two empires (owning a planet with and without
-	 * resource providers, respectively). 
+	 * We need a couple of resources (one natural, one basic), four planets
+	 * with valid planet resource records (three of the planets will have a
+	 * resource provider and one of them will have planet-specific mining
+	 * settings), three empires (owning a planet without resource provider,
+	 * with resource provider and with resource provider and mining settings,
+	 * respectively). 
 	 */
 	\i utils/strings.sql
 	\i utils/resources.sql
@@ -15,7 +17,7 @@ BEGIN;
 	\i utils/universe.sql
 	SELECT _create_natural_resources( 1 , 'natRes' );
 	SELECT _create_resources( 1 , 'basicRes' );
-	SELECT _create_raw_planets( 3 , 'planet' );
+	SELECT _create_raw_planets( 4 , 'planet' );
 	INSERT INTO verse.planet_resources(
 			planet_id , resource_name_id , pres_income , pres_upkeep
 		) VALUES (
@@ -30,22 +32,37 @@ BEGIN;
 			_get_map_name( 'planet3' ) , _get_string( 'basicRes1' ) , 9 , 10
 		) , (
 			_get_map_name( 'planet3' ) , _get_string( 'natRes1' ) , 11 , 12
+		) , (
+			_get_map_name( 'planet4' ) , _get_string( 'basicRes1' ) , 13 , 14
+		) , (
+			_get_map_name( 'planet4' ) , _get_string( 'natRes1' ) , 15 , 16
 		);
 	SELECT _create_resource_provider( 'planet1' , 'natRes1' );
 	SELECT _create_resource_provider( 'planet3' , 'natRes1' );
+	SELECT _create_resource_provider( 'planet4' , 'natRes1' );
 
-	SELECT _create_emp_names( 2 , 'empire' );
+	SELECT _create_emp_names( 3 , 'empire' );
 	SELECT emp.create_empire( _get_emp_name( 'empire1' ) ,
 				_get_map_name( 'planet1' ) ,
 				200.0 );
 	SELECT emp.create_empire( _get_emp_name( 'empire2' ) ,
 				_get_map_name( 'planet2' ) ,
 				200.0 );
+	SELECT emp.create_empire( _get_emp_name( 'empire3' ) ,
+				_get_map_name( 'planet4' ) ,
+				200.0 );
+				
+	INSERT INTO emp.planet_mining_settings(
+			empire_id , planet_id , resource_name_id , emppmset_weight
+		) VALUES (
+			_get_emp_name( 'empire3' ) , _get_map_name( 'planet4' ) ,
+			_get_string( 'natRes1' ) , 2
+		);
 
 
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 2 );
+	SELECT plan( 3 );
 
 	SELECT diag_test_name( 'bugs.dump_planet_resources_view - Records without resource providers' );
 	SELECT set_eq( $$
@@ -58,16 +75,30 @@ BEGIN;
 		_get_emp_name( 'empire2' ) , _get_map_name( 'planet2' ) , 'basicRes1' , 5 , 6
 	) , (
 		_get_emp_name( 'empire2' ) , _get_map_name( 'planet2' ) , 'natRes1' , 7 , 8
+	) , (
+		_get_emp_name( 'empire3' ) , _get_map_name( 'planet4' ) , 'basicRes1' , 13 , 14
 	) $$ );
 
-	SELECT diag_test_name( 'bugs.dump_planet_resources_view - Records with resource providers' );
+	SELECT diag_test_name( 'bugs.dump_planet_resources_view - Records with resource providers, no settings' );
 	SELECT set_eq( $$
 		SELECT empire_id , planet_id , resource_name , pres_income , pres_upkeep
 			FROM bugs.dump_planet_resources_view
 			WHERE resprov_quantity IS NOT NULL
+				AND mining_priority IS NULL
 	$$ , $$ VALUES (
 		_get_emp_name( 'empire1' ) , _get_map_name( 'planet1' ) , 'natRes1' , 3 , 4
 	) $$ );
 
+	SELECT diag_test_name( 'bugs.dump_planet_resources_view - Records with resource providers and settings' );
+	SELECT set_eq( $$
+		SELECT empire_id , planet_id , resource_name , pres_income ,
+				pres_upkeep , mining_priority
+			FROM bugs.dump_planet_resources_view
+			WHERE resprov_quantity IS NOT NULL
+				AND mining_priority IS NOT NULL
+	$$ , $$ VALUES (
+		_get_emp_name( 'empire3' ) , _get_map_name( 'planet4' ) , 'natRes1' , 15 , 16 , 2
+	) $$ );
+
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestEmpireResourceInformationMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestEmpireResourceInformationMapper.java
index 9f8f96d..b8bb7fc 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestEmpireResourceInformationMapper.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestEmpireResourceInformationMapper.java
@@ -24,13 +24,22 @@ public class TestEmpireResourceInformationMapper
 {
 
 	/** Resource identifier used in tests */
-	private static final String TEST_ID = "Test";
+	private static final String[] TEST_ID = {
+			"Test1" , "Test2"
+	};
 
 	/** "Owed" value used in tests */
-	private static final Double TEST_OWED = 1.0;
+	private static final Double[] TEST_OWED = {
+			1.0 , 2.0
+	};
 
 	/** "Possessed" value used in tests */
-	private static final Double TEST_POSSESSED = 2.0;
+	private static final Double[] TEST_POSSESSED = {
+			3.0 , 4.0
+	};
+
+	/** Mining priority used in tests */
+	private static final Integer TEST_PRIORITY = 5;
 
 	/** The fake result set used in the tests */
 	private ResultSet resultSet;
@@ -46,18 +55,23 @@ public class TestEmpireResourceInformationMapper
 	{
 		this.mapper = new EmpireResourceInformationMapper( );
 
-		HashMap< String , Object > row = new HashMap< String , Object >( );
-		row.put( "resource_name" , TEST_ID );
-		row.put( "empres_possessed" , TEST_POSSESSED );
-		row.put( "empres_owed" , TEST_OWED );
+		HashMap< String , Object > rows[] = new HashMap[ 2 ];
+		for ( int i = 0 ; i < 2 ; i++ ) {
+			HashMap< String , Object > row = new HashMap< String , Object >( );
+			row.put( "resource_name" , TEST_ID[ i ] );
+			row.put( "empres_possessed" , TEST_POSSESSED[ i ] );
+			row.put( "empres_owed" , TEST_OWED[ i ] );
+			if ( i == 1 ) {
+				row.put( "mining_priority" , TEST_PRIORITY );
+			}
+			rows[ i ] = row;
+		}
 
-		this.resultSet = MockResultSet.create( new HashMap[] {
-			row
-		} );
+		this.resultSet = MockResultSet.create( rows );
 	}
 
 
-	/** Mapping a row */
+	/** Mapping a row without mining priority */
 	@Test
 	public void testMapRow( )
 			throws SQLException
@@ -67,8 +81,26 @@ public class TestEmpireResourceInformationMapper
 		this.resultSet.absolute( 1 );
 		empRes = this.mapper.mapRow( this.resultSet , 1 );
 
-		assertEquals( TEST_ID , empRes.getResource( ) );
-		assertEquals( TEST_POSSESSED , empRes.getPossessed( ) );
-		assertEquals( TEST_OWED , empRes.getOwed( ) );
+		assertEquals( TEST_ID[ 0 ] , empRes.getResource( ) );
+		assertEquals( TEST_POSSESSED[ 0 ] , empRes.getPossessed( ) );
+		assertEquals( TEST_OWED[ 0 ] , empRes.getOwed( ) );
+		assertNull( empRes.getMiningPriority( ) );
+	}
+
+
+	/** Mapping a row with mining priority */
+	@Test
+	public void testMapRowWithPriority( )
+			throws SQLException
+	{
+		EmpireResourceInformation empRes;
+
+		this.resultSet.absolute( 2 );
+		empRes = this.mapper.mapRow( this.resultSet , 2 );
+
+		assertEquals( TEST_ID[ 1 ] , empRes.getResource( ) );
+		assertEquals( TEST_POSSESSED[ 1 ] , empRes.getPossessed( ) );
+		assertEquals( TEST_OWED[ 1 ] , empRes.getOwed( ) );
+		assertEquals( TEST_PRIORITY , empRes.getMiningPriority( ) );
 	}
 }
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestResourceRowMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestResourceRowMapper.java
index 2235fbd..142e2b4 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestResourceRowMapper.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/TestResourceRowMapper.java
@@ -22,35 +22,46 @@ public class TestResourceRowMapper
 {
 	/** Planet identifiers found in the "results" */
 	private static final int[] PLANET_IDS = new int[] {
-			1 , 2
+			1 , 2 , 3
 	};
 
-	/** Resource names found in the two "results", respectively */
+	/** Resource names found in the three "results", respectively */
 	private static final String[] RESOURCE_NAMES = new String[] {
-			"Test1" , "Test2"
+			"Test1" , "Test2" , "Test3"
 	};
 
-	/** Income values found in the two results */
+	/** Income values found in the three results */
 	private static final double[] INCOME_VALUES = new double[] {
-			3.0 , 4.0
+			4.0 , 5.0 , 6.0
 	};
 
-	/** Upkeep values found in the two results */
+	/** Upkeep values found in the three results */
 	private static final double[] UPKEEP_VALUES = new double[] {
-			5.0 , 6.0
+			7.0 , 8.0 , 9.0
 	};
 
-	/** Resource provider quantity for the second row */
-	private static final double RP_QUANTITY = 7.0;
+	/** Resource provider quantity for the second and third rows */
+	private static final double[] RP_QUANTITY = {
+			10.0 , 11.0
+	};
 
-	/** Resource provider capacity for the second row */
-	private static final double RP_CAPACITY = 8.0;
+	/** Resource provider capacity for the second and third rows */
+	private static final double[] RP_CAPACITY = {
+			12.0 , 13.0
+	};
 
-	/** Resource provider extraction difficulty for the second row */
-	private static final double RP_DIFFICULTY = 9.0;
+	/** Resource provider extraction difficulty for the second and third rows */
+	private static final double[] RP_DIFFICULTY = {
+			14.0 , 15.0
+	};
 
-	/** Resource provider recovery rate for the second row */
-	private static final double RP_RECOVERY = 10.0;
+	/** Resource provider recovery rate for the second and third rows */
+	private static final double[] RP_RECOVERY = {
+			16.0 , 17.0
+	};
+
+	/** Mining priority value used for the third row */
+	private static final Integer MINING_PRIORITY = 18;
 
 	/** The fake result set used in the tests */
 	private ResultSet resultSet;
@@ -66,18 +77,21 @@ public class TestResourceRowMapper
 		this.mapper = new ResourceRowMapper( );
 
 		@SuppressWarnings( "unchecked" )
-		HashMap< String , Object > rows[] = new HashMap[ 2 ];
-		for ( int i = 0 ; i < 2 ; i++ ) {
+		HashMap< String , Object > rows[] = new HashMap[ 3 ];
+		for ( int i = 0 ; i < 3 ; i++ ) {
 			HashMap< String , Object > row = new HashMap< String , Object >( );
 			row.put( "planet_id" , PLANET_IDS[ i ] );
 			row.put( "resource_name" , RESOURCE_NAMES[ i ] );
 			row.put( "pres_income" , INCOME_VALUES[ i ] );
 			row.put( "pres_upkeep" , UPKEEP_VALUES[ i ] );
-			if ( i == 1 ) {
-				row.put( "resprov_quantity_max" , RP_CAPACITY );
-				row.put( "resprov_quantity" , RP_QUANTITY );
-				row.put( "resprov_difficulty" , RP_DIFFICULTY );
-				row.put( "resprov_recovery" , RP_RECOVERY );
+			if ( i > 0 ) {
+				row.put( "resprov_quantity_max" , RP_CAPACITY[ i - 1 ] );
+				row.put( "resprov_quantity" , RP_QUANTITY[ i - 1 ] );
+				row.put( "resprov_difficulty" , RP_DIFFICULTY[ i - 1 ] );
+				row.put( "resprov_recovery" , RP_RECOVERY[ i - 1 ] );
+				if ( i > 1 ) {
+					row.put( "mining_priority" , MINING_PRIORITY );
+				}
 			}
 			rows[ i ] = row;
 		}
@@ -106,7 +120,7 @@ public class TestResourceRowMapper
 
 
 	/**
-	 * Mapping a row with a provider information record
+	 * Mapping a row with a provider information record but no mining priority
 	 */
 	@Test
 	public void testMapWithProvider( )
@@ -121,9 +135,34 @@ public class TestResourceRowMapper
 		assertEquals( INCOME_VALUES[ 1 ] , row.getDelta( ).getIncome( ) , 0 );
 		assertEquals( UPKEEP_VALUES[ 1 ] , row.getDelta( ).getUpkeep( ) , 0 );
 		assertNotNull( row.getProvider( ) );
-		assertEquals( RP_CAPACITY , row.getProvider( ).getMaximalQuantity( ) , 0 );
-		assertEquals( RP_QUANTITY , row.getProvider( ).getCurrentQuantity( ) , 0 );
-		assertEquals( RP_DIFFICULTY , row.getProvider( ).getDifficulty( ) , 0 );
-		assertEquals( RP_RECOVERY , row.getProvider( ).getRecovery( ) , 0 );
+		assertEquals( RP_CAPACITY[ 0 ] , row.getProvider( ).getMaximalQuantity( ) , 0 );
+		assertEquals( RP_QUANTITY[ 0 ] , row.getProvider( ).getCurrentQuantity( ) , 0 );
+		assertEquals( RP_DIFFICULTY[ 0 ] , row.getProvider( ).getDifficulty( ) , 0 );
+		assertEquals( RP_RECOVERY[ 0 ] , row.getProvider( ).getRecovery( ) , 0 );
+		assertNull( row.getProvider( ).getMiningPriority( ) );
+	}
+
+
+	/**
+	 * Mapping a row with a provider information record and a mining priority
+	 */
+	@Test
+	public void testMapWithPriority( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 3 );
+
+		PlanetResourceRow row = this.mapper.mapRow( this.resultSet , 0 );
+		assertNotNull( row );
+		assertEquals( PLANET_IDS[ 2 ] , row.getPlanetId( ) );
+		assertEquals( RESOURCE_NAMES[ 2 ] , row.getDelta( ).getResource( ) );
+		assertEquals( INCOME_VALUES[ 2 ] , row.getDelta( ).getIncome( ) , 0 );
+		assertEquals( UPKEEP_VALUES[ 2 ] , row.getDelta( ).getUpkeep( ) , 0 );
+		assertNotNull( row.getProvider( ) );
+		assertEquals( RP_CAPACITY[ 1 ] , row.getProvider( ).getMaximalQuantity( ) , 0 );
+		assertEquals( RP_QUANTITY[ 1 ] , row.getProvider( ).getCurrentQuantity( ) , 0 );
+		assertEquals( RP_DIFFICULTY[ 1 ] , row.getProvider( ).getDifficulty( ) , 0 );
+		assertEquals( RP_RECOVERY[ 1 ] , row.getProvider( ).getRecovery( ) , 0 );
+		assertEquals( MINING_PRIORITY , row.getProvider( ).getMiningPriority( ) );
 	}
 }
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestEmpireResourceInformation.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestEmpireResourceInformation.java
index 2f03667..093a0e3 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestEmpireResourceInformation.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestEmpireResourceInformation.java
@@ -27,6 +27,9 @@ public class TestEmpireResourceInformation
 	/** "Possessed" value used in tests */
 	private static final Double TEST_POSSESSED = 2.0;
 
+	/** Mining priority value used in tests */
+	private static final Integer TEST_PRIORITY = 3;
+
 	/** Empire resource information instance used in tests */
 	private EmpireResourceInformation empRes;
 
@@ -46,6 +49,7 @@ public class TestEmpireResourceInformation
 		assertNull( this.empRes.getResource( ) );
 		assertNull( this.empRes.getOwed( ) );
 		assertNull( this.empRes.getPossessed( ) );
+		assertNull( this.empRes.getMiningPriority( ) );
 	}
 
 
@@ -90,7 +94,16 @@ public class TestEmpireResourceInformation
 	}
 
 
-	/** Serialising the instance to XML */
+	/** Setting and reading the mining priority */
+	@Test
+	public void testMiningPriority( )
+	{
+		this.empRes.setMiningPriority( TEST_PRIORITY );
+		assertEquals( TEST_PRIORITY , this.empRes.getMiningPriority( ) );
+	}
+
+
+	/** Serialising the instance to XML - no mining priority */
 	@Test
 	public void testXMLSerialisation( )
 	{
@@ -105,10 +118,31 @@ public class TestEmpireResourceInformation
 		assertTrue( serialised.contains( " id=\"" + TEST_ID + "\"" ) );
 		assertTrue( serialised.contains( " owed=\"" + TEST_OWED + "\"" ) );
 		assertTrue( serialised.contains( " possessed=\"" + TEST_POSSESSED + "\"" ) );
+		assertFalse( serialised.contains( "mining-priority=\"" ) );
 	}
 
 
-	/** Deserialising an instance from XML */
+	/** Serialising the instance to XML - including mining priority */
+	@Test
+	public void testXMLSerialisationWithPriority( )
+	{
+		this.empRes.setResource( TEST_ID );
+		this.empRes.setOwed( TEST_OWED );
+		this.empRes.setPossessed( TEST_POSSESSED );
+		this.empRes.setMiningPriority( TEST_PRIORITY );
+
+		String serialised = this.createXStreamInstance( ).toXML( this.empRes );
+		assertNotNull( serialised );
+		assertTrue( serialised.startsWith( "<resource " ) );
+		assertTrue( serialised.endsWith( "/>" ) );
+		assertTrue( serialised.contains( " id=\"" + TEST_ID + "\"" ) );
+		assertTrue( serialised.contains( " owed=\"" + TEST_OWED + "\"" ) );
+		assertTrue( serialised.contains( " possessed=\"" + TEST_POSSESSED + "\"" ) );
+		assertTrue( serialised.contains( " mining-priority=\"" + TEST_PRIORITY + "\"" ) );
+	}
+
+
+	/** Deserialising an instance from XML - no mining priority */
 	@Test
 	public void testXMLDeserialisation( )
 	{
@@ -123,6 +157,26 @@ public class TestEmpireResourceInformation
 		assertEquals( TEST_ID , this.empRes.getResource( ) );
 		assertEquals( TEST_POSSESSED , this.empRes.getPossessed( ) );
 		assertEquals( TEST_OWED , this.empRes.getOwed( ) );
+		assertNull( this.empRes.getMiningPriority( ) );
+	}
+
+
+	/** Deserialising an instance from XML - including mining priority */
+	@Test
+	public void testXMLDeserialisationWithPriority( )
+	{
+		String xml = "<resource id=\"" + TEST_ID + "\" owed=\"" + TEST_OWED.toString( ) + "\" possessed=\""
+				+ TEST_POSSESSED.toString( ) + "\" mining-priority=\"" + TEST_PRIORITY.toString( ) + "\" />";
+		Object deserialised = this.createXStreamInstance( ).fromXML( xml );
+
+		assertNotNull( deserialised );
+		assertEquals( EmpireResourceInformation.class , deserialised.getClass( ) );
+		this.empRes = (EmpireResourceInformation) deserialised;
+
+		assertEquals( TEST_ID , this.empRes.getResource( ) );
+		assertEquals( TEST_POSSESSED , this.empRes.getPossessed( ) );
+		assertEquals( TEST_OWED , this.empRes.getOwed( ) );
+		assertEquals( TEST_PRIORITY , this.empRes.getMiningPriority( ) );
 	}
 
 
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceProviderInformation.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceProviderInformation.java
index d545624..c912562 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceProviderInformation.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/bt/es/data/TestResourceProviderInformation.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.beans.bt.es.data;
 
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -27,6 +28,9 @@ public class TestResourceProviderInformation
 	/** A real number used in tests */
 	private static final Double TEST_DOUBLE = 4.2;
 
+	/** Mining priority value used in tests */
+	private static final Integer TEST_PRIORITY = 3;
+
 	/** A resource provider information record */
 	private ResourceProviderInformation rpi;
 
@@ -49,6 +53,7 @@ public class TestResourceProviderInformation
 		assertNull( this.rpi.getMaximalQuantity( ) );
 		assertNull( this.rpi.getDifficulty( ) );
 		assertNull( this.rpi.getRecovery( ) );
+		assertNull( this.rpi.getMiningPriority( ) );
 	}
 
 
@@ -124,7 +129,18 @@ public class TestResourceProviderInformation
 
 
 	/**
-	 * Serialising the instance to XML
+	 * Setting and reading the mining priority
+	 */
+	@Test
+	public void testMiningPriority( )
+	{
+		this.rpi.setMiningPriority( TEST_PRIORITY );
+		assertEquals( TEST_PRIORITY , this.rpi.getMiningPriority( ) );
+	}
+
+
+	/**
+	 * Serialising the instance to XML, without mining priority
 	 */
 	@Test
 	public void testXMLSerialisation( )
@@ -145,11 +161,39 @@ public class TestResourceProviderInformation
 		assertTrue( serialised.contains( " max=\"0.2\"" ) );
 		assertTrue( serialised.contains( " difficulty=\"0.3\"" ) );
 		assertTrue( serialised.contains( " recovery=\"0.4\"" ) );
+		assertFalse( serialised.contains( "mining-priority=" ) );
 	}
 
 
 	/**
-	 * Deserialising an instance that contains data
+	 * Serialising the instance to XML, with a mining priority
+	 */
+	@Test
+	public void testXMLSerialisationWithPriority( )
+	{
+		this.rpi.setResource( TEST_STRING );
+		this.rpi.setCurrentQuantity( 0.1 );
+		this.rpi.setMaximalQuantity( 0.2 );
+		this.rpi.setDifficulty( 0.3 );
+		this.rpi.setRecovery( 0.4 );
+		this.rpi.setMiningPriority( TEST_PRIORITY );
+
+		XStream xstream = this.createXStreamInstance( );
+		String serialised = xstream.toXML( this.rpi );
+		assertNotNull( serialised );
+		assertTrue( serialised.startsWith( "<resource-provider " ) );
+		assertTrue( serialised.endsWith( "/>" ) );
+		assertTrue( serialised.contains( " resource=\"" + TEST_STRING + "\"" ) );
+		assertTrue( serialised.contains( " current=\"0.1\"" ) );
+		assertTrue( serialised.contains( " max=\"0.2\"" ) );
+		assertTrue( serialised.contains( " difficulty=\"0.3\"" ) );
+		assertTrue( serialised.contains( " recovery=\"0.4\"" ) );
+		assertTrue( serialised.contains( " mining-priority=\"" + TEST_PRIORITY.toString( ) + "\"" ) );
+	}
+
+
+	/**
+	 * Deserialising an instance - no mining priority
 	 */
 	@Test
 	public void testXMLDeserialisation( )
@@ -167,16 +211,19 @@ public class TestResourceProviderInformation
 		assertEquals( (Double) 0.2 , this.rpi.getMaximalQuantity( ) );
 		assertEquals( (Double) 0.3 , this.rpi.getDifficulty( ) );
 		assertEquals( (Double) 0.4 , this.rpi.getRecovery( ) );
+		assertNull( this.rpi.getMiningPriority( ) );
 	}
 
 
 	/**
-	 * Deserialising an instance that contains no data
+	 * Deserialising an instance with mining priority
 	 */
 	@Test
-	public void testXMLDeserialisationNoData( )
+	public void testXMLDeserialisationWithPriority( )
 	{
-		String xml = "<resource-provider />";
+		String xml = "<resource-provider resource=\"" + TEST_STRING
+				+ "\" current=\"0.1\" max=\"0.2\" difficulty=\"0.3\" recovery=\"0.4\" mining-priority=\""
+				+ TEST_PRIORITY.toString( ) + "\" />";
 		XStream xstream = this.createXStreamInstance( );
 		Object deserialised = xstream.fromXML( xml );
 
@@ -184,11 +231,13 @@ public class TestResourceProviderInformation
 		assertEquals( ResourceProviderInformation.class , deserialised.getClass( ) );
 		this.rpi = (ResourceProviderInformation) deserialised;
 
-		assertNull( this.rpi.getResource( ) );
-		assertNull( this.rpi.getCurrentQuantity( ) );
-		assertNull( this.rpi.getMaximalQuantity( ) );
-		assertNull( this.rpi.getDifficulty( ) );
-		assertNull( this.rpi.getRecovery( ) );
+		assertEquals( TEST_STRING , this.rpi.getResource( ) );
+		assertEquals( (Double) 0.1 , this.rpi.getCurrentQuantity( ) );
+		assertEquals( (Double) 0.2 , this.rpi.getMaximalQuantity( ) );
+		assertEquals( (Double) 0.3 , this.rpi.getDifficulty( ) );
+		assertEquals( (Double) 0.4 , this.rpi.getRecovery( ) );
+		assertEquals( TEST_PRIORITY , this.rpi.getMiningPriority( ) );
+
 	}
 
 

From f4a16aa431cc1878a2dfe97d79a1ac16ba0b1a2c Mon Sep 17 00:00:00 2001
From: Tim Rosser <tim@mitheren.com>
Date: Tue, 10 Jan 2012 22:38:29 +1100
Subject: [PATCH 31/94] Resource Definition Loader

* Implemented resource definition loader including tests

* Added resource definition xml file and style definition

* Made a small style change to i18n loader
---
 legacyworlds-server-main/data/resources.xml   |  17 +
 legacyworlds-server-main/data/resources.xsd   |  44 +++
 .../com/deepclone/lw/cli/ImportResources.java | 372 ++++++++++++++++++
 .../java/com/deepclone/lw/cli/ImportText.java |   6 +-
 .../lw/cli/xmlimport/ResourceLoader.java      | 118 ++++++
 .../data/resources/BasicResource.java         | 105 +++++
 .../data/resources/NaturalResource.java       | 119 ++++++
 .../data/resources/ResourceParameter.java     |  65 +++
 .../xmlimport/data/resources/Resources.java   |  96 +++++
 .../data/resources/UOCResourceErrorCode.java  |  20 +
 .../resource-loader/bad-contents.xml          |   6 +
 .../TestFiles/resource-loader/bad-data.xml    |   8 +
 .../TestFiles/resource-loader/bad-xml.xml     |   2 +
 .../TestFiles/resource-loader/good-data.xml   |  17 +
 .../lw/cli/xmlimport/TestResourceLoader.java  | 155 ++++++++
 .../lw/cli/xmlimport/resources/BaseTest.java  | 267 +++++++++++++
 .../resources/TestBasicResource.java          | 203 ++++++++++
 .../resources/TestNaturalResource.java        | 329 ++++++++++++++++
 .../resources/TestResourceParameter.java      | 192 +++++++++
 .../xmlimport/resources/TestResources.java    | 137 +++++++
 20 files changed, 2275 insertions(+), 3 deletions(-)
 create mode 100644 legacyworlds-server-main/data/resources.xml
 create mode 100644 legacyworlds-server-main/data/resources.xsd
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportResources.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/ResourceLoader.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/BasicResource.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/NaturalResource.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/ResourceParameter.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/Resources.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/UOCResourceErrorCode.java
 create mode 100644 legacyworlds-server-tests/TestFiles/resource-loader/bad-contents.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/resource-loader/bad-data.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/resource-loader/bad-xml.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/resource-loader/good-data.xml
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestResourceLoader.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/BaseTest.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestBasicResource.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestNaturalResource.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResourceParameter.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResources.java

diff --git a/legacyworlds-server-main/data/resources.xml b/legacyworlds-server-main/data/resources.xml
new file mode 100644
index 0000000..77a3e62
--- /dev/null
+++ b/legacyworlds-server-main/data/resources.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-resources xmlns="http://www.deepclone.com/lw/b6/m2/resources"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/resources
+resources.xsd">
+
+	<basic-resource name="money" description="moneyDescription"
+		weight="0" /> <!-- This could have a category="" as well -->
+
+	<natural-resource name="titanium" description="titaniumDescription"
+		category="minerals" weight="1" presence-probability="0.8">
+		<quantity average="5000" deviation="1500" />
+		<difficulty average="0.1" deviation="0.05" />
+		<recovery average="0.4" deviation="0.05" />
+	</natural-resource>
+
+</lw-resources>
\ No newline at end of file
diff --git a/legacyworlds-server-main/data/resources.xsd b/legacyworlds-server-main/data/resources.xsd
new file mode 100644
index 0000000..a0a46f2
--- /dev/null
+++ b/legacyworlds-server-main/data/resources.xsd
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns="http://www.deepclone.com/lw/b6/m2/resources"
+	targetNamespace="http://www.deepclone.com/lw/b6/m2/resources"
+xmlns:xs="http://www.w3.org/2001/XMLSchema"
+	elementFormDefault="qualified" attributeFormDefault="unqualified">
+
+	<xs:element name="lw-resources">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="basic-resource" type="basic-resource"
+					minOccurs="0" maxOccurs="unbounded" />
+				<xs:element name="natural-resource" type="natural-resource"
+					minOccurs="0" maxOccurs="unbounded" />
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:complexType name="basic-resource">
+		<xs:attribute name="name" use="required" type="xs:token" />
+		<xs:attribute name="description" use="required" type="xs:token" />
+		<xs:attribute name="category" use="optional" type="xs:token" />
+		<xs:attribute name="weight" use="required" type="xs:integer" />
+	</xs:complexType>
+
+	<xs:complexType name="natural-resource">
+		<xs:complexContent>
+			<xs:extension base="basic-resource">
+				<xs:sequence>
+					<xs:element name="quantity" type="resource-parameter" />
+					<xs:element name="difficulty" type="resource-parameter" />
+					<xs:element name="recovery" type="resource-parameter" />
+				</xs:sequence>
+				<xs:attribute name="presence-probability" use="required"
+					type="xs:decimal" />
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+
+	<xs:complexType name="resource-parameter">
+		<xs:attribute name="average" use="required" type="xs:decimal" />
+		<xs:attribute name="deviation" use="required" type="xs:decimal" />
+	</xs:complexType>
+
+</xs:schema>
\ No newline at end of file
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportResources.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportResources.java
new file mode 100644
index 0000000..b66063b
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportResources.java
@@ -0,0 +1,372 @@
+package com.deepclone.lw.cli;
+
+
+import java.io.File;
+
+import javax.sql.DataSource;
+
+import org.apache.log4j.Logger;
+import org.postgresql.util.PGobject;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.FileSystemXmlApplicationContext;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.cli.xmlimport.ResourceLoader;
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.resources.BasicResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.NaturalResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.Resources;
+import com.deepclone.lw.cli.xmlimport.data.resources.UOCResourceErrorCode;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Resources import tool
+ * 
+ * <p>
+ * This class defines the body of the resource import tool. It loads the data file specified on the
+ * command line, validates it and then loads the resource definitions into the database.
+ * 
+ * @author <a href="mailto:tim@mitheren.com">T. Rosser</a>
+ */
+public class ImportResources
+		extends CLITool
+{
+	/** Logging system */
+	private final Logger logger = Logger.getLogger( ImportResources.class );
+
+	/** File to read the definitions from */
+	private File file;
+
+	/** Spring transaction template */
+	private TransactionTemplate tTemplate;
+
+	/** Basic resource with category update or create stored procedure */
+	private StoredProc uocBasicResourceWithCategory;
+
+	/** Basic resource without category update or create stored procedure */
+	private StoredProc uocBasicResourceWithoutCategory;
+
+	/** Natural resource with category update or create stored procedure */
+	private StoredProc uocNaturalResourceWithCategory;
+
+	/** Natural resource without category update or create stored procedure */
+	private StoredProc uocNaturalResourceWithoutCategory;
+
+
+	/**
+	 * Create the Spring context
+	 * 
+	 * <p>
+	 * Load the definition of the data source as a Spring context, then adds the transaction
+	 * management component.
+	 * 
+	 * @return the Spring application context
+	 */
+	private ClassPathXmlApplicationContext createContext( )
+	{
+		FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( new String[] {
+			this.getDataSource( )
+		} );
+		ctx.refresh( );
+
+		return new ClassPathXmlApplicationContext( new String[] {
+			"configuration/transaction-bean.xml"
+		} , true , ctx );
+	}
+
+
+	/**
+	 * Create database access templates
+	 * 
+	 * <p>
+	 * Initialise the transaction template and the four stored procedure definitions.
+	 * 
+	 * @param ctx
+	 *            the Spring application context
+	 */
+	private void createTemplates( ApplicationContext ctx )
+	{
+		DataSource dSource = ctx.getBean( DataSource.class );
+		PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
+
+		this.uocBasicResourceWithoutCategory = new StoredProc( dSource , "defs" , "uoc_resource" )
+				.addParameter( "_name" , java.sql.Types.VARCHAR )
+				.addParameter( "_description" , java.sql.Types.VARCHAR )
+				.addParameter( "_weight" , java.sql.Types.INTEGER ).addOutput( "_return" , java.sql.Types.OTHER );
+
+		this.uocBasicResourceWithCategory = new StoredProc( dSource , "defs" , "uoc_resource" )
+				.addParameter( "_name" , java.sql.Types.VARCHAR )
+				.addParameter( "_description" , java.sql.Types.VARCHAR )
+				.addParameter( "_category" , java.sql.Types.VARCHAR ).addParameter( "_weight" , java.sql.Types.INTEGER )
+				.addOutput( "_return" , java.sql.Types.OTHER );
+
+		this.uocNaturalResourceWithoutCategory = new StoredProc( dSource , "defs" , "uoc_natural_resource" )
+				.addParameter( "_name" , java.sql.Types.VARCHAR )
+				.addParameter( "_description" , java.sql.Types.VARCHAR )
+				.addParameter( "_weight" , java.sql.Types.INTEGER ).addParameter( "_presence" , java.sql.Types.DOUBLE )
+				.addParameter( "_quantity_avg" , java.sql.Types.DOUBLE )
+				.addParameter( "_quantity_dev" , java.sql.Types.DOUBLE )
+				.addParameter( "_difficulty_avg" , java.sql.Types.DOUBLE )
+				.addParameter( "_difficulty_dev" , java.sql.Types.DOUBLE )
+				.addParameter( "_recovery_avg" , java.sql.Types.DOUBLE )
+				.addParameter( "_recovery_dev" , java.sql.Types.DOUBLE ).addOutput( "_return" , java.sql.Types.OTHER );
+
+		this.uocNaturalResourceWithCategory = new StoredProc( dSource , "defs" , "uoc_natural_resource" )
+				.addParameter( "_name" , java.sql.Types.VARCHAR )
+				.addParameter( "_description" , java.sql.Types.VARCHAR )
+				.addParameter( "_category" , java.sql.Types.VARCHAR ).addParameter( "_weight" , java.sql.Types.INTEGER )
+				.addParameter( "_presence" , java.sql.Types.DOUBLE )
+				.addParameter( "_quantity_avg" , java.sql.Types.DOUBLE )
+				.addParameter( "_quantity_dev" , java.sql.Types.DOUBLE )
+				.addParameter( "_difficulty_avg" , java.sql.Types.DOUBLE )
+				.addParameter( "_difficulty_dev" , java.sql.Types.DOUBLE )
+				.addParameter( "_recovery_avg" , java.sql.Types.DOUBLE )
+				.addParameter( "_recovery_dev" , java.sql.Types.DOUBLE ).addOutput( "_return" , java.sql.Types.OTHER );
+
+		this.tTemplate = new TransactionTemplate( tManager );
+	}
+
+
+	/**
+	 * Import all resource definitions
+	 * 
+	 * <p>
+	 * Import all resource definitions from the top-level Resources data instance based on the type
+	 * of resource, and whether or not it has a category.
+	 * 
+	 * @param data
+	 *            the top level Resources data instance
+	 * 
+	 * @throws DataImportException
+	 *             when some external resource definition fails to load
+	 */
+	private void importResources( Resources data )
+			throws DataImportException
+	{
+		for ( BasicResource br : data ) {
+			if ( br instanceof NaturalResource ) {
+				if ( br.getCategory( ) == null ) {
+					this.importNaturalResourceWithoutCategory( (NaturalResource) br );
+				} else {
+					this.importNaturalResourceWithCategory( (NaturalResource) br );
+				}
+			} else {
+				if ( br.getCategory( ) == null ) {
+					this.importBasicResourceWithoutCategory( br );
+				} else {
+					this.importBasicResourceWithCategory( br );
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * Import a Natural Resource
+	 * 
+	 * <p>
+	 * Import a natural resource that does not have a defined category.
+	 * 
+	 * @param nr
+	 *            the resources definition data
+	 * 
+	 * @throws DataImportException
+	 *             when some external resource definition fails to load
+	 */
+	private void importNaturalResourceWithoutCategory( NaturalResource nr )
+			throws DataImportException
+	{
+		UOCResourceErrorCode err = UOCResourceErrorCode.valueOf( ( (PGobject) this.uocNaturalResourceWithoutCategory
+				.execute( nr.getName( ) , nr.getDescription( ) , nr.getWeight( ) , nr.getPresenceProbability( ) ,
+						nr.getQuantity( ).getAverage( ) , nr.getQuantity( ).getDeviation( ) ,
+						nr.getDifficulty( ).getAverage( ) , nr.getDifficulty( ).getDeviation( ) ,
+						nr.getRecovery( ).getAverage( ) , nr.getRecovery( ).getDeviation( ) ).get( "_return" ) )
+				.getValue( ) );
+
+		if ( !err.equals( UOCResourceErrorCode.CREATED ) && !err.equals( UOCResourceErrorCode.UPDATED ) ) {
+			throw new DataImportException( "uocNaturalResourceWithoutCategory returned error code " + err.toString( ) );
+		}
+	}
+
+
+	/**
+	 * Import a Natural Resource
+	 * 
+	 * <p>
+	 * Import a natural resource that does have a defined category.
+	 * 
+	 * @param nr
+	 *            the resources definition data
+	 * 
+	 * @throws DataImportException
+	 *             when some external resource definition fails to load
+	 */
+	private void importNaturalResourceWithCategory( NaturalResource nr )
+			throws DataImportException
+	{
+		UOCResourceErrorCode err = UOCResourceErrorCode.valueOf( ( (PGobject) this.uocNaturalResourceWithCategory
+				.execute( nr.getName( ) , nr.getDescription( ) , nr.getCategory( ) , nr.getWeight( ) ,
+						nr.getPresenceProbability( ) , nr.getQuantity( ).getAverage( ) ,
+						nr.getQuantity( ).getDeviation( ) , nr.getDifficulty( ).getAverage( ) ,
+						nr.getDifficulty( ).getDeviation( ) , nr.getRecovery( ).getAverage( ) ,
+						nr.getRecovery( ).getDeviation( ) ).get( "_return" ) ).getValue( ) );
+
+		if ( !err.equals( UOCResourceErrorCode.CREATED ) && !err.equals( UOCResourceErrorCode.UPDATED ) ) {
+			throw new DataImportException( "uocNaturalResourceWithCategory returned error code " + err.toString( ) );
+		}
+	}
+
+
+	/**
+	 * Import a Basic Resource
+	 * 
+	 * <p>
+	 * Import a basic resource that does not have a defined category.
+	 * 
+	 * @param br
+	 *            the resources definition data
+	 * 
+	 * @throws DataImportException
+	 *             when some external resource definition fails to load
+	 */
+	private void importBasicResourceWithoutCategory( BasicResource br )
+			throws DataImportException
+	{
+		UOCResourceErrorCode err = UOCResourceErrorCode.valueOf( ( (PGobject) this.uocBasicResourceWithoutCategory
+				.execute( br.getName( ) , br.getDescription( ) , br.getWeight( ) ).get( "_return" ) ).getValue( ) );
+
+		if ( !err.equals( UOCResourceErrorCode.CREATED ) && !err.equals( UOCResourceErrorCode.UPDATED ) ) {
+			throw new DataImportException( "uocBasicResourceWithoutCategory returned error code " + err.toString( ) );
+		}
+	}
+
+
+	/**
+	 * Import a Basic Resource
+	 * 
+	 * <p>
+	 * Import a basic resource that does not have a defined category.
+	 * 
+	 * @param br
+	 *            the resources definition data
+	 * 
+	 * @throws DataImportException
+	 *             when some external resource definition fails to load
+	 */
+	private void importBasicResourceWithCategory( BasicResource br )
+			throws DataImportException
+	{
+		UOCResourceErrorCode err = UOCResourceErrorCode
+				.valueOf( ( (PGobject) this.uocBasicResourceWithCategory.execute( br.getName( ) , br.getDescription( ) ,
+						br.getCategory( ) , br.getWeight( ) ).get( "_return" ) ).getValue( ) );
+
+		if ( !err.equals( UOCResourceErrorCode.CREATED ) && !err.equals( UOCResourceErrorCode.UPDATED ) ) {
+			throw new DataImportException( "uocBasicResourceWithCategory returned error code " + err.toString( ) );
+		}
+	}
+
+
+	/**
+	 * Run the resource definitions import tool
+	 * 
+	 * <p>
+	 * Loads the data file, the connects to the database and creates or updates all definitions.
+	 */
+	@Override
+	public void run( )
+	{
+		Resources data;
+		try {
+			data = new ResourceLoader( this.file ).load( );
+		} catch ( DataImportException e ) {
+			System.err.println( "Error while loading '" + this.file + "': " + e.getMessage( ) );
+			this.logger.error( "Error while loading resources data" , e );
+			return;
+		}
+
+		AbstractApplicationContext ctx = this.createContext( );
+		this.createTemplates( ctx );
+		this.executeImportTransaction( data );
+		ToolBase.destroyContext( ctx );
+	}
+
+
+	/**
+	 * Execute the resource definitions importation transaction
+	 * 
+	 * <p>
+	 * Run a transaction and execute the importation code inside it. Roll back if anything goes
+	 * wrong.
+	 * 
+	 * @param data
+	 *            the Resources definitions instance
+	 */
+	private void executeImportTransaction( final Resources data )
+	{
+		boolean rv = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
+			@Override
+			public Boolean doInTransaction( TransactionStatus status )
+			{
+				boolean rv = ImportResources.this.doTransaction( data );
+				if ( !rv ) {
+					status.setRollbackOnly( );
+				}
+				return rv;
+			}
+		} );
+		if ( rv ) {
+			this.logger.info( "Resource import successful" );
+		}
+	}
+
+
+	/**
+	 * Import transaction body
+	 * 
+	 * <p>
+	 * Import all definitions and handle exceptions.
+	 * 
+	 * @param data
+	 *            the Resources definitions instance
+	 * @return
+	 */
+	private boolean doTransaction( Resources data )
+	{
+		try {
+			this.importResources( data );
+			return true;
+		} catch ( RuntimeException e ) {
+			this.logger.error( "Caught runtime exception" , e );
+		} catch ( DataImportException e ) {
+			this.logger.error( "Error while importing resources" , e );
+		}
+		return false;
+	}
+
+
+	/**
+	 * Obtain the name of the definitions file
+	 * 
+	 * <p>
+	 * Check the command line options, setting the definitions file accordingly.
+	 */
+	@Override
+	public boolean setOptions( String... options )
+	{
+		if ( options.length != 1 ) {
+			return false;
+		}
+		this.file = new File( options[ 0 ] );
+		if ( ! ( this.file.isFile( ) && this.file.canRead( ) ) ) {
+			return false;
+		}
+		return true;
+	}
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
index 8d6e856..339182d 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
@@ -217,12 +217,12 @@ public class ImportText
 	private boolean doTransaction( I18NText data )
 	{
 		try {
-			ImportText.this.importText( data );
+			this.importText( data );
 			return true;
 		} catch ( RuntimeException e ) {
-			ImportText.this.logger.error( "Caught runtime exception" , e );
+			this.logger.error( "Caught runtime exception" , e );
 		} catch ( DataImportException e ) {
-			ImportText.this.logger.error( "Error while importing external strings" , e );
+			this.logger.error( "Error while importing external strings" , e );
 		}
 		return false;
 	}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/ResourceLoader.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/ResourceLoader.java
new file mode 100644
index 0000000..47656d8
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/ResourceLoader.java
@@ -0,0 +1,118 @@
+package com.deepclone.lw.cli.xmlimport;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.resources.NaturalResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.Resources;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.XStreamException;
+
+
+
+/**
+ * Resource Loader
+ * 
+ * <p>
+ * This class can be used to load all resource definitions. It extracts them from the XML file and
+ * verifies them.
+ * 
+ * @author <a href="mailto:tim@mitheren.com">T. Rosser</a>
+ */
+public class ResourceLoader
+{
+	/** The file to read the XML from */
+	private final File file;
+
+
+	/**
+	 * Initialise the loader
+	 * 
+	 * @param file
+	 *            the XML file that contains the definitions
+	 */
+	public ResourceLoader( File file )
+	{
+		this.file = file.getAbsoluteFile( );
+	}
+
+
+	/**
+	 * Initialise the necessary XStream instance
+	 * 
+	 * <p>
+	 * Initialise the XStream instance by processing the annotations of all the resource importable
+	 * data classes.
+	 * 
+	 * @return the XStream instance to use when loading the data
+	 */
+	private XStream initXStream( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( Resources.class );
+		xstream.processAnnotations( NaturalResource.class );
+		return xstream;
+	}
+
+
+	/**
+	 * Load the resource definitions
+	 * 
+	 * <p>
+	 * Load the XML file and process the definitions using XStream.
+	 * 
+	 * @return the top-level importable data instance
+	 * 
+	 * @throws DataImportException
+	 *             if reading from the file or parsing its contents fail
+	 */
+	private Resources loadXMLFile( )
+			throws DataImportException
+	{
+		FileInputStream fis;
+		try {
+			fis = new FileInputStream( this.file );
+		} catch ( FileNotFoundException e ) {
+			throw new DataImportException( "Unable to load resource definitions" , e );
+		}
+
+		try {
+			try {
+				XStream xstream = this.initXStream( );
+				return (Resources) xstream.fromXML( fis );
+			} finally {
+				fis.close( );
+			}
+		} catch ( IOException e ) {
+			throw new DataImportException( "Input error while loading resource definitions" , e );
+		} catch ( XStreamException e ) {
+			throw new DataImportException( "XML error while loading resource definitions" , e );
+		}
+	}
+
+
+	/**
+	 * Load and process resource definitions
+	 * 
+	 * <p>
+	 * Attempt to load all resource definitions, ensure they are valid, then set the original file's
+	 * path.
+	 * 
+	 * @return the top-level importable data instance
+	 * 
+	 * @throws DataImportException
+	 *             if loading or verifying the data fails
+	 */
+	public Resources load( )
+			throws DataImportException
+	{
+		Resources resources = this.loadXMLFile( );
+		resources.verifyData( );
+		resources.setReadFrom( this.file );
+		return resources;
+	}
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/BasicResource.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/BasicResource.java
new file mode 100644
index 0000000..551ff47
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/BasicResource.java
@@ -0,0 +1,105 @@
+package com.deepclone.lw.cli.xmlimport.data.resources;
+
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * A Basic Resource
+ * 
+ * <p>
+ * This class is the base for all resource classes providing a name, description, category and
+ * weight. In certain cases the category can be null. It provides a method to verify that data has
+ * been imported successfully.
+ * 
+ * @author <a href="mailto:tim@mitheren.com">T. Rosser</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "basic-resource" )
+public class BasicResource
+		extends ImportableData
+{
+	/** The resource's name */
+	@XStreamAsAttribute
+	private String name;
+
+	/** The resource's description */
+	@XStreamAsAttribute
+	private String description;
+
+	/**
+	 * The resource's category
+	 * 
+	 * <p>
+	 * Can be null for resources that do not belong to a category
+	 */
+	@XStreamAsAttribute
+	private String category;
+
+	/**
+	 * The resource's weight
+	 * 
+	 * <p>
+	 * Used when sorting resources and resource categories.
+	 */
+	@XStreamAsAttribute
+	private Integer weight; // Is int enough?
+
+
+	/**
+	 * Check a resource's data
+	 * 
+	 * <p>
+	 * Make sure that a resource's properties are both present and valid.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		if ( this.name == null || "".equals( this.name.trim( ) ) ) {
+			throw new DataImportException( "Missing name string" );
+		}
+		if ( this.description == null || "".equals( this.description.trim( ) ) ) {
+			throw new DataImportException( "Missing name string" );
+		}
+		if ( this.category != null && "".equals( this.description.trim( ) ) ) {
+			throw new DataImportException( "Category invalid" );
+		}
+		if ( this.weight == null ) {
+			throw new DataImportException( "Missing weight" );
+		}
+	}
+
+
+	/** @return the resource's name */
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	/** @return the resource's description */
+	public String getDescription( )
+	{
+		return this.description;
+	}
+
+
+	/** @return the resource's category */
+	public String getCategory( )
+	{
+		return this.category;
+	}
+
+
+	/** @return the resource's weight */
+	public Integer getWeight( )
+	{
+		return this.weight;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/NaturalResource.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/NaturalResource.java
new file mode 100644
index 0000000..7929d13
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/NaturalResource.java
@@ -0,0 +1,119 @@
+package com.deepclone.lw.cli.xmlimport.data.resources;
+
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * A Natural Resource
+ * 
+ * <p>
+ * This class represents naturally occurring resources. As well as extending {@link BasicResource}
+ * it contains the presence-probability, quantity, difficulty and recovery of a resource and a
+ * method to verify the data of these properties.
+ * 
+ * @author <a href="mailto:tim@mitheren.com">T. Rosser</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "natural-resource" )
+public class NaturalResource
+		extends BasicResource
+{
+	/**
+	 * The resource's presence-probability
+	 * 
+	 * <p>
+	 * Used by the universe generator to decide whether to add this type of resource to a particular
+	 * planet or not.
+	 */
+	@XStreamAlias( "presence-probability" )
+	@XStreamAsAttribute
+	private Double presenceProbability;
+
+	/** The resource's quantity */
+	@XStreamAlias( "quantity" )
+	private ResourceParameter quantity;
+
+	/**
+	 * The resource's difficulty
+	 * 
+	 * <p>
+	 * The difficulty of extracting the resource.
+	 */
+	@XStreamAlias( "difficulty" )
+	private ResourceParameter difficulty;
+
+	/**
+	 * The resource's recovery
+	 * 
+	 * <p>
+	 * The recovery rate of the resource.
+	 */
+	@XStreamAlias( "recovery" )
+	private ResourceParameter recovery;
+
+
+	/**
+	 * Check a resource's data
+	 * 
+	 * <p>
+	 * Make sure that a resource's properties are both present and valid. Calls the base class'
+	 * verifyData() method to check all of the data.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		super.verifyData( );
+
+		if ( this.presenceProbability == null ) {
+			throw new DataImportException( "Missing presence-probability" );
+		}
+		if ( this.quantity == null ) {
+			throw new DataImportException( "Missing quantity" );
+		}
+		this.quantity.verifyData( "quantity" );
+
+		if ( this.difficulty == null ) {
+			throw new DataImportException( "Missing difficulty" );
+		}
+		this.difficulty.verifyData( "difficulty" );
+
+		if ( this.recovery == null ) {
+			throw new DataImportException( "Missing recovery" );
+		}
+		this.recovery.verifyData( "recovery" );
+
+	}
+
+
+	/** @return the resource's presence-probability */
+	public Double getPresenceProbability( )
+	{
+		return this.presenceProbability;
+	}
+
+
+	/** @return the resource's quantity */
+	public ResourceParameter getQuantity( )
+	{
+		return this.quantity;
+	}
+
+
+	/** @return the resource's difficulty */
+	public ResourceParameter getDifficulty( )
+	{
+		return this.difficulty;
+	}
+
+
+	/** @return the resource's recovery */
+	public ResourceParameter getRecovery( )
+	{
+		return this.recovery;
+	}
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/ResourceParameter.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/ResourceParameter.java
new file mode 100644
index 0000000..6d7c6fe
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/ResourceParameter.java
@@ -0,0 +1,65 @@
+package com.deepclone.lw.cli.xmlimport.data.resources;
+
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * Resource property parameter
+ * 
+ * <p>
+ * This class represents the average and deviation of various properties of a resource.
+ * 
+ * @author <a href="mailto:tim@mitheren.com">T. Rosser</a>
+ */
+@SuppressWarnings( "serial" )
+public class ResourceParameter
+		extends ImportableData
+{
+	/** The property's average */
+	@XStreamAsAttribute
+	private Double average;
+
+	/** The property's deviation */
+	@XStreamAsAttribute
+	private Double deviation;
+
+
+	/**
+	 * Check the property's data
+	 * 
+	 * <p>
+	 * Make sure that a average and deviation are present.
+	 * 
+	 * @param property
+	 *            the name of the property
+	 */
+	public void verifyData( String property )
+			throws DataImportException
+	{
+		if ( this.average == null ) {
+			throw new DataImportException( "Missing average for " + property );
+		}
+		if ( this.deviation == null ) {
+			throw new DataImportException( "Missing deviation for " + property );
+		}
+	}
+
+
+	/** @return the property's average */
+	public Double getAverage( )
+	{
+		return this.average;
+	}
+
+
+	/** @return the property's deviation */
+	public Double getDeviation( )
+	{
+		return this.deviation;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/Resources.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/Resources.java
new file mode 100644
index 0000000..ad8b907
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/Resources.java
@@ -0,0 +1,96 @@
+package com.deepclone.lw.cli.xmlimport.data.resources;
+
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+/**
+ * Resource Data
+ * 
+ * <p>
+ * This class represents the contents of the resource text file. It contains a list of resource
+ * definitions.
+ * 
+ * @author <a href="mailto:tim@mitheren.com">T. Rosser</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "lw-resources" )
+public class Resources
+		extends ImportableData
+		implements Iterable< BasicResource >
+{
+	/** All present resource definitions */
+	@XStreamImplicit
+	private final List< BasicResource > resources = new LinkedList< BasicResource >( );
+
+
+	/**
+	 * Checks the resource data
+	 * 
+	 * <p>
+	 * Checks each definition and ensures they are all unique.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		if ( this.resources == null ) {
+			throw new DataImportException( "No resource definitions" );
+		}
+
+		HashSet< String > names = new HashSet< String >( );
+		for ( BasicResource resource : this.resources ) {
+			resource.verifyData( );
+			this.checkUniqueItem( names , resource.getName( ) );
+		}
+	}
+
+
+	/**
+	 * Checks that the name of the resource is unique
+	 * 
+	 * <p>
+	 * This helper method is used by {@link #verifyData()} to make sure resource names are unique.
+	 * 
+	 * @param existing
+	 *            the existing set of items
+	 * @param value
+	 *            the item's value
+	 * 
+	 * @throws DataImportException
+	 *             if the item's value is already present in the set of existing items
+	 */
+	public void checkUniqueItem( HashSet< String > existing , String value )
+			throws DataImportException
+	{
+		if ( existing.contains( value ) ) {
+			throw new DataImportException( "Duplicate resource name '" + value + "'" );
+		}
+		existing.add( value );
+	}
+
+
+	/**
+	 * Resource definition iterator
+	 * 
+	 * <p>
+	 * Grant access to the list of resources in read-only mode.
+	 * 
+	 * @return a read-only iterator on the list of resources.
+	 */
+	@Override
+	public Iterator< BasicResource > iterator( )
+	{
+		return Collections.unmodifiableList( this.resources ).iterator( );
+	}
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/UOCResourceErrorCode.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/UOCResourceErrorCode.java
new file mode 100644
index 0000000..47dbed7
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/UOCResourceErrorCode.java
@@ -0,0 +1,20 @@
+package com.deepclone.lw.cli.xmlimport.data.resources;
+
+import com.deepclone.lw.cli.ImportResources;
+
+
+/**
+ * Enum representing the error codes returned by the uoc_* stored procedures in
+ * {@link ImportResources}
+ * 
+ * @author <a href="tim@mitheren.com">T. Rosser</a>
+ * 
+ */
+public enum UOCResourceErrorCode {
+	CREATED ,
+	UPDATED ,
+	BAD_TYPE ,
+	BAD_STRINGS ,
+	BAD_VALUE ,
+	DUP_DESCR;
+}
diff --git a/legacyworlds-server-tests/TestFiles/resource-loader/bad-contents.xml b/legacyworlds-server-tests/TestFiles/resource-loader/bad-contents.xml
new file mode 100644
index 0000000..262c241
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/resource-loader/bad-contents.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-resources>
+
+	<does-not-exist />
+
+</lw-resources>
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/resource-loader/bad-data.xml b/legacyworlds-server-tests/TestFiles/resource-loader/bad-data.xml
new file mode 100644
index 0000000..2209c73
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/resource-loader/bad-data.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-resources xmlns="http://www.deepclone.com/lw/b6/m2/resources"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/resources
+resources.xsd">
+
+	<basic-resource /> 
+</lw-resources>
diff --git a/legacyworlds-server-tests/TestFiles/resource-loader/bad-xml.xml b/legacyworlds-server-tests/TestFiles/resource-loader/bad-xml.xml
new file mode 100644
index 0000000..ee1d0ed
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/resource-loader/bad-xml.xml
@@ -0,0 +1,2 @@
+This is not an XML file, obviously.
+We'll make that even more confusing: <<<<<< & >>!!!
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/resource-loader/good-data.xml b/legacyworlds-server-tests/TestFiles/resource-loader/good-data.xml
new file mode 100644
index 0000000..77a3e62
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/resource-loader/good-data.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-resources xmlns="http://www.deepclone.com/lw/b6/m2/resources"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/resources
+resources.xsd">
+
+	<basic-resource name="money" description="moneyDescription"
+		weight="0" /> <!-- This could have a category="" as well -->
+
+	<natural-resource name="titanium" description="titaniumDescription"
+		category="minerals" weight="1" presence-probability="0.8">
+		<quantity average="5000" deviation="1500" />
+		<difficulty average="0.1" deviation="0.05" />
+		<recovery average="0.4" deviation="0.05" />
+	</natural-resource>
+
+</lw-resources>
\ No newline at end of file
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestResourceLoader.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestResourceLoader.java
new file mode 100644
index 0000000..d49210d
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestResourceLoader.java
@@ -0,0 +1,155 @@
+package com.deepclone.lw.cli.xmlimport;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.resources.BasicResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.NaturalResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.Resources;
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.io.StreamException;
+
+
+
+/**
+ * Unit tests for the {@link ResourceLoader} class.
+ * 
+ * @author <a href="mailto:tim@mitheren.com">T. Rosser</a>
+ * 
+ */
+public class TestResourceLoader
+{
+	/**
+	 * Try initialising the loader with a <code>null</code> file instance.
+	 * 
+	 * @throws NullPointerException
+	 *             when the constructor is executed
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testNullFile( )
+			throws NullPointerException
+	{
+		new ResourceLoader( null );
+	}
+
+
+	/** Try loading a file that does not exist */
+	@Test
+	public void testMissingFile( )
+	{
+		ResourceLoader loader = new ResourceLoader( new File( "does-not-exist" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a" + e.getCause( ).getClass( ).getName( ) , e.getCause( ) instanceof IOException );
+			return;
+		}
+		fail( "no exception after trying to load a missing file" );
+	}
+
+
+	/** Try loading a file that contains something that is not XML. */
+	@Test
+	public void testBadXML( )
+	{
+		ResourceLoader loader = new ResourceLoader( new File( "TestFiles/resource-loader/bad-xml.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a " + e.getCause( ).getClass( ).getName( ) , e.getCause( ) instanceof StreamException );
+			return;
+		}
+		fail( "no exception after loading stuff that isn't XML" );
+	}
+
+
+	/**
+	 * Test loading a file that contains XML but which cannot be deserialised to an
+	 * {@link Resources} instance.
+	 */
+	@Test
+	public void testBadContents( )
+	{
+		ResourceLoader loader = new ResourceLoader( new File( "TestFiles/resource-loader/bad-contents.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a " + e.getCause( ).getClass( ).getName( ) ,
+					e.getCause( ) instanceof ConversionException );
+			return;
+		}
+		fail( "no exception after loading bad XML" );
+	}
+
+
+	/**
+	 * Try loading a file that contains valid XML for a {@link ResourceLoader} instance with
+	 * semantic errors.
+	 */
+	@Test
+	public void testBadData( )
+	{
+		ResourceLoader loader = new ResourceLoader( new File( "TestFiles/resource-loader/bad-data.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertNull( e.getCause( ) );
+			return;
+		}
+		fail( "no exception after loading bad data" );
+	}
+
+
+	/** Try loading valid data, make sure that it contains exactly what is expected */
+	@Test
+	public void testGoodData( )
+	{
+		ResourceLoader loader = new ResourceLoader( new File( "TestFiles/resource-loader/good-data.xml" ) );
+		Resources resources;
+		try {
+			resources = loader.load( );
+		} catch ( DataImportException e ) {
+			fail( "could not load valid file" );
+			return;
+		}
+		assertNotNull( resources );
+
+		// Not sure if this is the best way to code for the two different resource types...
+		int rCount = 0;
+
+		for ( BasicResource br : resources ) {
+			if ( rCount == 0 ) {
+				assertEquals( "money" , br.getName( ) );
+				assertEquals( "moneyDescription" , br.getDescription( ) );
+				assertEquals( new Integer( 0 ) , br.getWeight( ) );
+				assertEquals( null , br.getCategory( ) );
+			} else if ( rCount == 1 ) {
+				// This isn't retarded is it?
+				NaturalResource nr = (NaturalResource) br;
+				assertEquals( "titanium" , nr.getName( ) );
+				assertEquals( "titaniumDescription" , nr.getDescription( ) );
+				assertEquals( new Integer( 1 ) , nr.getWeight( ) );
+				assertEquals( "minerals" , nr.getCategory( ) );
+				assertEquals( new Double( 0.8 ) , nr.getPresenceProbability( ) );
+				assertEquals( new Double( 5000 ) , nr.getQuantity( ).getAverage( ) );
+				assertEquals( new Double( 1500 ) , nr.getQuantity( ).getDeviation( ) );
+				assertEquals( new Double( 0.1 ) , nr.getDifficulty( ).getAverage( ) );
+				assertEquals( new Double( 0.05 ) , nr.getDifficulty( ).getDeviation( ) );
+				assertEquals( new Double( 0.4 ) , nr.getRecovery( ).getAverage( ) );
+				assertEquals( new Double( 0.05 ) , nr.getRecovery( ).getDeviation( ) );
+			}
+			rCount++;
+		}
+		assertEquals( 2 , rCount );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/BaseTest.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/BaseTest.java
new file mode 100644
index 0000000..74a88d2
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/BaseTest.java
@@ -0,0 +1,267 @@
+package com.deepclone.lw.cli.xmlimport.resources;
+
+
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.deepclone.lw.cli.xmlimport.data.resources.BasicResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.NaturalResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.ResourceParameter;
+import com.deepclone.lw.cli.xmlimport.data.resources.Resources;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.XStreamException;
+
+
+
+/**
+ * Base class for testing resource importation
+ * 
+ * <p>
+ * This class is used as a parent for tests of the resource import structures. It includes the code
+ * used to actually create these resources, as this can normally only be done through XStream.
+ * 
+ * @author <a href="mailto:tim@mitheren.com">T. Rosser</a>
+ * 
+ */
+
+abstract public class BaseTest
+{
+	/**
+	 * Escape &amp;, &lt; and &gt; in XML strings
+	 * 
+	 * @param string
+	 *            the string to escape
+	 * 
+	 * @return the escaped string
+	 */
+	private String xmlString( String string )
+	{
+		return string.replace( "&" , "&amp;" ).replace( "<" , "&lt;" ).replace( ">" , "&gt;" );
+	}
+
+
+	/**
+	 * Escape &amp;, &lt; and &gt;, ' and " in XML strings
+	 * 
+	 * @param string
+	 *            the string to escape
+	 * 
+	 * @return the escaped string
+	 */
+	private String quoteString( String string )
+	{
+		return "\"" + this.xmlString( string ).replace( "\"" , "&quot;" ).replace( "'" , "&apos;" ) + "\"";
+	}
+
+
+	/**
+	 * Create a {@link BasicResource} definition.
+	 * 
+	 * <p>
+	 * Generate the XML code that corresponds to a basic resource definition.
+	 * 
+	 * @param name
+	 *            The resources name, or <code>null</code> to omit the name.
+	 * @param description
+	 *            The resources description, or <code>null</code> to omit the description.
+	 * @param weight
+	 *            The resources weight, or <code>null</code> to omit the weight.
+	 * @param category
+	 *            The resources category, or <code>null</code> to omit the category.
+	 * 
+	 * @return the XML code corresponding to the resource definition.
+	 */
+	protected String createBasicResourceDefinition( String name , String description , String weight , String category )
+	{
+		StringBuilder str = new StringBuilder( );
+
+		str.append( "<basic-resource" );
+
+		if ( name != null ) {
+			str.append( " name=" ).append( this.quoteString( name ) );
+		}
+		if ( description != null ) {
+			str.append( " description=" ).append( this.quoteString( description ) );
+		}
+		if ( weight != null ) {
+			str.append( " weight=" ).append( this.quoteString( weight ) );
+		}
+		if ( category != null ) {
+			str.append( " category=" ).append( this.quoteString( category ) );
+		}
+
+		str.append( " />" );
+
+		return str.toString( );
+	}
+
+
+	/**
+	 * Create the a {@link NaturalResource} definition.
+	 * 
+	 * <p>
+	 * Create the XML code that corresponds to a natural resource definition.
+	 * 
+	 * @param name
+	 *            The resources name, or <code>null</code> to omit the name.
+	 * @param description
+	 *            The resources description, or <code>null</code> to omit the description.
+	 * @param weight
+	 *            The resources weight, or <code>null</code> to omit the weight.
+	 * @param category
+	 *            The resources category, or <code>null</code> to omit the category.
+	 * @param probability
+	 *            The resources presence-probability, or <code>null</code> to omit the probability.
+	 * @param quantity
+	 *            The XML code representing the {@link ResourceParameter} giving the resources
+	 *            quantity average and deviation or <code>null</code> to omit the quantity.
+	 * @param difficulty
+	 *            The XML code representing the {@link ResourceParameter} giving the resources
+	 *            difficulty average and deviation or <code>null</code> to omit the difficulty.
+	 * @param recovery
+	 *            The XML code representing the {@link ResourceParameter} giving the resources
+	 *            recovery average and deviation or <code>null</code> to omit the recovery.
+	 * 
+	 * @return the XML code corresponding to the natural resource definition
+	 */
+	protected String createNaturalResourceDefinition( String name , String description , String weight ,
+			String category , String probability , String quantity , String difficulty , String recovery )
+	{
+		StringBuilder str = new StringBuilder( );
+
+		str.append( "<natural-resource" );
+
+		if ( name != null ) {
+			str.append( " name=" ).append( this.quoteString( name ) );
+		}
+		if ( description != null ) {
+			str.append( " description=" ).append( this.quoteString( description ) );
+		}
+		if ( weight != null ) {
+			str.append( " weight=" ).append( this.quoteString( weight ) );
+		}
+		if ( category != null ) {
+			str.append( " category=" ).append( this.quoteString( category ) );
+		}
+		if ( probability != null ) {
+			str.append( " presence-probability=" ).append( this.quoteString( probability ) );
+		}
+
+		str.append( ">" );
+
+		if ( quantity != null ) {
+			str.append( quantity );
+		}
+		if ( difficulty != null ) {
+			str.append( difficulty );
+		}
+		if ( recovery != null ) {
+			str.append( recovery );
+		}
+
+		str.append( "</natural-resource>" );
+
+		return str.toString( );
+	}
+
+
+	/**
+	 * Create a {@link ResourceParameter} definition
+	 * 
+	 * <p>
+	 * Generates the XML code the corresponds to a resource parameter definition.
+	 * 
+	 * @param type
+	 *            Gives the type of resource parameter to create (quantity/difficulty/recovery)
+	 * @param average
+	 *            The parameters average.
+	 * @param deviation
+	 *            The parameters deviation.
+	 * 
+	 * @return the XML code corresponding to the resource parameter definition
+	 */
+	protected String createResourceParameterDefinition( String type , String average , String deviation )
+	{
+		StringBuilder str = new StringBuilder( );
+
+		str.append( "<" );
+
+		if ( type != null ) {
+			str.append( type );
+		}
+		if ( average != null ) {
+			str.append( " average=" ).append( this.quoteString( average ) );
+		}
+		if ( deviation != null ) {
+			str.append( " deviation=" ).append( this.quoteString( deviation ) );
+		}
+
+		str.append( " />" );
+
+		return str.toString( );
+	}
+
+
+	/**
+	 * Creates the XML code for a top-level resource definition element
+	 * 
+	 * @param resources
+	 *            XML definitions of resources
+	 * 
+	 * @return the top-level elements XML code
+	 */
+	protected String createTopLevel( String... resources )
+	{
+		StringBuilder str = new StringBuilder( );
+		str.append( "<lw-resources>" );
+		for ( String resource : resources ) {
+			str.append( resource );
+		}
+		str.append( "</lw-resources>" );
+		return str.toString( );
+	}
+
+
+	/**
+	 * Create the necessary XStream instance
+	 * 
+	 * <p>
+	 * Initialise an XStream instance and set it up so it can process resource data definitions.
+	 * Unlike {@link ResourceLoader}, this version also registers multiple aliases for
+	 * ResourceParameter. This is because ResourceParameter is usually only used inside a
+	 * NaturalResource, but when testing may need to be created independently.
+	 */
+	private XStream createXStreamInstance( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( Resources.class );
+		xstream.processAnnotations( NaturalResource.class );
+		xstream.alias( "quantity" , ResourceParameter.class );
+		xstream.alias( "difficulty" , ResourceParameter.class );
+		xstream.alias( "recovery" , ResourceParameter.class );
+		xstream.alias( "test" , ResourceParameter.class ); // Just for unit tests
+		xstream.alias( "lw-resources" , Resources.class );
+
+		return xstream;
+	}
+
+
+	/**
+	 * Create a resource object from its XML code
+	 * 
+	 * @param xml
+	 *            the XML code
+	 * @param cls
+	 *            the class of the object
+	 * 
+	 * @return the object that was created from the code
+	 * 
+	 * @throws ClassCastException
+	 *             if the code corresponds to some other type of object
+	 * @throws XStreamException
+	 *             if some error occurred while deserialising the object
+	 */
+	protected < T extends ImportableData > T createObject( String xml , Class< T > cls )
+			throws ClassCastException , XStreamException
+	{
+		return cls.cast( this.createXStreamInstance( ).fromXML( xml ) );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestBasicResource.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestBasicResource.java
new file mode 100644
index 0000000..a6bb6b3
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestBasicResource.java
@@ -0,0 +1,203 @@
+package com.deepclone.lw.cli.xmlimport.resources;
+
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.resources.BasicResource;
+import com.thoughtworks.xstream.converters.ConversionException;
+
+
+
+/**
+ * Unit tests for {@link BasicResource}.
+ * 
+ * @author <a href="mailto:tim@mitheren.com>T. Rosser</a>
+ * 
+ */
+public class TestBasicResource
+		extends BaseTest
+{
+	/** Test loading a valid basic resource */
+	@Test
+	public void testLoadBasicResourceOK( )
+	{
+		String resource = this.createBasicResourceDefinition( "test" , "test" , "0" , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		assertEquals( "test" , instance.getName( ) );
+		assertEquals( "test" , instance.getDescription( ) );
+		assertEquals( new Integer( 0 ) , instance.getWeight( ) );
+		assertEquals( "test" , instance.getCategory( ) );
+	}
+
+
+	/** Test loading a basic resource without name */
+	@Test
+	public void testLoadResourceNoName( )
+	{
+		String resource = this.createBasicResourceDefinition( null , "test" , "0" , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		assertEquals( null , instance.getName( ) );
+		assertEquals( "test" , instance.getDescription( ) );
+		assertEquals( new Integer( 0 ) , instance.getWeight( ) );
+		assertEquals( "test" , instance.getCategory( ) );
+	}
+
+
+	/** Test loading a basic resource without description */
+	@Test
+	public void testLoadResourceNoDescription( )
+	{
+		String resource = this.createBasicResourceDefinition( "test" , null , "0" , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		assertEquals( "test" , instance.getName( ) );
+		assertEquals( null , instance.getDescription( ) );
+		assertEquals( new Integer( 0 ) , instance.getWeight( ) );
+		assertEquals( "test" , instance.getCategory( ) );
+	}
+
+
+	/** Test loading a basic resource without weight */
+	@Test
+	public void testLoadBasicResourceNoWeight( )
+	{
+		String resource = this.createBasicResourceDefinition( "test" , "test" , null , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		assertEquals( "test" , instance.getName( ) );
+		assertEquals( "test" , instance.getDescription( ) );
+		assertEquals( null , instance.getWeight( ) );
+		assertEquals( "test" , instance.getCategory( ) );
+	}
+
+
+	/** Test loading a basic resource without category */
+	@Test
+	public void testLoadBasicResourceNoCategory( )
+	{
+		String resource = this.createBasicResourceDefinition( "test" , "test" , "0" , null );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		assertEquals( "test" , instance.getName( ) );
+		assertEquals( "test" , instance.getDescription( ) );
+		assertEquals( new Integer( 0 ) , instance.getWeight( ) );
+		assertEquals( null , instance.getCategory( ) );
+	}
+
+
+	/**
+	 * Test validating a valid basic resource
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test
+	public void testValidateBasicResourceOK( )
+			throws DataImportException
+	{
+		String resource = this.createBasicResourceDefinition( "test" , "test" , "0" , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a basic resource without name
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateResourceNoName( )
+			throws DataImportException
+	{
+		String resource = this.createBasicResourceDefinition( null , "test" , "0" , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a basic resource without description
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateResourceNoDescription( )
+			throws DataImportException
+	{
+		String resource = this.createBasicResourceDefinition( "test" , null , "0" , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a basic resource without weight
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateBasicResourceNoWeight( )
+			throws DataImportException
+	{
+		String resource = this.createBasicResourceDefinition( "test" , "test" , null , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a basic resource without category
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test
+	public void testValidateBasicResourceNoCategory( )
+			throws DataImportException
+	{
+		String resource = this.createBasicResourceDefinition( "test" , "test" , "0" , null );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a basic resource with a non-numeric string weight
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 * @throws ConversionException
+	 *             if XStream cannot convert the text to an object
+	 */
+	@Test( expected = ConversionException.class )
+	public void testValidateBasicResourceStringWeight( )
+			throws DataImportException , ConversionException
+	{
+		String resource = this.createBasicResourceDefinition( "test" , "test" , "test" , "test" );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a basic resource with a non-integer weight
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 * @throws ConversionException
+	 *             if XStream cannot convert the text to an object
+	 */
+	@Test( expected = ConversionException.class )
+	public void testValidateBasicResourceStringFloat( )
+			throws DataImportException , ConversionException
+	{
+		String resource = this.createBasicResourceDefinition( "test" , "test" , "1.2" , null );
+		BasicResource instance = this.createObject( resource , BasicResource.class );
+		instance.verifyData( );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestNaturalResource.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestNaturalResource.java
new file mode 100644
index 0000000..e46ed9c
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestNaturalResource.java
@@ -0,0 +1,329 @@
+package com.deepclone.lw.cli.xmlimport.resources;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.resources.NaturalResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.ResourceParameter;
+import com.thoughtworks.xstream.converters.ConversionException;
+
+
+
+/**
+ * Unit tests for {@link NaturalResource}.
+ * 
+ * @author <a href="mailto:tim@mitheren.com>T. Rosser</a>
+ * 
+ */
+public class TestNaturalResource
+		extends BaseTest
+{
+	/** The XML definition for a {@link ResourceParameter} representing a resources quantity */
+	private String quantity;
+
+	/** The XML definition for a {@link ResourceParameter} representing a resources quantity */
+	private String difficulty;
+
+	/** The XML definition for a {@link ResourceParameter} representing a resources quantity */
+	private String recovery;
+
+
+	/**
+	 * Create valid definitions for the ResourceParameters to be used in the NaturalResource tests
+	 */
+	@Before
+	public void setup( )
+	{
+		this.quantity = this.createResourceParameterDefinition( "quantity" , "0" , "0" );
+		this.difficulty = this.createResourceParameterDefinition( "difficulty" , "0" , "0" );
+		this.recovery = this.createResourceParameterDefinition( "recovery" , "0" , "0" );
+	}
+
+
+	/** Test loading a valid natural resource */
+	@Test
+	public void testLoadNaturalResourceOK( )
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "0" , this.quantity ,
+				this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		assertEquals( new Double( 0 ) , instance.getPresenceProbability( ) );
+		if ( instance.getQuantity( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getDeviation( ) );
+		} else {
+			fail( "Quantity is null" );
+		}
+		if ( instance.getDifficulty( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getDeviation( ) );
+		} else {
+			fail( "Difficulty is null" );
+		}
+		if ( instance.getRecovery( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getDeviation( ) );
+		} else {
+			fail( "Recovery is null" );
+		}
+
+	}
+
+
+	/** Test loading a valid natural resource with presence-probability giving a Double value */
+	@Test
+	public void testLoadNaturalResourceWithDoubleOK( )
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "1.2" , this.quantity ,
+				this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		assertEquals( new Double( 1.2 ) , instance.getPresenceProbability( ) );
+		if ( instance.getQuantity( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getDeviation( ) );
+		} else {
+			fail( "Quantity is null" );
+		}
+		if ( instance.getDifficulty( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getDeviation( ) );
+		} else {
+			fail( "Difficulty is null" );
+		}
+		if ( instance.getRecovery( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getDeviation( ) );
+		} else {
+			fail( "Recovery is null" );
+		}
+	}
+
+
+	/** Test loading a natural resource with no presence-probability */
+	@Test
+	public void testLoadNaturalResourceNoProbability( )
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , null , this.quantity ,
+				this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		assertEquals( null , instance.getPresenceProbability( ) );
+		if ( instance.getQuantity( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getDeviation( ) );
+		} else {
+			fail( "Quantity is null" );
+		}
+		if ( instance.getDifficulty( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getDeviation( ) );
+		} else {
+			fail( "Difficulty is null" );
+		}
+		if ( instance.getRecovery( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getDeviation( ) );
+		} else {
+			fail( "Recovery is null" );
+		}
+	}
+
+
+	/** Test loading a natural resource with no quantity */
+	@Test
+	public void testLoadNaturalResourceNoQuantity( )
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "0" , null ,
+				this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		assertEquals( new Double( 0 ) , instance.getPresenceProbability( ) );
+		assertEquals( null , instance.getQuantity( ) );
+		if ( instance.getDifficulty( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getDeviation( ) );
+		} else {
+			fail( "Difficulty is null" );
+		}
+		if ( instance.getRecovery( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getDeviation( ) );
+		} else {
+			fail( "Recovery is null" );
+		}
+	}
+
+
+	/** Test loading a natural resource with no difficulty */
+	@Test
+	public void testLoadNaturalResourceNoDifficulty( )
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "0" , this.quantity ,
+				null , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		assertEquals( new Double( 0 ) , instance.getPresenceProbability( ) );
+
+		if ( instance.getQuantity( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getDeviation( ) );
+		} else {
+			fail( "Quantity is null" );
+		}
+		assertEquals( null , instance.getDifficulty( ) );
+		if ( instance.getRecovery( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getRecovery( ).getDeviation( ) );
+		} else {
+			fail( "Recovery is null" );
+		}
+	}
+
+
+	/** Test loading a natural resource with no recovery */
+	@Test
+	public void testLoadNaturalResourceNoRecovery( )
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "0" , this.quantity ,
+				this.difficulty , null );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		assertEquals( new Double( 0 ) , instance.getPresenceProbability( ) );
+		if ( instance.getQuantity( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getQuantity( ).getDeviation( ) );
+		} else {
+			fail( "Quantity is null" );
+		}
+		if ( instance.getDifficulty( ) != null ) {
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getAverage( ) );
+			assertEquals( new Double( 0 ) , instance.getDifficulty( ).getDeviation( ) );
+		} else {
+			fail( "Difficulty is null" );
+		}
+		assertEquals( null , instance.getRecovery( ) );
+	}
+
+
+	/**
+	 * Test validating a valid natural resource
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test
+	public void testValidateNaturalResourceOK( )
+			throws DataImportException
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "0" , this.quantity ,
+				this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a valid natural resource with presence-probability containing a Double value
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test
+	public void testValidateNaturalResourceWithDoubleOK( )
+			throws DataImportException
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "1.2" , this.quantity ,
+				this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a natural resource with no presence-probability
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNaturalResourceNoProbability( )
+			throws DataImportException
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , null , this.quantity ,
+				this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a natural resource with no quantity
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNaturalResourceNoQuantity( )
+			throws DataImportException
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "0" , null ,
+				this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a natural resource with no difficulty
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNaturalResourceNoDifficulty( )
+			throws DataImportException
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "0" , this.quantity ,
+				null , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a natural resource with no recovery
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateNaturalResourceNoRecovery( )
+			throws DataImportException
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "0" , this.quantity ,
+				this.difficulty , null );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		instance.verifyData( );
+	}
+
+
+	/**
+	 * Test validating a natural resource with a non-numerical string as presence-probability
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 * @throws ConversionException
+	 *             if XStream cannot convert the text into an object
+	 */
+	@Test( expected = ConversionException.class )
+	public void testValidateNaturalResourceStringProbability( )
+			throws DataImportException , ConversionException
+	{
+		String resource = this.createNaturalResourceDefinition( "test" , "test" , "0" , "test" , "test" ,
+				this.quantity , this.difficulty , this.recovery );
+		NaturalResource instance = this.createObject( resource , NaturalResource.class );
+		instance.verifyData( );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResourceParameter.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResourceParameter.java
new file mode 100644
index 0000000..7ff4d6d
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResourceParameter.java
@@ -0,0 +1,192 @@
+package com.deepclone.lw.cli.xmlimport.resources;
+
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.resources.ResourceParameter;
+import com.thoughtworks.xstream.converters.ConversionException;
+
+
+
+/**
+ * Unit tests for {@link ResourceParameter}.
+ * 
+ * @author <a href="mailto:tim@mitheren.com>T. Rosser</a>
+ * 
+ */
+public class TestResourceParameter
+		extends BaseTest
+{
+	/** Test loading a valid resource parameter */
+	@Test
+	public void testLoadResourceParameterOK( )
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "0" , "0" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		assertEquals( new Double( 0 ) , instance.getAverage( ) );
+		assertEquals( new Double( 0 ) , instance.getDeviation( ) );
+	}
+
+
+	/** Test loading a valid resource parameter with average containing a Double value */
+	@Test
+	public void testLoadResourceParameterDoubleAverageOK( )
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "1.2" , "0" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		assertEquals( new Double( 1.2 ) , instance.getAverage( ) );
+		assertEquals( new Double( 0 ) , instance.getDeviation( ) );
+	}
+
+
+	/** Test loading a valid resource parameter with deviation containing a Double value */
+	@Test
+	public void testLoadResourceParameterDoubleDeviationOK( )
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "0" , "1.2" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		assertEquals( new Double( 0 ) , instance.getAverage( ) );
+		assertEquals( new Double( 1.2 ) , instance.getDeviation( ) );
+	}
+
+
+	/** Test loading a valid resource parameter with no average */
+	@Test
+	public void testLoadResourceParameterNoAverage( )
+	{
+		String rp = this.createResourceParameterDefinition( "test" , null , "0" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		assertEquals( null , instance.getAverage( ) );
+		assertEquals( new Double( 0 ) , instance.getDeviation( ) );
+	}
+
+
+	/** Test loading a valid resource parameter with no deviation */
+	@Test
+	public void testLoadResourceParameterNoDeviation( )
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "0" , null );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		assertEquals( new Double( 0 ) , instance.getAverage( ) );
+		assertEquals( null , instance.getDeviation( ) );
+	}
+
+
+	/**
+	 * Test validating a valid resource parameter
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test
+	public void testValidateResourceParameterOK( )
+			throws DataImportException
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "0" , "0" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		instance.verifyData( "test" );
+	}
+
+
+	/**
+	 * Test validating a valid resource parameter with average containing a Double value
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test
+	public void testValidateResourceParameterDoubleAverageOK( )
+			throws DataImportException
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "1.2" , "0" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		instance.verifyData( "test" );
+	}
+
+
+	/**
+	 * Test validating a valid resource parameter with deviation containing a Double value
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test
+	public void testValidateResourceParameterDoubleDeviationOK( )
+			throws DataImportException
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "0" , "1.2" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		instance.verifyData( "test" );
+	}
+
+
+	/**
+	 * Test validating a resource parameter with no average
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateResourceParameterNoAverage( )
+			throws DataImportException
+	{
+		String rp = this.createResourceParameterDefinition( "test" , null , "0" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		instance.verifyData( "test" );
+	}
+
+
+	/**
+	 * Test validating a resource parameter with no deviation
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateResourceParameterNoDeviation( )
+			throws DataImportException
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "0" , null );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		instance.verifyData( "test" );
+	}
+
+
+	/**
+	 * Test validating a resource parameter with a non-numerical string average
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 * @throws ConversionException
+	 *             if XStream cannot convert the text into an object
+	 */
+	@Test( expected = ConversionException.class )
+	public void testValidateResourceParameterStringAverage( )
+			throws DataImportException , ConversionException
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "test" , "0" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		instance.verifyData( "test" );
+	}
+
+
+	/**
+	 * Test validating a resource parameter with a non-numerical string deviation
+	 * 
+	 * @throws DataImportException
+	 *             if the data is not valid
+	 * @throws ConversionException
+	 *             if XStream cannot convert the text into an object
+	 */
+	@Test( expected = ConversionException.class )
+	public void testValidateResourceParameterStringDeviation( )
+			throws DataImportException , ConversionException
+	{
+		String rp = this.createResourceParameterDefinition( "test" , "0" , "test" );
+		ResourceParameter instance = this.createObject( rp , ResourceParameter.class );
+		instance.verifyData( "test" );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResources.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResources.java
new file mode 100644
index 0000000..3a75fc9
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResources.java
@@ -0,0 +1,137 @@
+package com.deepclone.lw.cli.xmlimport.resources;
+
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.resources.BasicResource;
+import com.deepclone.lw.cli.xmlimport.data.resources.Resources;
+
+
+
+/**
+ * Unit tests for {@link Resources}.
+ * 
+ * @author <a href="mailto:tim@mitheren.com>T. Rosser</a>
+ * 
+ */
+public class TestResources
+		extends BaseTest
+{
+	/** The basic resource definition used in most tests */
+	private String basicResourceDefinition;
+
+
+	/** Initialise the basic resource definition used in tests */
+	@Before
+	public void setUp( )
+	{
+		this.basicResourceDefinition = this.createBasicResourceDefinition( "test" , "test" , "0" , "test" );
+	}
+
+
+	/**
+	 * Try loading a valid top-level resource definition and make sure it's contents are correct
+	 * 
+	 * @throws DataImportException
+	 *             if the resource definition check fails
+	 */
+	@Test
+	public void testLoadValidResources( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( this.basicResourceDefinition );
+		Resources resources = this.createObject( xml , Resources.class );
+
+		int count = 0;
+		for ( BasicResource resource : resources ) {
+			resource.verifyData( );
+			count++;
+		}
+		assertEquals( 1 , count );
+	}
+
+
+	/**
+	 * Try loading an empty top-level element
+	 * 
+	 * @throws NullPointerException
+	 *             if the resource definition check fails
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testLoadEmpty( )
+			throws NullPointerException
+	{
+		String xml = this.createTopLevel( );
+		Resources resources = this.createObject( xml , Resources.class );
+		resources.iterator( );
+	}
+
+
+	/**
+	 * Test validating a valid top-level resource definition
+	 * 
+	 * @throws DataImportException
+	 *             if the resource definition check fails
+	 */
+	@Test
+	public void testValidateOK( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( this.basicResourceDefinition );
+		Resources resources = this.createObject( xml , Resources.class );
+		resources.verifyData( );
+	}
+
+
+	/**
+	 * Try rejecting a top-level element that does not contain any resource definitions
+	 * 
+	 * @throws DataImportException
+	 *             if the resource definition check fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateEmpty( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( );
+		Resources resources = this.createObject( xml , Resources.class );
+		resources.verifyData( );
+	}
+
+
+	/**
+	 * Try rejecting a top-level element that contains an invalid resource definition
+	 * 
+	 * @throws DataImportException
+	 *             if the resource definition check fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateBadDefinition( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( this.createBasicResourceDefinition( null , null , null , null ) );
+		Resources resources = this.createObject( xml , Resources.class );
+		resources.verifyData( );
+	}
+
+
+	/**
+	 * Try rejecting a top-level element that contains two valid but duplicate resource definitions
+	 * 
+	 * @throws DataImportException
+	 *             if the resource definition check fails
+	 */
+	@Test( expected = DataImportException.class )
+	public void testValidateDuplicateDefinition( )
+			throws DataImportException
+	{
+		String xml = this.createTopLevel( this.basicResourceDefinition , this.basicResourceDefinition );
+		Resources resources = this.createObject( xml , Resources.class );
+		resources.verifyData( );
+	}
+
+}
\ No newline at end of file

From 775426347a822282ca8106e131e2dd7250aaf8e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 24 Jan 2012 14:49:23 +0100
Subject: [PATCH 32/94] Resource definitions

* Added a few resource definitions and the corresponding strings. While
the descriptions are blank, and resource definitions themselves are
incomplete (relations to e.g. technologies are missing), this should
allow easier testing of the rest of the resources system.
---
 legacyworlds-server-main/data/i18n-text.xml | 99 +++++++++++++++++++++
 legacyworlds-server-main/data/resources.xml | 43 +++++++--
 2 files changed, 134 insertions(+), 8 deletions(-)

diff --git a/legacyworlds-server-main/data/i18n-text.xml b/legacyworlds-server-main/data/i18n-text.xml
index aaf18ca..0e54e2e 100644
--- a/legacyworlds-server-main/data/i18n-text.xml
+++ b/legacyworlds-server-main/data/i18n-text.xml
@@ -519,6 +519,56 @@ It was disbanded.</value>
 		<inline-string id="imBugReportUpdateEmpire">
 			<value>Bug report ${bug} has been updated by the owner of empire ${submitter}.</value>
 		</inline-string>
+		
+		
+		<!-- Resource categories -->
+		<inline-string id="rcMinerals">
+			<value>Mineral resources</value>
+		</inline-string>
+		
+		<!-- Resource names and descriptions -->
+
+		<inline-string id="rnMoney">
+			<value>Money</value>
+		</inline-string>
+		<inline-string id="rdMoney">
+			<value>(Get Doug or Rendesh to write this)</value>
+		</inline-string>
+
+		<inline-string id="rnTitanium">
+			<value>Titanium</value>
+		</inline-string>
+		<inline-string id="rdTitanium">
+			<value>(Get Doug or Rendesh to write this)</value>
+		</inline-string>
+
+		<inline-string id="rnCopper">
+			<value>Copper</value>
+		</inline-string>
+		<inline-string id="rdCopper">
+			<value>(Get Doug or Rendesh to write this)</value>
+		</inline-string>
+
+		<inline-string id="rnStrentium">
+			<value>Strentium</value>
+		</inline-string>
+		<inline-string id="rdStrentium">
+			<value>(Get Doug or Rendesh to write this)</value>
+		</inline-string>
+
+		<inline-string id="rnDororhium">
+			<value>Dororhium</value>
+		</inline-string>
+		<inline-string id="rdDororhium">
+			<value>(Get Doug or Rendesh to write this)</value>
+		</inline-string>
+
+		<inline-string id="rnNothentium">
+			<value>Nothentium</value>
+		</inline-string>
+		<inline-string id="rdNothentium">
+			<value>(Get Doug or Rendesh to write this)</value>
+		</inline-string>
 
 	</language>
 
@@ -1039,6 +1089,55 @@ Elle a été dissoute.</value>
 		<inline-string id="imBugReportUpdateEmpire">
 			<value>Le rapport de bug ${bug} a été mis à jour par le propriétaire de l'empire ${submitter}.</value>
 		</inline-string>
+		
+		<!-- Resource categories -->
+		<inline-string id="rcMinerals">
+			<value>Minéraux</value>
+		</inline-string>
+		
+		<!-- Resource names and descriptions -->
+
+		<inline-string id="rnMoney">
+			<value>Monaie</value>
+		</inline-string>
+		<inline-string id="rdMoney">
+			<value>(À traduire quand il y aura une version anglaise)</value>
+		</inline-string>
+
+		<inline-string id="rnTitanium">
+			<value>Titane</value>
+		</inline-string>
+		<inline-string id="rdTitanium">
+			<value>(À traduire quand il y aura une version anglaise)</value>
+		</inline-string>
+
+		<inline-string id="rnCopper">
+			<value>Cuivre</value>
+		</inline-string>
+		<inline-string id="rdCopper">
+			<value>(À traduire quand il y aura une version anglaise)</value>
+		</inline-string>
+
+		<inline-string id="rnStrentium">
+			<value>Strentium</value>
+		</inline-string>
+		<inline-string id="rdStrentium">
+			<value>(À traduire quand il y aura une version anglaise)</value>
+		</inline-string>
+
+		<inline-string id="rnDororhium">
+			<value>Dororhium</value>
+		</inline-string>
+		<inline-string id="rdDororhium">
+			<value>(À traduire quand il y aura une version anglaise)</value>
+		</inline-string>
+
+		<inline-string id="rnNothentium">
+			<value>Nothentium</value>
+		</inline-string>
+		<inline-string id="rdNothentium">
+			<value>(À traduire quand il y aura une version anglaise)</value>
+		</inline-string>
 
 	</language>
 
diff --git a/legacyworlds-server-main/data/resources.xml b/legacyworlds-server-main/data/resources.xml
index 77a3e62..6f4646e 100644
--- a/legacyworlds-server-main/data/resources.xml
+++ b/legacyworlds-server-main/data/resources.xml
@@ -1,17 +1,44 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <lw-resources xmlns="http://www.deepclone.com/lw/b6/m2/resources"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/resources
-resources.xsd">
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/resources resources.xsd">
 
-	<basic-resource name="money" description="moneyDescription"
-		weight="0" /> <!-- This could have a category="" as well -->
+	<basic-resource name="rnMoney" description="rdMoney"
+		weight="1" />
 
-	<natural-resource name="titanium" description="titaniumDescription"
-		category="minerals" weight="1" presence-probability="0.8">
-		<quantity average="5000" deviation="1500" />
+	<natural-resource name="rnTitanium" description="rdTitanium"
+		category="rcMinerals" weight="1" presence-probability="0.5">
+		<quantity average="50000" deviation="5000" />
 		<difficulty average="0.1" deviation="0.05" />
-		<recovery average="0.4" deviation="0.05" />
+		<recovery average="0.2" deviation="0.05" />
+	</natural-resource>
+
+	<natural-resource name="rnCopper" description="rdCopper"
+			category="rcMinerals" weight="2" presence-probability="0.6">
+		<quantity average="75000" deviation="7500" />
+		<difficulty average="0.15" deviation="0.075" />
+		<recovery average="0.2" deviation="0.05" />
+	</natural-resource>
+
+	<natural-resource name="rnStrentium" description="rdStrentium"
+			category="rcMinerals" weight="3" presence-probability="0.3">
+		<quantity average="30000" deviation="5000" />
+		<difficulty average="0.4" deviation="0.1" />
+		<recovery average="0.15" deviation="0.05" />
+	</natural-resource>
+
+	<natural-resource name="rnDororhium" description="rdDororhium"
+			category="rcMinerals" weight="4" presence-probability="0.1">
+		<quantity average="15000" deviation="2500" />
+		<difficulty average="0.7" deviation="0.15" />
+		<recovery average="0.15" deviation="0.05" />
+	</natural-resource>
+
+	<natural-resource name="rnNothentium" description="rdNothentium"
+			category="rcMinerals" weight="5" presence-probability="0.05">
+		<quantity average="10000" deviation="2500" />
+		<difficulty average="0.85" deviation="0.15" />
+		<recovery average="0.1" deviation="0.05" />
 	</natural-resource>
 
 </lw-resources>
\ No newline at end of file

From 4e27875d87bdfc3ff3b509bd4e6abb34e87e1bd8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 24 Jan 2012 14:51:51 +0100
Subject: [PATCH 33/94] Maven project & PostgreSQL fix

* Made the PostgreSQL library a compile-time dependency (it used to be
runtime only as it was used as the JDBC driver, however it is now used
directly to access PostgreSQL-specific types; this does not matter, as
the DB code is already 100% specific to PostgreSQL)
---
 legacyworlds-server-main/pom.xml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/legacyworlds-server-main/pom.xml b/legacyworlds-server-main/pom.xml
index e947132..30a4788 100644
--- a/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server-main/pom.xml
@@ -63,7 +63,6 @@
 		<dependency>
 			<groupId>postgresql</groupId>
 			<artifactId>postgresql</artifactId>
-			<scope>runtime</scope>
 		</dependency>
 
 	</dependencies>

From a4bce68b17dea6f222ba7f07bfd6132bd781e404 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 24 Jan 2012 16:08:30 +0100
Subject: [PATCH 34/94] Client/server communications overview

* Added a text file which describes the current client/server protocol,
and the implementation of both the protocol itself and the server-level
support. It also includes a discussion of what is wrong with the current
implementation.
---
 legacyworlds/doc/communications.txt | 134 ++++++++++++++++++++++++++++
 1 file changed, 134 insertions(+)
 create mode 100644 legacyworlds/doc/communications.txt

diff --git a/legacyworlds/doc/communications.txt b/legacyworlds/doc/communications.txt
new file mode 100644
index 0000000..b295a15
--- /dev/null
+++ b/legacyworlds/doc/communications.txt
@@ -0,0 +1,134 @@
+Communications between the server and its clients
+==================================================
+
+This file contains a few notes about the implementation of the client/server
+system, as well as a (purely speculative) section about changes that ought to
+be made.
+
+
+Definitions
+------------
+
+The Legacy Worlds "server" has nothing to do with the web interfaces. It is a
+standalone application which handles most aspects of the game itself, but does
+not provide any kind of human-readable interface.
+
+Legay Worlds "clients", on the other hand, are applications (whether they are
+standalone or depend on some container is a detail) which connect to the
+server and provide either some user interface or an intermediary layer for
+an user interface application.
+
+At this time, the two clients in existence are:
+ -> the main web site,
+ -> the administration web site.
+ 
+This architecture was designed to keep any access to the game completely
+separate from the game's logic. The only exception to that principle is the
+email notifications system (see discussion).
+
+
+Protocol implementation
+------------------------
+
+All client/server communications go through a RMI interface, SessionAccessor.
+This interface can be used by clients to initiate, authenticate and terminate
+sessions and to execute commands on the server. The general type of a session
+(i.e. whether it is an administrative session, a game session, or an
+"external" session which serves for user registration) is determined when the
+session is created.
+
+Commands are classes that extend the Command abstract class. They carry all
+the information required for their specific purpose.
+
+In response to a Command, the server will reply with either an exception or
+a response. The following exceptions can be thrown by the server:
+
+ -> SessionInternalException means that some internal error occurred while
+    manipulating the session or executing the command. It can in theory be
+    thrown by all parts of the session access interface, but shouldn't
+    occur too often in practice, as it indicates a bug or server problem.
+
+ -> SessionIdentifierException means that the session identifier sent by
+    the client doesn't match any known session. This may indicate a timeout
+    on the client's part.
+
+ -> SessionStateException is thrown when the client tries to re-authenticate
+    a session or to execute a command before the session has been
+    authenticated.
+
+ -> SessionCommandException is thrown when the command that was sent is not
+    supported by the server.
+
+When everything is fine, a command's execution will cause the server to return
+an object of some class that inherits the CommandResponse class. If no data is
+to be transmitted in response to the command, the response may be a
+NullResponse instance.
+
+
+Server implementation
+----------------------
+
+The server supports various types of session. Each type of session may have
+different parameters (for example, its timeout) or behaviours (for example,
+the way the session's authentication request is verified). In addition, each
+type of session will implement a different set of commands.
+
+The internals of the session and command routing system are somewhat outside
+the scope of this document. The various classes which implement that system
+can be found in com.deepclone.lw.beans.user.abst for the most basic elements,
+while the com.deepclone.lw.beans.user.admin,
+com.deepclone.lw.beans.user.player and com.deepclone.lw.beans.user.ext
+packages all contain parts specific to session types.
+
+Actual command handling is defined through classes which implement the
+AutowiredCommandDelegate interface.
+ -> The getCommandHandler() method must return the class which defines the
+    type of session in which the command handler is to be included. For
+    example it will return GameSubTypeBean.class for game session commands or
+    AdminCommandsBean.class for administration commands.
+ -> The getType() method must return the class of commands handled. For
+    example, a handler for commands of the WhateverCommand class would have
+    this method returning WhateverCommand.class
+ -> Finally, the execute() method implements the command itself.
+
+All command handlers, once defined, must be listed to one of the Spring XML
+configuration files in the legacyworlds-server-beans-user project's resources.
+
+
+Discussion
+-----------
+
+While the current implementation is successful in that it defines a clear line
+between the game and whatever is used to display it and access it, it has a
+few drawbacks.
+
+Firstly, the use of RMI associated with dozens of Command / CommandResponse
+classes, while easy to use, is definitely very heavy, specific to Java and
+causes a proliferation of mostly-useless classes.
+
+The protocol's weight is not too problematic at this time, since all
+communications occur either on the same system or between virtual machines
+running on the same system. However, the unbelievable amount of classes
+makes the implementation of new commands or the modification of existing
+commands somewhat tedious. Finally, the Java specificity would prevent the
+implementation of clients connecting directly to the server in any other
+language.
+
+However the main problem with this implementation lies elsewhere. As a matter
+of fact, communications between client and server only go one way: the clients
+may request the execution of commands, but the server cannot send the clients
+data independently. It is not possible to "poll" the server either: sessions
+are exclusive and can only be used to execute one command at a time, so having
+one session running a blocking command is not a possibility. The only way a
+client application could request updates would be by running a command every
+few seconds. This is not a problem at all with the current clients, as they
+are based on user-triggered HTTP requests.
+
+However, trying to implement anything more interactive would be difficult. As
+a matter of fact, the email notifications system should *be* a separate
+client, but the current architecture does not support it. 
+
+In the long run, the existing communications protocol should be replaced with
+something more flexible. For example, it is entirely conceivable to replace it
+with a custom TCP- or even UDP-based protocol which could be used by both
+"local" clients (such as the web sites) and remote applications.  
\ No newline at end of file

From b9bc5e038c02d95eae0be0a1c305a9c6238e1b72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Wed, 25 Jan 2012 09:36:46 +0100
Subject: [PATCH 35/94] Additional Maven projects for unit tests

* Changed names of common packages so they are coherent with the rest

* Added new project for tests on common classes (utilities and session)

* Added new project for tests on the web clients
---
 legacyworlds-session/pom.xml                  |  2 +-
 legacyworlds-tests/pom.xml                    | 39 +++++++++++++++
 legacyworlds-tests/src/main/java/.empty       |  0
 legacyworlds-tests/src/main/resources/.empty  |  0
 legacyworlds-tests/src/test/java/.empty       |  0
 legacyworlds-tests/src/test/resources/.empty  |  0
 legacyworlds-utils/pom.xml                    |  2 +-
 legacyworlds-web-tests/pom.xml                | 49 +++++++++++++++++++
 legacyworlds-web-tests/src/main/java/.empty   |  0
 .../src/main/resources/.empty                 |  0
 legacyworlds-web-tests/src/test/java/.empty   |  0
 .../src/test/resources/.empty                 |  0
 legacyworlds-web/pom.xml                      |  3 +-
 legacyworlds/pom.xml                          |  3 +-
 14 files changed, 94 insertions(+), 4 deletions(-)
 create mode 100644 legacyworlds-tests/pom.xml
 create mode 100644 legacyworlds-tests/src/main/java/.empty
 create mode 100644 legacyworlds-tests/src/main/resources/.empty
 create mode 100644 legacyworlds-tests/src/test/java/.empty
 create mode 100644 legacyworlds-tests/src/test/resources/.empty
 create mode 100644 legacyworlds-web-tests/pom.xml
 create mode 100644 legacyworlds-web-tests/src/main/java/.empty
 create mode 100644 legacyworlds-web-tests/src/main/resources/.empty
 create mode 100644 legacyworlds-web-tests/src/test/java/.empty
 create mode 100644 legacyworlds-web-tests/src/test/resources/.empty

diff --git a/legacyworlds-session/pom.xml b/legacyworlds-session/pom.xml
index 7169c86..605acc6 100644
--- a/legacyworlds-session/pom.xml
+++ b/legacyworlds-session/pom.xml
@@ -9,7 +9,7 @@
 	</parent>
 
 	<artifactId>legacyworlds-session</artifactId>
-	<name>Legacy Worlds - Sessions</name>
+	<name>Legacy Worlds - Common packages - Sessions</name>
 	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 	<description>This module contains the definition of sessions used in client-server communications and all related classes and exceptions.</description>
 
diff --git a/legacyworlds-tests/pom.xml b/legacyworlds-tests/pom.xml
new file mode 100644
index 0000000..176f5da
--- /dev/null
+++ b/legacyworlds-tests/pom.xml
@@ -0,0 +1,39 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-tests</artifactId>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Common packages - Tests</name>
+	<description>This package contains all tests for classes in common packages.</description>
+
+	<dependencies>
+		<dependency>
+			<artifactId>legacyworlds-session</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-utils</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/legacyworlds-tests/src/main/java/.empty b/legacyworlds-tests/src/main/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-tests/src/main/resources/.empty b/legacyworlds-tests/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-tests/src/test/java/.empty b/legacyworlds-tests/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-tests/src/test/resources/.empty b/legacyworlds-tests/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-utils/pom.xml b/legacyworlds-utils/pom.xml
index 34b6e3e..574f035 100644
--- a/legacyworlds-utils/pom.xml
+++ b/legacyworlds-utils/pom.xml
@@ -10,7 +10,7 @@
 
 	<artifactId>legacyworlds-utils</artifactId>
 	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
-	<name>Legacy Worlds - Common utilities</name>
+	<name>Legacy Worlds - Common packages - Utilities</name>
 	<description>The classes in this package are used by all parts of the Legacy Worlds code.</description>
 
 	<dependencies>
diff --git a/legacyworlds-web-tests/pom.xml b/legacyworlds-web-tests/pom.xml
new file mode 100644
index 0000000..4f626a6
--- /dev/null
+++ b/legacyworlds-web-tests/pom.xml
@@ -0,0 +1,49 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-web-tests</artifactId>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Web - Tests</name>
+	<description>This package contains all tests for web clients functionalities.</description>
+
+	<dependencies>
+		<dependency>
+			<artifactId>legacyworlds-web-admin</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+			<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			<type>war</type>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-web-beans</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-web-main</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+			<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			<type>war</type>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+
+</project>
\ No newline at end of file
diff --git a/legacyworlds-web-tests/src/main/java/.empty b/legacyworlds-web-tests/src/main/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-tests/src/main/resources/.empty b/legacyworlds-web-tests/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-tests/src/test/java/.empty b/legacyworlds-web-tests/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web-tests/src/test/resources/.empty b/legacyworlds-web-tests/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-web/pom.xml b/legacyworlds-web/pom.xml
index 4f90e51..26517d6 100644
--- a/legacyworlds-web/pom.xml
+++ b/legacyworlds-web/pom.xml
@@ -14,10 +14,11 @@
 	<description>Root module for Legacy Worlds web sites</description>
 
 	<modules>
-		<module>../legacyworlds-web-main</module>
 		<module>../legacyworlds-web-admin</module>
 		<module>../legacyworlds-web-beans</module>
 		<module>../legacyworlds-web-DIST</module>
+		<module>../legacyworlds-web-main</module>
+		<module>../legacyworlds-web-tests</module>
 	</modules>
 
 
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index a9ff122..86d3192 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -348,8 +348,9 @@
 	<modules>
 		<module>../legacyworlds-server</module>
 		<module>../legacyworlds-session</module>
-		<module>../legacyworlds-web</module>
+		<module>../legacyworlds-tests</module>
 		<module>../legacyworlds-utils</module>
+		<module>../legacyworlds-web</module>
 	</modules>
 
 </project>

From 44b6ec1920e553358efc3a17e971ab64d00ada34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 30 Jan 2012 09:18:20 +0100
Subject: [PATCH 36/94] Fixed resource extraction

* Formula was mis-copied from the wiki, the result was multiplied by the
provider's current quantity

* Changed extraction per work unit from 1 to 10
---
 .../deepclone/lw/beans/sys/ConstantsRegistrarBean.java    | 3 +--
 .../db-structure/parts/050-updates/120-planet-mining.sql  | 6 ++++--
 .../120-planet-mining/040-gu-pmc-update-resource.sql      | 8 ++++----
 3 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 598e896..75d3a9c 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -66,8 +66,7 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( "game.resources.weightBase" , "Natural resources" , cDesc , 10.0 , 1.1 ,
 				100.0 ) );
 		cDesc = "Resources extracted per work unit, per (real) day, from a full provider with minimal difficulty.";
-		defs.add( new ConstantDefinition( "game.resources.extraction" , "Natural resources" , cDesc , 1.0 , 1.0 ,
-				true ) );
+		defs.add( new ConstantDefinition( "game.resources.extraction" , "Natural resources" , cDesc , 10.0 , 1.0 , true ) );
 
 		// Happiness
 		String[] hcNames = {
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
index dd16737..b641762 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
@@ -209,7 +209,7 @@ DECLARE
 
 BEGIN
 
-	_extraction := _input.quantity * verse.get_extraction_factor(
+	_extraction := verse.get_extraction_factor(
 		_input.quantity / _input.quantity_max ,
 		_input.difficulty );
 	_allocation := _input.weight / _input.total_weight;
@@ -219,7 +219,9 @@ BEGIN
 		* sys.get_constant( 'game.resources.extraction' )
 		/ 1440.0; -- FIXME: hardcoded!
 
-	RAISE NOTICE 'Extraction % , allocation % , production %' , _extraction , _allocation , _production;
+	PERFORM sys.write_sql_log( 'MiningUpdate' , 'TRACE' , 'Resource #' || _input.resource
+		|| ' @ planet #' || _input.planet || ': extraction ' || _extraction
+		|| ', allocation ' || _allocation || ', production ' || _production );
 	_quantity := _allocation * _production * _extraction;
 	IF _quantity > _input.quantity THEN
 		_quantity := _input.quantity;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
index c8181f2..f25019f 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
@@ -60,12 +60,12 @@ BEGIN;
 	) );
 	
 	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Provider udpated' );
-	SELECT is( resprov_quantity , 999.0::DOUBLE PRECISION )
+	SELECT is( resprov_quantity , 999.999::DOUBLE PRECISION )
 		FROM verse.resource_providers
 		WHERE planet_id = 1 AND resource_name_id = 1;
 	
 	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Planet resources income udpated' );
-	SELECT is( pres_income , 1.0::DOUBLE PRECISION )
+	SELECT is( pres_income , 0.001::DOUBLE PRECISION )
 		FROM verse.planet_resources
 		WHERE planet_id = 1 AND resource_name_id = 1;
 
@@ -78,12 +78,12 @@ BEGIN;
 	) );
 
 	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Bounded extraction quantity (1/2)' );
-	SELECT is( resprov_quantity , 0.0::DOUBLE PRECISION )
+	SELECT is( resprov_quantity , 990.0::DOUBLE PRECISION )
 		FROM verse.resource_providers
 		WHERE planet_id = 1 AND resource_name_id = 1;
 
 	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Bounded extraction quantity (2/2)' );
-	SELECT is( pres_income , 1000.0::DOUBLE PRECISION )
+	SELECT is( pres_income , 10.0::DOUBLE PRECISION )
 		FROM verse.planet_resources
 		WHERE planet_id = 1 AND resource_name_id = 1;
 

From ba6a1e2b416a29c7983b3d9d6202be834808a30a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 31 Jan 2012 12:14:38 +0100
Subject: [PATCH 37/94] SQL unit tests fixes

* Added plan() instead of no_plan() where necessary

* Removed junk from dirty tests system self-check
---
 .../040-functions/025-resources/005-add-resource-records.sql  | 4 ++--
 .../040-functions/045-empire-mining/030-mset-update-apply.sql | 4 ++--
 .../050-updates/120-planet-mining/030-gu-pmc-get-data.sql     | 4 ++--
 .../120-planet-mining/050-process-planet-mining-updates.sql   | 4 ++--
 .../tests/dirty/000-dirty-tests-self-check/run-test.sql       | 4 +---
 5 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
index 58ba524..37103d7 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
@@ -23,7 +23,7 @@ BEGIN;
 	SELECT defs.add_resource_records( _get_string( 'test1' ) );
 	
 	/***** TESTS BEGIN HERE *****/
-	SELECT no_plan( );
+	SELECT plan( 2 );
 
 	SELECT diag_test_name( 'defs.add_resource_records() - Empire record created' );
 	SELECT is( COUNT(*)::INT , 1 )
@@ -36,4 +36,4 @@ BEGIN;
 		WHERE planet_id = 1 AND resource_name_id = _get_string( 'test1' );
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
index 74d49b1..14c8d6d 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
@@ -21,7 +21,7 @@ BEGIN;
 	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 4 );
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT no_plan( );
+	SELECT plan( 8 );
 
 	SELECT diag_test_name( 'emp.mset_update_apply() - Applying valid empire update' );
 	SELECT ok( emp.mset_update_apply( ) );
@@ -90,4 +90,4 @@ BEGIN;
 	);
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
index 7765aa2..026aa34 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
@@ -11,7 +11,7 @@ BEGIN;
 
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT no_plan( );
+	SELECT plan( 9 );
 	
 	SELECT diag_test_name( 'sys.gu_pmc_get_data() - Neutral planet without resource providers not included' );
 	SELECT is_empty( $$
@@ -86,4 +86,4 @@ BEGIN;
 	$$ );
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
index 4c449b6..19da05c 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
@@ -42,7 +42,7 @@ BEGIN;
 	
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT no_plan( );
+	SELECT plan( 2 );
 	
 	SELECT sys.process_planet_mining_updates( 0 );
 
@@ -55,4 +55,4 @@ BEGIN;
 	);
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/run-test.sql b/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/run-test.sql
index 6786ab2..be13190 100644
--- a/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/run-test.sql
+++ b/legacyworlds-server-data/db-structure/tests/dirty/000-dirty-tests-self-check/run-test.sql
@@ -4,9 +4,7 @@
  * Insert an address, it should exist during the main test.
  */
 BEGIN;
-	SELECT no_plan( );
-
-	SELECT pg_advisory_lock( 1 );
+	SELECT plan( 1 );
 
 	SELECT diag_test_name( 'Dirty test system self-check - prepare.sql was executed' );
 	SELECT is( COUNT(*)::INT , 1 )

From 56eddcc4f0f54c632f643ec081f875409f28e939 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Fri, 3 Feb 2012 16:25:03 +0100
Subject: [PATCH 38/94] Game updates improvements

* Added a set of tables which define game updates and their targets.
These definitions replace the old enumerate type. Added a set of
triggers which automatically create specific update tables, insert
missing entries, etc... when game update types are being manipulated.

* Removed manual insertion of game updates from empire creation
function and universe generator.

* Added registration of core update targets (i.e. planets and empires),
updated all existing game update processing functions and added type
registrations

* Created Maven project for game updates control components, moved
existing components from the -simple project, rewritten most of what
they contained, added new components for server-side update batch
processing
---
 .../lw/beans/updates/GameUpdateBean.java      | 138 -----
 .../lw/beans/updates/UpdatesDAOBean.java      |  43 --
 .../resources/configuration/simple-beans.xml  |   2 -
 .../configuration/simple/game-update-bean.xml |   9 -
 legacyworlds-server-beans-updates/pom.xml     |  17 +
 .../src/main/java/.empty                      |   0
 .../updates/GameUpdateProcessorBean.java      | 238 ++++++++
 .../lw/beans/updates/GameUpdateTaskBean.java  |  96 +++
 .../beans/updates/GameUpdateTransaction.java  |  89 +++
 .../updates/ServerProcessorRegistry.java      |  33 +
 .../updates/ServerProcessorRegistryBean.java  |  68 +++
 .../updates/UnsupportedUpdateException.java   |  29 +
 .../lw/beans/updates/UpdatesDAOBean.java      |  69 +++
 .../src/main/resources/.empty                 |   0
 .../src/main/resources/configuration/game.xml |   6 +-
 .../resources/configuration/game/updates.xml  |  13 +
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 legacyworlds-server-beans/pom.xml             |  11 +-
 .../db-structure/database.sql                 |   2 +-
 .../parts/030-data/140-status.sql             |  54 +-
 .../parts/030-data/145-updates.sql            | 172 ++++++
 .../parts/040-functions/003-updates.sql       | 571 ++++++++++++++++++
 .../parts/040-functions/040-empire.sql        |  12 -
 .../parts/040-functions/060-universe.sql      |  11 -
 .../parts/050-updates/000-updates-ctrl.sql    | 208 +++++--
 .../050-updates/005-core-update-targets.sql   |  16 +
 .../parts/050-updates/010-empire-money.sql    |  33 +-
 .../050-updates/015-empire-resources.sql      |  20 +-
 .../parts/050-updates/020-empire-research.sql |  30 +-
 .../parts/050-updates/025-empire-debt.sql     |  19 +-
 .../parts/050-updates/030-fleet-arrivals.sql  |  51 +-
 .../parts/050-updates/040-fleet-movements.sql |  36 +-
 .../parts/050-updates/050-fleet-status.sql    |  42 +-
 .../parts/050-updates/060-planet-battle.sql   |  47 +-
 .../parts/050-updates/070-planet-abandon.sql  |  47 +-
 .../050-updates/080-planet-construction.sql   |  14 +-
 .../parts/050-updates/090-planet-military.sql |  14 +-
 .../050-updates/100-planet-population.sql     |  16 +-
 .../105-planet-resource-regeneration.sql      |  20 +-
 .../parts/050-updates/110-planet-money.sql    |  16 +-
 .../parts/050-updates/120-planet-mining.sql   |  29 +-
 .../003-updates/010-target-definition.sql     |  70 +++
 .../003-updates/020-type-definitions.sql      | 150 +++++
 .../040-empire/010-create-empire.sql          |  13 +-
 .../000-updates-ctrl/010-start-tick.sql       |  53 ++
 .../000-updates-ctrl/020-check-stuck-tick.sql |  60 ++
 .../000-updates-ctrl/030-process-updates.sql  | 179 ++++++
 .../003-core-update-targets/010-presence.sql  |  17 +
 .../010-process-empire-resources-updates.sql  |  15 +-
 .../010-process-planet-res-regen-updates.sql  |  35 +-
 .../145-updates/010-update-targets.sql        |  28 +
 .../030-data/145-updates/020-update-types.sql |  28 +
 .../user/030-data/145-updates/030-updates.sql |  28 +
 .../003-updates/010-get-table-pkey.sql        |  11 +
 .../020-tgf-speupd-before-insert.sql          |  11 +
 .../030-tgf-speupd-after-delete.sql           |  11 +
 .../040-insert-missing-updates.sql            |  11 +
 .../050-tgf-insert-missing-updates.sql        |  11 +
 .../060-tgf-updtype-after-insert-row.sql      |  11 +
 .../070-tgf-check-update-type-proc.sql        |  11 +
 .../080-tgf-reorder-update-types.sql          |  11 +
 .../090-tgf-updtgt-before-insert.sql          |  11 +
 .../100-tgf-updtgt-after-insert.sql           |  11 +
 .../110-tgf-updtgt-after-delete.sql           |  11 +
 .../003-updates/120-register-update-type.sql  |  11 +
 .../000-updates-ctrl/010-start-tick.sql       |  11 +
 .../000-updates-ctrl/020-check-stuck-tick.sql |  11 +
 .../000-updates-ctrl/030-process-updates.sql  |  11 +
 .../010-process-planet-res-regen-updates.sql  |  11 -
 .../010-process-res-regen-updates.sql         |  11 +
 .../setup-gu-pmc-get-data-test.sql            |  47 +-
 .../lw/sqld/sys/GameUpdateResult.java         |  82 +++
 .../lw/interfaces/game/UpdatesDAO.java        |   7 -
 .../game/updates/GameUpdateProcessor.java     |  75 +++
 .../game/updates/UpdateBatchProcessor.java    |  42 ++
 .../interfaces/game/updates/UpdatesDAO.java   |  34 ++
 legacyworlds-server-main/pom.xml              |   4 +
 .../src/main/resources/lw-server.xml          |   6 +
 .../lw/beans/updates/DummyBatchProcessor.java |  62 ++
 .../updates/MockGameUpdateProcessor.java      |  91 +++
 .../lw/beans/updates/MockRegistry.java        |  41 ++
 .../lw/beans/updates/MockUpdatesDAO.java      |  32 +
 .../updates/TestGameUpdateProcessorBean.java  | 240 ++++++++
 .../beans/updates/TestGameUpdateTaskBean.java |  93 +++
 .../updates/TestGameUpdateTransaction.java    |  86 +++
 .../TestServerProcessorRegistryBean.java      |  62 ++
 .../com/deepclone/lw/testing/MockLogger.java  |  34 ++
 .../lw/testing/MockSystemLogger.java          |  62 ++
 .../lw/testing/MockSystemStatus.java          | 148 +++++
 .../com/deepclone/lw/testing/MockTicker.java  |  82 +++
 .../lw/testing/MockTransactionManager.java    |  43 ++
 legacyworlds/pom.xml                          |   5 +
 93 files changed, 4004 insertions(+), 578 deletions(-)
 delete mode 100644 legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
 delete mode 100644 legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
 delete mode 100644 legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
 create mode 100644 legacyworlds-server-beans-updates/pom.xml
 create mode 100644 legacyworlds-server-beans-updates/src/main/java/.empty
 create mode 100644 legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateProcessorBean.java
 create mode 100644 legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTaskBean.java
 create mode 100644 legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTransaction.java
 create mode 100644 legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistry.java
 create mode 100644 legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistryBean.java
 create mode 100644 legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UnsupportedUpdateException.java
 create mode 100644 legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
 create mode 100644 legacyworlds-server-beans-updates/src/main/resources/.empty
 rename legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml => legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml (61%)
 create mode 100644 legacyworlds-server-beans-updates/src/main/resources/configuration/game/updates.xml
 create mode 100644 legacyworlds-server-beans-updates/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-updates/src/test/resources/.empty
 create mode 100644 legacyworlds-server-data/db-structure/parts/030-data/145-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/parts/040-functions/003-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/parts/050-updates/005-core-update-targets.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/010-target-definition.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/020-type-definitions.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/010-start-tick.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/030-process-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/003-core-update-targets/010-presence.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/010-update-targets.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/020-update-types.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/030-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/010-get-table-pkey.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/020-tgf-speupd-before-insert.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/030-tgf-speupd-after-delete.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/040-insert-missing-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/050-tgf-insert-missing-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/060-tgf-updtype-after-insert-row.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/070-tgf-check-update-type-proc.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/080-tgf-reorder-update-types.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/090-tgf-updtgt-before-insert.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/100-tgf-updtgt-after-insert.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/110-tgf-updtgt-after-delete.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/120-register-update-type.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/010-start-tick.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/030-process-updates.sql
 delete mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-res-regen-updates.sql
 create mode 100644 legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/GameUpdateResult.java
 delete mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateProcessor.java
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdateBatchProcessor.java
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/DummyBatchProcessor.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockGameUpdateProcessor.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockRegistry.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockUpdatesDAO.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateProcessorBean.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTaskBean.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTransaction.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestServerProcessorRegistryBean.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockLogger.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemLogger.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemStatus.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTicker.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTransactionManager.java

diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
deleted file mode 100644
index 3973457..0000000
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.deepclone.lw.beans.updates;
-
-
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.TransactionCallback;
-import org.springframework.transaction.support.TransactionTemplate;
-
-import com.deepclone.lw.cmd.admin.logs.LogLevel;
-import com.deepclone.lw.interfaces.eventlog.Logger;
-import com.deepclone.lw.interfaces.eventlog.SystemLogger;
-import com.deepclone.lw.interfaces.game.UpdatesDAO;
-import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
-import com.deepclone.lw.interfaces.sys.SystemStatus;
-import com.deepclone.lw.interfaces.sys.TickStatusException;
-import com.deepclone.lw.interfaces.sys.Ticker;
-import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
-
-
-
-public class GameUpdateBean
-		implements InitializingBean , Runnable
-{
-	private Ticker ticker;
-	private SystemStatus systemStatus;
-	private SystemLogger logger;
-
-	private TransactionTemplate tTemplate;
-	private UpdatesDAO updatesDao;
-
-
-	@Autowired( required = true )
-	public void setTicker( Ticker ticker )
-	{
-		this.ticker = ticker;
-	}
-
-
-	@Autowired( required = true )
-	public void setSystemStatus( SystemStatus systemStatus )
-	{
-		this.systemStatus = systemStatus;
-	}
-
-
-	@Autowired( required = true )
-	public void setLogger( Logger logger )
-	{
-		this.logger = logger.getSystemLogger( "GameUpdate" );
-	}
-
-
-	@Autowired( required = true )
-	public void setTransactionManager( PlatformTransactionManager transactionManager )
-	{
-		this.tTemplate = new TransactionTemplate( transactionManager );
-	}
-
-
-	@Autowired( required = true )
-	public void setUpdatesDAO( UpdatesDAO updatesDao )
-	{
-		this.updatesDao = updatesDao;
-	}
-
-
-	@Override
-	public void afterPropertiesSet( )
-	{
-		try {
-			this.endPreviousTick( );
-		} catch ( MaintenanceStatusException e ) {
-			// Do nothing
-		}
-		this.ticker.registerTask( Frequency.MINUTE , "Game update" , this );
-	}
-
-
-	@Override
-	public void run( )
-	{
-		// Attempt to end the previous tick, if e.g. maintenance mode was initiated while it was
-		// being processed
-		try {
-			this.endPreviousTick( );
-		} catch ( MaintenanceStatusException e1 ) {
-			return;
-		}
-
-		// Initiate next tick
-		long tickId;
-		try {
-			tickId = this.systemStatus.startTick( );
-		} catch ( TickStatusException e ) {
-			throw new RuntimeException( "tick initiated while previous tick still being processed" , e );
-		} catch ( MaintenanceStatusException e ) {
-			return;
-		}
-
-		// Execute tick
-		this.logger.log( LogLevel.DEBUG , "Tick " + tickId + " started" ).flush( );
-		this.executeTick( tickId );
-	}
-
-
-	private void endPreviousTick( )
-			throws MaintenanceStatusException
-	{
-		Long currentTick = this.systemStatus.checkStuckTick( );
-		if ( currentTick == null ) {
-			return;
-		}
-
-		this.logger.log( LogLevel.WARNING , "Tick " + currentTick + " restarted" ).flush( );
-		this.executeTick( currentTick.longValue( ) );
-	}
-
-
-	private void executeTick( final long tickId )
-	{
-		boolean hasMore;
-		do {
-			hasMore = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
-
-				@Override
-				public Boolean doInTransaction( TransactionStatus status )
-				{
-					return updatesDao.processUpdates( tickId );
-				}
-
-			} );
-		} while ( hasMore );
-		this.logger.log( LogLevel.TRACE , "Tick " + tickId + " completed" ).flush( );
-	}
-
-}
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
deleted file mode 100644
index 77a1bbd..0000000
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.deepclone.lw.beans.updates;
-
-
-import java.sql.Types;
-import java.util.Map;
-
-import javax.sql.DataSource;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.SqlOutParameter;
-import org.springframework.jdbc.core.SqlParameter;
-import org.springframework.jdbc.core.simple.SimpleJdbcCall;
-
-import com.deepclone.lw.interfaces.game.UpdatesDAO;
-
-
-
-public class UpdatesDAOBean
-		implements UpdatesDAO
-{
-
-	private SimpleJdbcCall process;
-
-
-	@Autowired( required = true )
-	public void setDataSource( DataSource dataSource )
-	{
-		this.process = new SimpleJdbcCall( dataSource );
-		this.process.withCatalogName( "sys" ).withFunctionName( "process_updates" );
-		this.process.withoutProcedureColumnMetaDataAccess( );
-		this.process.addDeclaredParameter( new SqlParameter( "tick_id" , Types.BIGINT ) );
-		this.process.addDeclaredParameter( new SqlOutParameter( "has_more" , Types.BOOLEAN ) );
-	}
-
-
-	@Override
-	public boolean processUpdates( long tickId )
-	{
-		Map< String , Object > m = this.process.execute( tickId );
-		return (Boolean) m.get( "has_more" );
-	}
-
-}
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
index ffd1068..a6219d9 100644
--- a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
+++ b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
@@ -10,13 +10,11 @@
 	<import resource="simple/empire-management-bean.xml" />
 	<import resource="simple/fleet-management-bean.xml" />
 	<import resource="simple/fleets-dao-bean.xml" />
-	<import resource="simple/game-update-bean.xml" />
 	<import resource="simple/map-viewer-bean.xml" />
 	<import resource="simple/message-beans.xml" />
 	<import resource="simple/planet-dao-bean.xml" />
 	<import resource="simple/planets-management-bean.xml" />
 	<import resource="simple/universe-dao-bean.xml" />
 	<import resource="simple/universe-generator-bean.xml" />
-	<import resource="simple/updates-dao-bean.xml" />
 
 </beans>
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
deleted file mode 100644
index 54534ff..0000000
--- a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<!-- Game update bean -->
-	<bean id="gameUpdate" class="com.deepclone.lw.beans.updates.GameUpdateBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-updates/pom.xml b/legacyworlds-server-beans-updates/pom.xml
new file mode 100644
index 0000000..d719f44
--- /dev/null
+++ b/legacyworlds-server-beans-updates/pom.xml
@@ -0,0 +1,17 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<parent>
+		<artifactId>legacyworlds-server-beans</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-server-beans-updates</artifactId>
+	<name>Legacy Worlds - Server - Components - Game updates</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<description>This module contains the components which process the game updates and allow the administration interface to display information about them.</description>
+
+</project>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-updates/src/main/java/.empty b/legacyworlds-server-beans-updates/src/main/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateProcessorBean.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateProcessorBean.java
new file mode 100644
index 0000000..73da48f
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateProcessorBean.java
@@ -0,0 +1,238 @@
+package com.deepclone.lw.beans.updates;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.cmd.admin.logs.LogLevel;
+import com.deepclone.lw.interfaces.eventlog.Logger;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+import com.deepclone.lw.interfaces.game.updates.GameUpdateProcessor;
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
+import com.deepclone.lw.interfaces.sys.SystemStatus;
+import com.deepclone.lw.interfaces.sys.TickStatusException;
+
+
+
+/**
+ * Game update processor component
+ * 
+ * <p>
+ * This component implements game updates processing. It will update the system status as necessary,
+ * checking for maintenance mode, and of course implement the necessary operations for game updates.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class GameUpdateProcessorBean
+		implements GameUpdateProcessor
+{
+	/** System state management component */
+	private SystemStatus systemStatus;
+
+	/** Logger used by this component */
+	private SystemLogger logger;
+
+	/** Transaction template used to run update batches */
+	private TransactionTemplate tTemplate;
+
+	/** Update processing database interface */
+	private UpdatesDAO updatesDao;
+
+	/** Server-level update processors registry */
+	private ServerProcessorRegistry registry;
+
+	/** Game update processor lock */
+	private boolean executing;
+
+
+	/**
+	 * Dependency injector that sets the sytstem state management component
+	 * 
+	 * @param systemStatus
+	 *            the sytstem state management component
+	 */
+	@Autowired( required = true )
+	public void setSystemStatus( SystemStatus systemStatus )
+	{
+		this.systemStatus = systemStatus;
+	}
+
+
+	/**
+	 * Dependency injector that initialises the logger from the logging component
+	 * 
+	 * @param logger
+	 *            the logging component
+	 */
+	@Autowired( required = true )
+	public void setLogger( Logger logger )
+	{
+		this.logger = logger.getSystemLogger( "GameUpdate" );
+	}
+
+
+	/**
+	 * Dependency injector that initialises the transaction template from the transaction manager
+	 * 
+	 * @param transactionManager
+	 *            the transaction manager
+	 */
+	@Autowired( required = true )
+	public void setTransactionManager( PlatformTransactionManager transactionManager )
+	{
+		this.tTemplate = new TransactionTemplate( transactionManager );
+	}
+
+
+	/**
+	 * Dependency injector that sets the database access component for game updates
+	 * 
+	 * @param updatesDao
+	 *            the database access component for game updates
+	 */
+	@Autowired( required = true )
+	public void setUpdatesDAO( UpdatesDAO updatesDao )
+	{
+		this.updatesDao = updatesDao;
+	}
+
+
+	/**
+	 * Dependency injector that sets the processor registry
+	 * 
+	 * @param registry
+	 *            the processor registry
+	 */
+	@Autowired( required = true )
+	public void setRegistry( ServerProcessorRegistry registry )
+	{
+		this.registry = registry;
+	}
+
+
+	/**
+	 * Try and lock the processor
+	 * 
+	 * <p>
+	 * Test the {@link #executing} field, setting it to <code>true</code> and returning
+	 * <code>true</code> if it wasn't or returning <code>false</code> if it was.
+	 */
+	@Override
+	public synchronized boolean tryLock( )
+	{
+		if ( this.executing ) {
+			return false;
+		}
+		this.executing = true;
+		return true;
+	}
+
+
+	/**
+	 * Unlock the processor
+	 * 
+	 * <p>
+	 * Set {@link #executing} back to <code>false</code>. If it was already set to
+	 * <code>false</code>, throw an {@link IllegalStateException}.
+	 */
+	@Override
+	public synchronized void unlock( )
+			throws IllegalStateException
+	{
+		if ( !this.executing ) {
+			throw new IllegalStateException( "Trying to unlock game update processor, but it isn't locked" );
+		}
+		this.executing = false;
+	}
+
+
+	/**
+	 * Check for an unfinished update cycle and terminate it
+	 * 
+	 * <p>
+	 * Check for a "stuck" update cycle. If there is one, issue a warning and process it through the
+	 * {@link #executeTick(long)} method.
+	 */
+	@Override
+	public boolean endPreviousCycle( )
+	{
+		Long currentTick;
+		try {
+			currentTick = this.systemStatus.checkStuckTick( );
+		} catch ( MaintenanceStatusException e ) {
+			return true;
+		}
+
+		if ( currentTick == null ) {
+			return false;
+		}
+
+		this.logger.log( LogLevel.WARNING , "Tick " + currentTick + " restarted" ).flush( );
+		this.executeTick( currentTick );
+		return true;
+	}
+
+
+	/**
+	 * Execute an update cycle
+	 * 
+	 * <p>
+	 * Start by checking for, and executing if necessary, a "stuck" update cycle. If one is found,
+	 * return. Otherwise, start a new tick through the system state manager, and call
+	 * {@link #executeTick(long)} to process it.
+	 * 
+	 * <p>
+	 * If maintenance mode is active, the new tick will not be started.
+	 */
+	@Override
+	public void executeUpdateCycle( )
+	{
+		// Attempt to end the previous tick, if e.g. maintenance mode was initiated while it was
+		// being processed
+		if ( this.endPreviousCycle( ) ) {
+			return;
+		}
+
+		// Initiate next tick
+		long tickId;
+		try {
+			tickId = this.systemStatus.startTick( );
+		} catch ( TickStatusException e ) {
+			throw new RuntimeException( "tick initiated while previous tick still being processed" , e );
+		} catch ( MaintenanceStatusException e ) {
+			return;
+		}
+
+		// Execute tick
+		this.logger.log( LogLevel.DEBUG , "Tick " + tickId + " started" ).flush( );
+		this.executeTick( tickId );
+	}
+
+
+	/**
+	 * Process all remaining update batches for an update cycle
+	 * 
+	 * <p>
+	 * This method will try and process all update batches using a {@link GameUpdateTransaction}
+	 * instance. It loops until the transaction indicates that no further updates are to be
+	 * processed. However, the loop will exit if maintenance mode becomes active between update
+	 * batches.
+	 * 
+	 * @param tickId
+	 *            the game update cycle's identifier
+	 */
+	private void executeTick( final long tickId )
+	{
+		GameUpdateTransaction transaction = new GameUpdateTransaction( this.updatesDao , this.registry , tickId );
+		while ( !this.tTemplate.execute( transaction ) ) {
+			if ( this.systemStatus.checkMaintenance( ) != null ) {
+				this.logger.log( LogLevel.INFO , "Tick " + tickId + " interrupted by system maintenance" ).flush( );
+				return;
+			}
+		}
+		this.logger.log( LogLevel.TRACE , "Tick " + tickId + " completed" ).flush( );
+	}
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTaskBean.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTaskBean.java
new file mode 100644
index 0000000..6878e1b
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTaskBean.java
@@ -0,0 +1,96 @@
+package com.deepclone.lw.beans.updates;
+
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.interfaces.game.updates.GameUpdateProcessor;
+import com.deepclone.lw.interfaces.sys.Ticker;
+import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
+
+
+
+/**
+ * Game update ticker task
+ * 
+ * <p>
+ * This component will register itself as a ticker task to be executed once per minute. When it
+ * runs, it will try and execute an update cycle.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class GameUpdateTaskBean
+		implements InitializingBean , Runnable
+{
+	/** The ticker component */
+	private Ticker ticker;
+
+	/** The game updates processor */
+	private GameUpdateProcessor gameUpdateProcessor;
+
+
+	/**
+	 * Dependency injector that sets the ticker component
+	 * 
+	 * @param ticker
+	 *            the ticker component
+	 */
+	@Autowired( required = true )
+	public void setTicker( Ticker ticker )
+	{
+		this.ticker = ticker;
+	}
+
+
+	/**
+	 * Dependency injector that sets the game update processor
+	 * 
+	 * @param gameUpdateProcessor
+	 *            the game update processor
+	 */
+	@Autowired( required = true )
+	public void setGameUpdateProcessor( GameUpdateProcessor gameUpdateProcessor )
+	{
+		this.gameUpdateProcessor = gameUpdateProcessor;
+	}
+
+
+	/**
+	 * Finish a previous update cycle if there is one, then register the component to the ticker
+	 */
+	@Override
+	public void afterPropertiesSet( )
+	{
+		if ( this.gameUpdateProcessor.tryLock( ) ) {
+
+			try {
+				this.gameUpdateProcessor.endPreviousCycle( );
+			} finally {
+				this.gameUpdateProcessor.unlock( );
+			}
+
+		}
+		this.ticker.registerTask( Frequency.MINUTE , "Game update" , this );
+	}
+
+
+	/**
+	 * Run a game update cycle
+	 * 
+	 * <p>
+	 * Try locking the game update processor. On success, execute an update cycle, then unlock the
+	 * processor.
+	 */
+	@Override
+	public void run( )
+	{
+		if ( !this.gameUpdateProcessor.tryLock( ) ) {
+			return;
+		}
+		try {
+			this.gameUpdateProcessor.executeUpdateCycle( );
+		} finally {
+			this.gameUpdateProcessor.unlock( );
+		}
+	}
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTransaction.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTransaction.java
new file mode 100644
index 0000000..b040555
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTransaction.java
@@ -0,0 +1,89 @@
+package com.deepclone.lw.beans.updates;
+
+
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.sqld.sys.GameUpdateResult;
+
+
+
+/**
+ * Game update transaction
+ * 
+ * <p>
+ * This class is responsible for executing the necessary operations during a game update
+ * transaction. It is used by the {@link GameUpdateProcessorBean} component to implement its main
+ * processing loop.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class GameUpdateTransaction
+		implements TransactionCallback< Boolean >
+{
+	/** The game updates database access interface */
+	private final UpdatesDAO updatesDao;
+
+	/** The registry of server-side update batch processors */
+	private final ServerProcessorRegistry registry;
+
+	/** The identifier of the current update cycle */
+	private final long tickId;
+
+
+	/**
+	 * Initialise the various dependencies and set the identifier of the current cycle
+	 * 
+	 * @param updatesDao
+	 *            the game updates database access interface
+	 * @param registry
+	 *            the registry of server-side update batch processors
+	 * @param tickId
+	 *            the identifier of the current update cycle
+	 */
+	GameUpdateTransaction( UpdatesDAO updatesDao , ServerProcessorRegistry registry , long tickId )
+	{
+		this.updatesDao = updatesDao;
+		this.registry = registry;
+		this.tickId = tickId;
+	}
+
+
+	/**
+	 * Update batch processing transaction
+	 * 
+	 * <p>
+	 * Call the database's game update processing function, returning immediately if the function
+	 * indicates that the cycle has been completed. Otherwise, execute a server-side processor if
+	 * necessary.
+	 * 
+	 * <p>
+	 * If the database requests server-side processing but no processor is found, an
+	 * {@link UnsupportedUpdateException} will be thrown.
+	 * 
+	 * @return <code>true</code> if the current update cycle has been completed, <code>false</code>
+	 *         if more updates are needed.
+	 */
+	@Override
+	public Boolean doInTransaction( TransactionStatus status )
+	{
+		GameUpdateResult result = this.updatesDao.processUpdates( this.tickId );
+		String local = result.getLocalExecution( );
+		if ( result.isFinished( ) ) {
+			return true;
+		} else if ( local == null ) {
+			return false;
+		}
+
+		UpdateBatchProcessor processor = this.registry.getProcessorFor( local );
+		if ( processor == null ) {
+			throw new UnsupportedUpdateException( local );
+		}
+
+		processor.processBatch( this.tickId );
+		return false;
+	}
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistry.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistry.java
new file mode 100644
index 0000000..423c7cc
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistry.java
@@ -0,0 +1,33 @@
+package com.deepclone.lw.beans.updates;
+
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+
+
+
+/**
+ * Server-side update processor registry
+ * 
+ * <p>
+ * This interface allows server-side game update batch processors to be looked up depending on the
+ * type of the updates to process.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+interface ServerProcessorRegistry
+{
+
+	/**
+	 * Get a batch processor for some type of update
+	 * 
+	 * @param type
+	 *            the type of updates to process, as stored in the <code>sys.update_types</code>
+	 *            database table
+	 * 
+	 * @return <code>null</code> if no processor for the specified type has been registered, or the
+	 *         processor to use.
+	 */
+	public UpdateBatchProcessor getProcessorFor( String type );
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistryBean.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistryBean.java
new file mode 100644
index 0000000..9055ae9
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistryBean.java
@@ -0,0 +1,68 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+
+
+
+/**
+ * Server-side update processor registry component
+ * 
+ * <p>
+ * This component will look through registered Spring components for update processors, and use the
+ * results as the registry's contents.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class ServerProcessorRegistryBean
+		implements ServerProcessorRegistry , BeanPostProcessor
+{
+
+	/** Known server-side update batch processors */
+	private final Map< String , UpdateBatchProcessor > processors = new HashMap< String , UpdateBatchProcessor >( );
+
+
+	/** Do nothing - we're not interested in uninitialised components */
+	@Override
+	public Object postProcessBeforeInitialization( Object bean , String beanName )
+	{
+		return bean;
+	}
+
+
+	/**
+	 * If an initialised component implements the {@link UpdateBatchProcessor} interface, add it to
+	 * the {@link #processors} map. A {@link BeanInitializationException} will be thrown if two
+	 * components seem to process the same update type.
+	 */
+	@Override
+	public Object postProcessAfterInitialization( Object bean , String beanName )
+			throws BeansException
+	{
+		if ( bean instanceof UpdateBatchProcessor ) {
+			UpdateBatchProcessor processor = (UpdateBatchProcessor) bean;
+			String updType = processor.getUpdateType( );
+			if ( this.processors.containsKey( updType ) ) {
+				throw new BeanInitializationException( "Duplicate update processor for update type " + updType );
+			}
+			this.processors.put( updType , processor );
+		}
+		return bean;
+	}
+
+
+	/** Obtain the processor from the {@link #processors} map */
+	@Override
+	public UpdateBatchProcessor getProcessorFor( String type )
+	{
+		return this.processors.get( type );
+	}
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UnsupportedUpdateException.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UnsupportedUpdateException.java
new file mode 100644
index 0000000..c167bf1
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UnsupportedUpdateException.java
@@ -0,0 +1,29 @@
+package com.deepclone.lw.beans.updates;
+
+
+/**
+ * Unsupported update exception
+ * 
+ * <p>
+ * This exception is thrown by the {@link GameUpdateTransaction} when there is no server-side
+ * processor for some update type.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+public class UnsupportedUpdateException
+		extends RuntimeException
+{
+
+	/**
+	 * Initialise the exception
+	 * 
+	 * @param name
+	 *            the name of the update type for which no processor is registered.
+	 */
+	UnsupportedUpdateException( String name )
+	{
+		super( "No processor found for update type " + name );
+	}
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
new file mode 100644
index 0000000..8de9681
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
@@ -0,0 +1,69 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.sql.Types;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.sqld.sys.GameUpdateResult;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Updates data access component
+ * 
+ * <p>
+ * This component accesses the database procedures and views which constitute the game update
+ * system.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class UpdatesDAOBean
+		implements UpdatesDAO
+{
+
+	/** Game update processor stored procedure */
+	private StoredProc process;
+
+
+	/**
+	 * Dependency injector for the data source
+	 * 
+	 * <p>
+	 * When Spring injects the data source component, initialise the stored procedures used by the
+	 * component.
+	 * 
+	 * @param dataSource
+	 *            the data source component
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.process = new StoredProc( dataSource , "sys" , "process_updates" ).addParameter( "tick_id" , Types.BIGINT )
+				.addOutput( "_has_more" , Types.BOOLEAN ).addOutput( "_process_externally" , Types.VARCHAR );
+	}
+
+
+	/**
+	 * Execute the processor's stored procedure
+	 * 
+	 * <p>
+	 * This implementation simply runs the database's <code>sys.process_updates()</code> stored
+	 * procedure, and converts the two values it returns into a {@link GameUpdateResult} instance.
+	 */
+	@Override
+	public GameUpdateResult processUpdates( long tickId )
+	{
+		Map< String , Object > m = this.process.execute( tickId );
+		if ( !(Boolean) m.get( "_has_more" ) ) {
+			return new GameUpdateResult( );
+		}
+		return new GameUpdateResult( (String) m.get( "_process_externally" ) );
+	}
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/resources/.empty b/legacyworlds-server-beans-updates/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml b/legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml
similarity index 61%
rename from legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml
rename to legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml
index eb24044..687e57a 100644
--- a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml
+++ b/legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml
@@ -2,8 +2,8 @@
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
-	<bean id="updatesDAO" class="com.deepclone.lw.beans.updates.UpdatesDAOBean" />
+	<import resource="game/updates.xml" />
 
-</beans>
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-updates/src/main/resources/configuration/game/updates.xml b/legacyworlds-server-beans-updates/src/main/resources/configuration/game/updates.xml
new file mode 100644
index 0000000..b957642
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/resources/configuration/game/updates.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<bean id="gameUpdateDAO" class="com.deepclone.lw.beans.updates.UpdatesDAOBean" />
+	<bean id="gameUpdateProcessor" class="com.deepclone.lw.beans.updates.GameUpdateProcessorBean" />
+	<bean id="gameUpdateRegistry"
+		class="com.deepclone.lw.beans.updates.ServerProcessorRegistryBean" />
+	<bean id="gameUpdateTask" class="com.deepclone.lw.beans.updates.GameUpdateTaskBean" />
+
+</beans>
diff --git a/legacyworlds-server-beans-updates/src/test/java/.empty b/legacyworlds-server-beans-updates/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-updates/src/test/resources/.empty b/legacyworlds-server-beans-updates/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans/pom.xml b/legacyworlds-server-beans/pom.xml
index 6e138de..a03d945 100644
--- a/legacyworlds-server-beans/pom.xml
+++ b/legacyworlds-server-beans/pom.xml
@@ -21,15 +21,16 @@
 	</dependencies>
 
 	<modules>
-		<module>../legacyworlds-server-beans-i18n</module>
-		<module>../legacyworlds-server-beans-eventlog</module>
 		<module>../legacyworlds-server-beans-accounts</module>
+		<module>../legacyworlds-server-beans-bt</module>
+		<module>../legacyworlds-server-beans-eventlog</module>
+		<module>../legacyworlds-server-beans-i18n</module>
 		<module>../legacyworlds-server-beans-mailer</module>
-		<module>../legacyworlds-server-beans-system</module>
 		<module>../legacyworlds-server-beans-naming</module>
 		<module>../legacyworlds-server-beans-resources</module>
-		<module>../legacyworlds-server-beans-bt</module>
-		<module>../legacyworlds-server-beans-user</module>
 		<module>../legacyworlds-server-beans-simple</module>
+		<module>../legacyworlds-server-beans-system</module>
+		<module>../legacyworlds-server-beans-updates</module>
+		<module>../legacyworlds-server-beans-user</module>
 	</modules>
 </project>
diff --git a/legacyworlds-server-data/db-structure/database.sql b/legacyworlds-server-data/db-structure/database.sql
index 50b7913..9552e38 100644
--- a/legacyworlds-server-data/db-structure/database.sql
+++ b/legacyworlds-server-data/db-structure/database.sql
@@ -48,4 +48,4 @@ BEGIN;
 COMMIT;
 
 /* Delete loader script */
-\! rm loader.tmp
\ No newline at end of file
+\! rm loader.tmp
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql b/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql
index a3c76bb..ef0b8d4 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql
@@ -1,7 +1,7 @@
 -- LegacyWorlds Beta 6
 -- PostgreSQL database scripts
 --
--- System & game updates status
+-- System & ticker status
 --
 -- Copyright(C) 2004-2010, DeepClone Development
 -- --------------------------------------------------------
@@ -53,55 +53,3 @@ INSERT INTO sys.ticker( task_name , status )
 	VALUES ( 'Game update' , 'STOPPED' );
 
 GRANT SELECT ON sys.ticker TO :dbuser;
-
-
-
---
--- Updates
---
-CREATE TABLE sys.updates(
-	id					BIGSERIAL NOT NULL PRIMARY KEY ,
-	gu_type				update_type NOT NULL ,
-	status				processing_status NOT NULL DEFAULT 'FUTURE' ,
-	last_tick			BIGINT NOT NULL DEFAULT -1
-);
-
-CREATE INDEX idx_updates_finder
-	ON sys.updates (gu_type, status, last_tick);
-
-
---
--- Planet updates
---
-CREATE TABLE verse.updates(
-	update_id			BIGINT NOT NULL PRIMARY KEY ,
-	planet_id			INT NOT NULL
-);
-
-CREATE INDEX idx_planetupdates_planet
-	ON verse.updates (planet_id);
-
-ALTER TABLE verse.updates
-	ADD CONSTRAINT fk_planetupdates_update
-		FOREIGN KEY ( update_id ) REFERENCES sys.updates ,
-	ADD CONSTRAINT fk_planetupdates_planet
-		FOREIGN KEY ( planet_id ) REFERENCES verse.planets;
-
-
---
--- Empire updates
---
-CREATE TABLE emp.updates(
-	update_id			BIGINT NOT NULL PRIMARY KEY ,
-	empire_id			INT NOT NULL
-);
-
-CREATE INDEX idx_empireupdates_empire
-	ON emp.updates( empire_id );
-
-ALTER TABLE emp.updates
-	ADD CONSTRAINT fk_empireupdates_update
-		FOREIGN KEY ( update_id ) REFERENCES sys.updates ,
-	ADD CONSTRAINT fk_empireupdates_empire
-		FOREIGN KEY ( empire_id ) REFERENCES emp.empires
-			ON DELETE CASCADE;
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/145-updates.sql b/legacyworlds-server-data/db-structure/parts/030-data/145-updates.sql
new file mode 100644
index 0000000..8ce13f5
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/030-data/145-updates.sql
@@ -0,0 +1,172 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates control tables
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Update targets
+ * ---------------
+ * 
+ * An update target refers to a table whose rows are the units to be updated
+ * by some step of the game update.
+ * 
+ * Inserting rows into this control table will generate a new, specific table
+ * to store references, and a set of triggers.
+ */
+DROP TABLE IF EXISTS sys.update_targets CASCADE;
+CREATE TABLE sys.update_targets(
+	/* Automatically generated identifier of the target type */
+	updtgt_id			SERIAL NOT NULL PRIMARY KEY ,
+	
+	/* Name of the target, displayed in the administration interface */
+	updtgt_name			VARCHAR( 32 ) NOT NULL ,
+
+	/* Schema in which the target table resides */
+	updtgt_schema		NAME NOT NULL ,
+
+	/* Name of the target table */
+	updtgt_table		NAME NOT NULL
+);
+
+/* Update target names are unique */
+CREATE UNIQUE INDEX idx_updtgt_name
+	ON sys.update_targets ( LOWER( updtgt_name ) );
+/* Schema / table combinations are unique */
+CREATE UNIQUE INDEX idx_updtgt_target
+	ON sys.update_targets ( updtgt_schema , updtgt_table );
+
+
+
+/*
+ * Update type definitions
+ * ------------------------
+ * 
+ * An update type corresponds to a procedure which will be applied at each
+ * game update cycle (once per minute, unless defaults have been modified),
+ * and which performs a computation or set thereof on the game's data.
+ */
+DROP TABLE IF EXISTS sys.update_types CASCADE;
+CREATE TABLE sys.update_types(
+	/* Automatically generated identifier of the update type */
+	updtype_id			SERIAL NOT NULL ,
+
+	/* The type of target this update refers to */
+	updtgt_id			INT NOT NULL ,
+	
+	/* Name of the update type, used in the administration interface and when
+	 * updates need to be executed externally.
+	 */
+	updtype_name		VARCHAR( 64 ) NOT NULL ,
+
+	/* Ordering index of the update type. This field is always re-generated
+	 * when update types are added or removed.
+	 */
+	updtype_ordering	INT NOT NULL ,
+
+	/* Description of the update type to be included in the administration
+	 * interface.
+	 */
+	updtype_description	TEXT NOT NULL ,
+	
+	/* Name of the stored procedure which handles the update. When this is
+	 * NULL, the update type is assumed to be supported externally (i.e. by
+	 * the game server) rather than internally. Otherwise, a stored procedure
+	 * bearing that name, accepting a BIGINT as its parameter and returning
+	 * VOID, must exist in the sys schema. 
+	 */
+	updtype_proc_name	NAME ,
+
+	/* Size of the update batch. If this is NULL, the global value from the
+	 * game.batchSize constant will be used.
+	 */
+	updtype_batch_size	INT ,
+
+	/* The primary key includes both the update type identifier and the target
+	 * identifier for coherence reasons.
+	 */
+	PRIMARY KEY( updtype_id , updtgt_id ) ,
+
+	/* Batch sizes are either NULL or strictly positive */
+	CHECK( updtype_batch_size IS NULL
+			OR updtype_batch_size > 0 )
+);
+
+/* Update names must be unique, independently of the case */
+CREATE UNIQUE INDEX idx_updtype_name
+	ON sys.update_types ( LOWER( updtype_name ) );
+/* Update ordering index must be unique */
+CREATE UNIQUE INDEX idx_updtype_ordering
+	ON sys.update_types ( updtype_ordering );
+/* Procedure names must be unique */
+CREATE UNIQUE INDEX idx_updtype_procname
+	ON sys.update_types ( updtype_proc_name );
+
+
+
+ALTER TABLE sys.update_types
+	ADD CONSTRAINT fk_updtype_target
+		FOREIGN KEY ( updtgt_id ) REFERENCES sys.update_targets( updtgt_id );
+
+
+
+/*
+ * Update state type
+ * ------------------
+ * 
+ * This type represents the possible states of a game update
+ */
+DROP TYPE IF EXISTS sys.update_state_type CASCADE;
+CREATE TYPE sys.update_state_type
+	AS ENUM (
+		/* The row will be included in the current game update */
+		'FUTURE' ,
+		/* The row is being processed */
+		'PROCESSING' ,
+		/* The row has been processed by the current or previous game update */
+		'PROCESSED'
+	);
+
+
+/*
+ * Main updates table
+ * -------------------
+ * 
+ * This table lists all update rows, including their type and target, as well
+ * as their state.
+ */
+DROP TABLE IF EXISTS sys.updates CASCADE;
+CREATE TABLE sys.updates(
+	/* The update row's automatically generated identifier */
+	update_id		BIGSERIAL NOT NULL ,
+
+	/* The type of update this row is about */
+	updtype_id		INT NOT NULL ,
+
+	/* The type of target for the update */
+	updtgt_id		INT NOT NULL ,
+
+	/* The update row's current state */
+	update_state	sys.update_state_type
+						NOT NULL
+						DEFAULT 'FUTURE' ,
+
+	/* The tick identifier corresponding to the last game update in which
+	 * this row was processed.
+	 */
+	update_last		BIGINT NOT NULL
+						DEFAULT -1 ,
+
+	/* The primary key includes the automatically generated identifier but
+	 * also the type and target type.
+	 */
+	PRIMARY KEY( update_id , updtype_id , updtgt_id )
+);
+
+ALTER TABLE sys.updates
+	ADD CONSTRAINT fk_update_type
+		FOREIGN KEY ( updtype_id , updtgt_id ) REFERENCES sys.update_types
+			ON DELETE CASCADE;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/003-updates.sql b/legacyworlds-server-data/db-structure/parts/040-functions/003-updates.sql
new file mode 100644
index 0000000..0a2e5f3
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/003-updates.sql
@@ -0,0 +1,571 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates - support functions for update definitions
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Type that represents a field from a primary key
+ * ------------------------------------------------
+ * 
+ * This type represents both the field itself (as a name) and its type.
+ */
+DROP TYPE IF EXISTS sys.pk_field_type CASCADE;
+CREATE TYPE sys.pk_field_type AS (
+	f_name			NAME ,
+	f_type			NAME 
+);
+
+
+/*
+ * Get a table's primary key fields and field types
+ * -------------------------------------------------
+ * 
+ * This function will list all fields from a table's primary key along with
+ * their types.
+ * 
+ * Parameters:
+ *		_schema_name		The name of the schema the table resides in
+ *		_table_name			The name of the table
+ *
+ * Returns:
+ * 		?					A set of sys.pk_field_type records
+ */
+DROP FUNCTION IF EXISTS sys.get_table_pkey( NAME , NAME ) CASCADE;
+CREATE FUNCTION sys.get_table_pkey( _schema_name NAME , _table_name NAME )
+	RETURNS SETOF sys.pk_field_type
+	LANGUAGE SQL
+	STRICT IMMUTABLE
+	SECURITY INVOKER
+AS $get_table_pkey$
+
+	SELECT _attr.attname AS f_name , _type.typname AS f_type
+
+		FROM pg_namespace _schema
+			INNER JOIN pg_class _table
+				ON _table.relnamespace = _schema.oid
+			INNER JOIN (
+					SELECT indrelid , unnest( indkey ) AS indattnum
+						FROM pg_index WHERE indisprimary
+				) _index ON _index.indrelid = _table.oid
+			INNER JOIN pg_attribute _attr
+				ON _attr.attrelid = _table.oid
+					AND _attr.attnum = _index.indattnum
+			INNER JOIN pg_type _type
+				ON _type.oid = _attr.atttypid
+
+		WHERE _schema.nspname = $1
+			AND _table.relname = $2
+
+		ORDER BY _index.indattnum;
+
+$get_table_pkey$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.get_table_pkey( NAME , NAME )
+	FROM PUBLIC;
+
+
+
+/*
+ * Automatic update row insertion
+ * -------------------------------
+ * 
+ * This trigger function is added to all type-specific update tables. It is
+ * called before insertions, and will make sure a generic update row exists
+ * for the new row.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_speupd_before_insert( ) CASCADE;
+CREATE FUNCTION sys.tgf_speupd_before_insert( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $tgf_speupd_before_insert$
+
+DECLARE
+	_update_id	BIGINT;
+
+BEGIN
+	
+	INSERT INTO sys.updates ( updtype_id , updtgt_id )
+		VALUES ( NEW.updtype_id , NEW.updtgt_id )
+		RETURNING update_id INTO _update_id;
+
+	NEW.update_id := _update_id;
+	RETURN NEW;
+END;
+$tgf_speupd_before_insert$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_speupd_before_insert( )
+	FROM PUBLIC;
+
+
+
+/*
+ * Automatic update row removal
+ * -----------------------------
+ * 
+ * This trigger function is added to all type-specific update tables. It is
+ * called once a row has been deleted, and will remove the corresponding
+ * generic update row.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_speupd_after_delete( ) CASCADE;
+CREATE FUNCTION sys.tgf_speupd_after_delete( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $tgf_speupd_after_delete$
+BEGIN
+
+	DELETE FROM sys.updates
+		WHERE update_id = OLD.update_id
+			AND updtype_id = OLD.updtype_id
+			AND updtgt_id = OLD.updtgt_id;
+
+	RETURN OLD;
+END;
+$tgf_speupd_after_delete$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_speupd_after_delete( )
+	FROM PUBLIC;
+
+
+
+/*
+ * Add missing update rows
+ * ------------------------
+ * 
+ * This function adds rows missing from the update and specific update tables
+ * for a given target type.
+ * 
+ * Parameters:
+ *		_target_type	The identifier of the target type for which missing
+ *							rows are to be inserted.
+ */
+DROP FUNCTION IF EXISTS sys.insert_missing_updates( INT ) CASCADE;
+CREATE FUNCTION sys.insert_missing_updates( _target_type INT )
+		RETURNS VOID
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $insert_missing_updates$
+
+DECLARE
+	_t_schema		TEXT;
+	_t_table		TEXT;
+	_field_list		TEXT;
+	_query			TEXT;
+
+BEGIN
+	SELECT INTO _t_schema , _t_table updtgt_schema , updtgt_table
+		FROM sys.update_targets
+		WHERE updtgt_id = _target_type;
+	SELECT INTO _field_list array_to_string( array_agg( f_name ) , ' , ' )
+		FROM sys.get_table_pkey( _t_schema , _t_table );
+
+	_query := 'INSERT INTO "' || _t_schema || '"."' || _t_table || '_updates" '
+		|| '( updtgt_id , updtype_id , ' || _field_list || ') '
+		|| 'SELECT updtgt_id , updtype_id , ' || _field_list
+			|| ' FROM sys.update_types CROSS JOIN "'
+		|| _t_schema || '"."' || _t_table || '" LEFT OUTER JOIN "'
+		|| _t_schema || '"."' || _t_table
+		|| '_updates" USING ( updtgt_id , updtype_id , ' || _field_list
+		|| ') WHERE update_id IS NULL AND updtgt_id = ' || _target_type;
+	EXECUTE _query;
+
+END;
+$insert_missing_updates$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.insert_missing_updates( INT )
+	FROM PUBLIC;
+
+
+
+/*
+ * Trigger function that adds missing update rows
+ * ----------------------------------------------
+ * 
+ * This function calls sys.insert_missing_updates. The main difference between
+ * the original function and this one is that the former cannot be used in
+ * triggers, while the latter is meant to be used as a trigger on the target
+ * table.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_insert_missing_updates( ) CASCADE;
+CREATE FUNCTION sys.tgf_insert_missing_updates( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $tgf_insert_missing_updates$
+BEGIN
+	IF TG_NARGS <> 1 THEN
+		RAISE EXCEPTION 'This trigger function requires one argument';
+	END IF;
+	PERFORM sys.insert_missing_updates( TG_ARGV[ 0 ]::INT );
+	RETURN NULL;
+END;
+$tgf_insert_missing_updates$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_insert_missing_updates( )
+	FROM PUBLIC;
+
+
+/*
+ * Handle new update types
+ * ------------------------
+ * 
+ * This function is triggered when a new update type is created; it will insert
+ * the update rows for the new type.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_updtype_after_insert_row( ) CASCADE;
+CREATE FUNCTION sys.tgf_updtype_after_insert_row( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $tgf_updtype_after_insert$
+BEGIN
+	PERFORM sys.insert_missing_updates( NEW.updtgt_id );
+	RETURN NEW;
+END;
+$tgf_updtype_after_insert$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_updtype_after_insert_row( )
+	FROM PUBLIC;
+
+CREATE TRIGGER tg_updtype_after_insert_row
+	AFTER INSERT ON sys.update_types
+	FOR EACH ROW EXECUTE PROCEDURE sys.tgf_updtype_after_insert_row( );
+
+	
+/*
+ * Update type definition check
+ * -----------------------------
+ * 
+ * This trigger function is called when a new update type is added or when
+ * an update type's stored procedure is updated. It makes sure that the
+ * corresponding stored procedure actually exists.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_check_update_type_proc( ) CASCADE;
+CREATE FUNCTION sys.tgf_check_update_type_proc( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $tgf_check_update_type_proc$
+BEGIN
+
+	IF NEW.updtype_proc_name IS NULL THEN
+		RETURN NEW;
+	END IF;
+	
+	PERFORM 1
+		FROM pg_namespace _schema
+			INNER JOIN pg_proc _proc
+				ON _proc.pronamespace = _schema.oid
+		WHERE _schema.nspname = 'sys'
+			AND _proc.proname = NEW.updtype_proc_name;
+	IF NOT FOUND THEN
+		RAISE EXCEPTION 'Update type % has invalid update function sys.%' ,
+			NEW.updtype_name , NEW.updtype_proc_name;
+	END IF;
+
+	RETURN NEW;
+END;
+$tgf_check_update_type_proc$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_check_update_type_proc( )
+	FROM PUBLIC;
+
+CREATE TRIGGER tg_check_update_type_proc_insert
+	BEFORE INSERT ON sys.update_types
+	FOR EACH ROW EXECUTE PROCEDURE sys.tgf_check_update_type_proc( );
+CREATE TRIGGER tg_check_update_type_proc_update
+	BEFORE UPDATE OF updtype_proc_name ON sys.update_types
+	FOR EACH ROW EXECUTE PROCEDURE sys.tgf_check_update_type_proc( );
+
+
+/*
+ * Trigger that reorders the update types
+ * ---------------------------------------
+ * 
+ * This function will update the ordering field of update types whenever rows
+ * are inserted or deleted.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_reorder_update_types( ) CASCADE;
+CREATE FUNCTION sys.tgf_reorder_update_types( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $tgf_reorder_update_types$
+
+DECLARE
+	_max_ordering	INT;
+
+BEGIN
+	
+	SELECT INTO _max_ordering MAX( updtype_ordering ) FROM sys.update_types;
+	IF _max_ordering IS NOT NULL THEN
+		UPDATE sys.update_types
+			SET updtype_ordering = updtype_ordering + _max_ordering * 2;
+	END IF;
+
+	UPDATE sys.update_types
+		SET updtype_ordering = updtype_rownumber * 2
+		FROM (
+			SELECT updtype_id AS id,
+					row_number( ) OVER (
+							ORDER BY updtype_ordering
+						) AS updtype_rownumber
+				FROM sys.update_types
+		) _row
+		WHERE updtype_id = _row.id;
+
+		RETURN NULL;
+END;
+$tgf_reorder_update_types$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_reorder_update_types( )
+	FROM PUBLIC;
+
+CREATE TRIGGER tg_updtype_after_insert_stmt
+	AFTER INSERT ON sys.update_types
+	FOR EACH STATEMENT EXECUTE PROCEDURE sys.tgf_reorder_update_types( );
+
+CREATE TRIGGER tg_updtype_after_delete_stmt
+	AFTER DELETE ON sys.update_types
+	FOR EACH STATEMENT EXECUTE PROCEDURE sys.tgf_reorder_update_types( );
+
+
+
+/*
+ * Check target tables
+ * --------------------
+ * 
+ * Before a new row is inserted into sys.update_targets, make sure
+ * the target table it contains actually exists.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_updtgt_before_insert( ) CASCADE;
+CREATE FUNCTION sys.tgf_updtgt_before_insert( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $tgf_updtgt_before_insert$
+BEGIN
+	PERFORM 1
+		FROM pg_class pc
+			INNER JOIN pg_namespace pn
+				ON pn.oid = pc.relnamespace
+			INNER JOIN pg_roles pr
+				ON pr.oid = pc.relowner
+		WHERE pn.nspname = NEW.updtgt_schema
+			AND pc.relname = NEW.updtgt_table
+			AND pc.reltype <> 0 AND pc.relkind = 'r'
+			AND pr.rolname <> 'postgres';
+	IF NOT FOUND THEN
+		RAISE EXCEPTION 'Update target table %.% not found' ,
+				NEW.updtgt_schema , NEW.updtgt_table  
+			USING ERRCODE = 'foreign_key_violation';
+	END IF;
+	
+	RETURN NEW;
+END;
+$tgf_updtgt_before_insert$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_updtgt_before_insert( )
+	FROM PUBLIC;
+
+CREATE TRIGGER tg_updtgt_before_insert
+	BEFORE INSERT ON sys.update_targets
+	FOR EACH ROW EXECUTE PROCEDURE sys.tgf_updtgt_before_insert( );
+
+
+
+/*
+ * Create update list and triggers for target table
+ * -------------------------------------------------
+ *
+ * After a new update type has been registered, a table listing updates
+ * as well as a set of triggers on the target table need to be created.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_updtgt_after_insert( ) CASCADE;
+CREATE FUNCTION sys.tgf_updtgt_after_insert( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+	AS $tgf_updtgt_after_insert$
+
+DECLARE
+	_table_name		TEXT;
+	_query			TEXT;
+	_field_name		NAME;
+	_field_type		NAME;
+	_field_list		TEXT;
+
+BEGIN
+	_table_name := '"' || NEW.updtgt_schema || '"."' || NEW.updtgt_table || '_updates"';
+	_query := 'CREATE TABLE ' || _table_name || $_table_base_fields$(
+			update_id		BIGINT NOT NULL ,
+			updtype_id		INT NOT NULL ,
+			updtgt_id		INT NOT NULL
+		$_table_base_fields$;
+
+	-- List target table's primary key fields
+	_field_list := '';
+	FOR _field_name , _field_type IN
+			SELECT * FROM sys.get_table_pkey( NEW.updtgt_schema , NEW.updtgt_table )
+	LOOP
+		_query := _query || ', "' || _field_name || '" "' || _field_type
+				|| '" NOT NULL';
+		IF _field_list <> '' THEN
+			_field_list := _field_list || ' , ';
+		END IF;
+		_field_list := _field_list || '"' || _field_name || '"';
+	END LOOP;
+	
+	_query := _query ||
+		' , PRIMARY KEY ( updtgt_id , updtype_id , update_id , '
+		|| _field_list || ' ) , CHECK ( updtgt_id = ' || NEW.updtgt_id || ' ) )';
+	EXECUTE _query;
+
+	-- Add foreign keys
+	_query := 'ALTER TABLE ' || _table_name
+		|| ' ADD CONSTRAINT fk_upd_' || NEW.updtgt_table || '_update '
+			|| 'FOREIGN KEY ( update_id , updtype_id , updtgt_id ) REFERENCES sys.updates '
+				|| 'ON DELETE CASCADE , '
+		|| ' ADD CONSTRAINT fk_upd_' || NEW.updtgt_table || '_target '
+			|| 'FOREIGN KEY ( ' || _field_list
+				|| ' ) REFERENCES "' || NEW.updtgt_schema || '"."' || NEW.updtgt_table
+				|| '" ON DELETE CASCADE';
+	EXECUTE _query;
+
+	-- Create automatic update creation/deletion triggers
+	_query := 'CREATE TRIGGER tg_speupd_before_insert BEFORE INSERT ON ' || _table_name
+		|| ' FOR EACH ROW EXECUTE PROCEDURE sys.tgf_speupd_before_insert( )';
+	EXECUTE _query;
+	_query := 'CREATE TRIGGER tg_speupd_after_delete AFTER DELETE ON ' || _table_name
+		|| ' FOR EACH ROW EXECUTE PROCEDURE sys.tgf_speupd_after_delete( )';
+	EXECUTE _query;
+	
+	-- Create triggers that will insert/delete update rows when new items are added/removed
+	_query := 'CREATE TRIGGER tg_speupd_after_insert AFTER INSERT ON "'
+		||	NEW.updtgt_schema || '"."' || NEW.updtgt_table
+		|| '" FOR EACH STATEMENT EXECUTE PROCEDURE sys.tgf_insert_missing_updates( '
+		|| NEW.updtgt_id || ' )';
+	EXECUTE _query;
+
+	RETURN NEW;
+END;
+$tgf_updtgt_after_insert$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_updtgt_after_insert( )
+	FROM PUBLIC;
+
+CREATE TRIGGER tg_updtgt_after_insert
+	AFTER INSERT ON sys.update_targets
+	FOR EACH ROW EXECUTE PROCEDURE sys.tgf_updtgt_after_insert( );
+
+
+/*
+ * Trigger function that deletes specific update tables
+ * -----------------------------------------------------
+ *
+ * This trigger function is called after a row has been deleted from the
+ * update target definitions table. It will destroy the corresponding table.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_updtgt_after_delete( );
+CREATE FUNCTION sys.tgf_updtgt_after_delete( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $tgf_updtgt_after_delete$
+DECLARE
+	_query	TEXT;
+BEGIN
+	_query := 'DROP TABLE "' || OLD.updtgt_schema || '"."' || OLD.updtgt_table || '_updates"';
+	EXECUTE _query;
+	RETURN NULL;
+END;
+$tgf_updtgt_after_delete$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.tgf_updtgt_after_delete( )
+	FROM PUBLIC;
+
+CREATE TRIGGER tg_updtgt_after_delete
+	AFTER DELETE ON sys.update_targets
+	FOR EACH ROW EXECUTE PROCEDURE sys.tgf_updtgt_after_delete( );
+
+
+
+/*
+ * Stored update type registration
+ * --------------------------------
+ *
+ * This function can be called to register an update type that uses a stored
+ * procedure. The new update type is added at the end of the list of updates.
+ * 
+ * Since this function is meant to be used from the SQL definition code only,
+ * it does not handle errors and will raise exceptions when something goes
+ * wrong.
+ * 
+ * Parameters:
+ *		_target			The name of the target for this update type
+ *		_name			The name of the update type
+ *		_description	The update type's description
+ *		_proc			The name of the stored procedure
+ */
+DROP FUNCTION IF EXISTS sys.register_update_type( TEXT , TEXT , TEXT , NAME );
+CREATE FUNCTION sys.register_update_type( _target TEXT , _name TEXT , _description TEXT , _proc NAME )
+		RETURNS VOID
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $register_update_type$
+
+DECLARE
+	_target_id		INT;
+	_max_ordering	INT;
+
+BEGIN
+	SELECT INTO _target_id updtgt_id
+		FROM sys.update_targets
+		WHERE updtgt_name = _target;
+
+	SELECT INTO _max_ordering MAX( updtype_ordering )
+		FROM sys.update_types;
+	IF _max_ordering IS NULL THEN
+		_max_ordering := 1;
+	END IF;
+
+	INSERT INTO sys.update_types(
+			updtgt_id , updtype_name , updtype_ordering ,
+			updtype_description , updtype_proc_name
+		) VALUES (
+			_target_id , _name , _max_ordering + 1 ,
+			_description , _proc
+		);
+END;
+$register_update_type$;
+
+
+REVOKE EXECUTE
+	ON FUNCTION sys.register_update_type( TEXT , TEXT , TEXT , NAME )
+	FROM PUBLIC;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 7963876..63ac90e 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -45,18 +45,6 @@ BEGIN
 	-- Add empire resources
 	INSERT INTO emp.resources ( empire_id , resource_name_id )
 		SELECT _name_id , resource_name_id FROM defs.resources;
-
-	-- Add empire update records
-	FOR _update_type IN SELECT _type
-			FROM unnest( enum_range( NULL::update_type ) ) AS _type
-			WHERE _type::text LIKE 'EMPIRE_%'
-	LOOP
-		INSERT INTO sys.updates( gu_type )
-			VALUES ( _update_type )
-			RETURNING id INTO _update;
-		INSERT INTO emp.updates ( update_id , empire_id )
-			VALUES ( _update , _name_id );
-	END LOOP;
 END;
 $$ LANGUAGE plpgsql;
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
index 79a7f62..ca6718e 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
@@ -196,17 +196,6 @@ BEGIN
 	-- FIXME: for now, just stick data about resources in the appropriate table
 	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
 		SELECT pnid , resource_name_id FROM defs.resources;
-
-	-- Add planet update records
-	FOR utp IN SELECT x FROM unnest( enum_range( NULL::update_type ) ) AS x
-					WHERE x::text LIKE 'PLANET_%'
-	LOOP
-		INSERT INTO sys.updates( gu_type )
-			VALUES ( utp )
-			RETURNING id INTO uid;
-		INSERT INTO verse.updates ( update_id , planet_id )
-			VALUES ( uid , pnid );
-	END LOOP;
 END;
 $$ LANGUAGE plpgsql;
 
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql b/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql
index 9133bf8..7ab8252 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql
@@ -8,17 +8,29 @@
 
 
 
---
--- Start a tick
---
-
-CREATE OR REPLACE FUNCTION sys.start_tick( OUT tick_id BIGINT )
+/*
+ * Start a game update cycle
+ * --------------------------
+ *
+ * This function prepares the execution of a cycle of game updates. It will
+ * try to find the identifier of the next update, and mark all game updates
+ * as requiring an update for this identifier.
+ * 
+ * Returns:
+ *		tick_id		The identifier of the new update cycle, or NULL if an
+ *						update was already in progress. 
+ */
+DROP FUNCTION IF EXISTS sys.start_tick( ) CASCADE;
+CREATE FUNCTION sys.start_tick( OUT tick_id BIGINT )
+		LANGUAGE PLPGSQL
 		STRICT VOLATILE
 		SECURITY DEFINER
-	AS $$
+	AS $start_tick$
+
 DECLARE
 	n_tick	BIGINT;
 	c_tick	BIGINT;
+
 BEGIN
 	-- Get next / current tick
 	SELECT INTO n_tick , c_tick next_tick , current_tick
@@ -31,17 +43,27 @@ BEGIN
 	END IF;
 
 	-- Prepare game updates
-	UPDATE sys.updates SET last_tick = n_tick , status = 'FUTURE'
-		WHERE last_tick < n_tick;
+	UPDATE sys.updates
+		SET update_last = n_tick ,
+			update_state = 'FUTURE'
+		WHERE update_last < n_tick;
 
 	-- Update system status
-	UPDATE sys.status SET current_tick = n_tick , next_tick = n_tick + 1;
+	UPDATE sys.status
+		SET current_tick = n_tick ,
+			next_tick = n_tick + 1;
 	
 	tick_id := n_tick;
-END;
-$$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION sys.start_tick( ) TO :dbuser;
+END;
+$start_tick$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.start_tick( )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION sys.start_tick( )
+	TO :dbuser;
 
 
 
@@ -64,17 +86,29 @@ $$ LANGUAGE plpgsql;
 
 
 
---
--- Check if a tick got "stuck"
---
-
-CREATE OR REPLACE FUNCTION sys.check_stuck_tick( OUT tick_id BIGINT )
+/*
+ * Check if a game update cycle got "stuck"
+ * -----------------------------------------
+ * 
+ * Check sys.status for an in-progress game update identifier. If there is
+ * one, check if there are further updates to execute in the cycle in
+ * question.
+ * 
+ * Returns:
+ *		tick_id		The stuck tick's identifier, or NULL if there is no stuck
+ *						tick or if maintenance mode is enabled  
+ */
+DROP FUNCTION IF EXISTS sys.check_stuck_tick( ) CASCADE;
+CREATE FUNCTION sys.check_stuck_tick( OUT tick_id BIGINT )
+		LANGUAGE PLPGSQL
 		STRICT VOLATILE
 		SECURITY DEFINER
-	AS $$
+	AS $check_stuck_tick$
+
 DECLARE
 	c_tick	BIGINT;
 	u_count	INT;
+
 BEGIN
 	-- Get next / current tick
 	SELECT INTO c_tick current_tick
@@ -87,71 +121,123 @@ BEGIN
 	END IF;
 	
 	-- Are there any updates left?
-	SELECT INTO u_count count(*) FROM sys.updates
-		WHERE status = 'FUTURE' AND last_tick = c_tick;
+	SELECT INTO u_count count(*)
+		FROM sys.updates
+		WHERE update_state = 'FUTURE'
+			AND update_last = c_tick;
 	IF u_count = 0 THEN
 		PERFORM sys.end_tick( c_tick );
 		tick_id := NULL;
 	ELSE
 		tick_id := c_tick;
 	END IF;
+
 END;
-$$ LANGUAGE plpgsql;
+$check_stuck_tick$;
 
-GRANT EXECUTE ON FUNCTION sys.check_stuck_tick( ) TO :dbuser;
+REVOKE EXECUTE
+	ON FUNCTION sys.check_stuck_tick( )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION sys.check_stuck_tick( )
+	TO :dbuser;
 
 
 
---
--- Process game updates
---
--- Parameters:
---	c_tick	Current tick
---
--- Returns:
---	TRUE if the function must be called again, FALSE otherwise
---
-
-CREATE OR REPLACE FUNCTION sys.process_updates( IN c_tick BIGINT , OUT has_more BOOLEAN )
+/*
+ * Process game updates
+ * ---------------------
+ * 
+ * This function checks for game update elements, marking some of them for
+ * processing depending on their type. If there are no more updates to run,
+ * end the update cycle; otherwise, depending on the type of update, process
+ * the items internally or return a value that indicates they are to be
+ * processed by the external Java code.
+ * 
+ * Parameters:
+ *		_current_update		Current game update's identifier
+ *
+ * Returns:
+ *		_has_more			TRUE if the function must be called again, FALSE
+ *								otherwise
+ *		_process_externally	NULL if there is no update to process or if the
+ *								updates were processed in the database, or
+ *								the name of the update type if external
+ *								processing is needed.
+ */
+DROP FUNCTION IF EXISTS sys.process_updates( BIGINT ) CASCADE;
+CREATE FUNCTION sys.process_updates(
+			IN _current_update BIGINT ,
+			OUT _has_more BOOLEAN ,
+			OUT _process_externally TEXT )
+		LANGUAGE PLPGSQL
 		STRICT VOLATILE
 		SECURITY DEFINER
-	AS $$
+	AS $process_updates$
+
 DECLARE
-	b_size	INT;
-	p_utype	update_type;
-	utype	update_type;
-	uid		BIGINT;
+	_current_type	INT;
+	_batch_size		INT;
+	_type_name		TEXT;
+	_proc_name		NAME;
+
 BEGIN
-	b_size := sys.get_constant( 'game.batchSize' );
-	p_utype := NULL;
+	-- Mark all entries that were being processed as having been processed
+	UPDATE sys.updates
+		SET update_state = 'PROCESSED'
+		WHERE update_state = 'PROCESSING'
+			AND update_last = _current_update;
 
-	-- Mark at most b_size entries as being updated
-	FOR uid , utype IN SELECT id , gu_type FROM sys.updates
-						WHERE last_tick = c_tick AND status = 'FUTURE'
-						ORDER BY gu_type LIMIT b_size
-	LOOP
-		IF p_utype IS NULL THEN
-			p_utype := utype;
+	-- Find the next type of update to process and its specific parameters
+	SELECT INTO _current_type , _batch_size , _type_name , _proc_name
+			updtype_id , updtype_batch_size , updtype_name , updtype_proc_name
+		FROM sys.update_types
+			INNER JOIN sys.updates
+				USING ( updtype_id , updtgt_id )
+		WHERE update_state = 'FUTURE'
+			AND update_last = _current_update
+		ORDER BY updtype_ordering
+		LIMIT 1;
+
+	_has_more := FOUND;
+	IF _has_more THEN
+		-- Check batch size
+		IF _batch_size IS NULL THEN
+			_batch_size := sys.get_constant( 'game.batchSize' ); 
 		END IF;
-		EXIT WHEN utype <> p_utype;
-		UPDATE sys.updates SET status = 'PROCESSING' WHERE id = uid;
-	END LOOP;
+	
+		-- Mark at most _batch_size entries of the right type as being updated
+		UPDATE sys.updates
+			SET update_state = 'PROCESSING'
+			WHERE update_id IN (
+					SELECT update_id FROM sys.updates
+						WHERE updtype_id = _current_type
+							AND update_state = 'FUTURE'
+							AND update_last = _current_update
+						LIMIT _batch_size
+				);
 
-	has_more := p_utype IS NOT NULL;
-	IF has_more THEN
-		-- Execute actual updates
-		EXECUTE 'SELECT sys.process_' || lower( p_utype::TEXT ) || '_updates( $1 )'
-			USING c_tick;
-		UPDATE sys.updates SET status = 'PROCESSED'
-			WHERE status = 'PROCESSING' AND last_tick = c_tick;
+		IF _proc_name IS NULL THEN
+			-- External processing is required
+			_process_externally := _type_name;
+		ELSE
+			-- Process updates using a stored procedure
+			EXECUTE 'SELECT sys."' || _proc_name || '"( $1 )'
+				USING _current_update;
+		END IF;
 	ELSE
-		-- If nothing was found, we're done
-		PERFORM sys.end_tick( c_tick );
+		-- No updates left to run
+		PERFORM sys.end_tick( _current_update );
 	END IF;
 END;
-$$ LANGUAGE plpgsql;
+$process_updates$;
 
-GRANT EXECUTE ON FUNCTION sys.process_updates( BIGINT ) TO :dbuser;
+REVOKE EXECUTE
+	ON FUNCTION sys.process_updates( BIGINT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION sys.process_updates( BIGINT )
+	TO :dbuser;
 
 
 
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/005-core-update-targets.sql b/legacyworlds-server-data/db-structure/parts/050-updates/005-core-update-targets.sql
new file mode 100644
index 0000000..8fa557e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/005-core-update-targets.sql
@@ -0,0 +1,16 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates - core update targets
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+INSERT INTO sys.update_targets (
+		updtgt_name , updtgt_schema , updtgt_table
+	) VALUES (
+		'Planets' , 'verse' , 'planets'
+	) , (
+		'Empires' , 'emp' , 'empires'
+	);
+	
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql b/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql
index a0bb8b1..26e3088 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql
@@ -18,13 +18,14 @@ DECLARE
 	c_debt	REAL;
 BEGIN
 	-- Lock empires for update
-	PERFORM e.name_id FROM sys.updates su
-			INNER JOIN emp.updates eu 
-				ON eu.update_id = su.id
+	PERFORM e.name_id
+		FROM sys.updates su
+			INNER JOIN emp.empires_updates eu 
+				USING ( updtgt_id , updtype_id , update_id )
 			INNER JOIN emp.empires e
-				ON eu.empire_id = e.name_id
-		WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-			AND su.gu_type = 'EMPIRE_MONEY'
+				USING ( name_id )
+		WHERE su.update_last = c_tick
+			AND su.update_state = 'PROCESSING'
 		FOR UPDATE OF e;
 
 	-- Select all money-related data from empires being updated
@@ -32,14 +33,16 @@ BEGIN
 						( pov.planet_income - pov.planet_upkeep ) AS p_money ,
 						fov.fleet_upkeep AS f_money , ( v.status = 'PROCESSED' ) AS on_vacation
 					FROM sys.updates su
-						INNER JOIN emp.updates eu ON eu.update_id = su.id
-						INNER JOIN emp.empires e ON eu.empire_id = e.name_id
+						INNER JOIN emp.empires_updates eu 
+							USING ( updtgt_id , updtype_id , update_id )
+						INNER JOIN emp.empires e
+							USING ( name_id )
 						INNER JOIN emp.fleets_overview fov ON fov.empire = e.name_id
 						INNER JOIN emp.planets_overview pov ON pov.empire = e.name_id
 						INNER JOIN naming.empire_names en ON en.id = e.name_id
 						LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
-					WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-						AND su.gu_type = 'EMPIRE_MONEY'
+					WHERE su.update_last = c_tick
+						AND su.update_state = 'PROCESSING'
 	LOOP
 		-- Compute new cash reserve
 		c_cash := 0;
@@ -78,4 +81,12 @@ BEGIN
 			WHERE name_id = rec.id;
 	END LOOP;
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+
+SELECT sys.register_update_type( 'Empires' , 'EmpireMoney' ,
+		'Empires'' money is being updated using the previous update''s results. '
+			|| 'This update type should disappear, as everything it does will '
+		 	|| 'be replaced by the resources update.' ,
+		'process_empire_money_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql b/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
index 89c40e2..6004945 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
@@ -42,17 +42,16 @@ AS $process_empire_resources_updates$
 				END
 
 		FROM sys.updates _upd_sys
-			INNER JOIN emp.updates _upd_emp
-				ON _upd_emp.update_id = _upd_sys.id
+			INNER JOIN emp.empires_updates _upd_emp
+				USING ( updtgt_id , updtype_id , update_id )
 			INNER JOIN emp.planets _emp_planets
-				USING ( empire_id )
+				ON empire_id = name_id
 			INNER JOIN verse.planet_resources _pl_resources
 				USING ( planet_id )
 
-		WHERE _upd_sys.last_tick = $1
-			AND _upd_sys.status = 'PROCESSING'
-			AND _upd_sys.gu_type = 'EMPIRE_RESOURCES'
-			AND _emp_resources.empire_id = _upd_emp.empire_id
+		WHERE _upd_sys.update_last = $1
+			AND _upd_sys.update_state = 'PROCESSING'
+			AND _emp_resources.empire_id = _upd_emp.name_id
 			AND _emp_resources.resource_name_id = _pl_resources.resource_name_id;
 
 $process_empire_resources_updates$ LANGUAGE SQL;
@@ -61,3 +60,10 @@ $process_empire_resources_updates$ LANGUAGE SQL;
 REVOKE EXECUTE
 	ON FUNCTION sys.process_empire_resources_updates( BIGINT )
 	FROM PUBLIC;
+
+
+
+SELECT sys.register_update_type( 'Empires' , 'EmpireResources' ,
+		'Empires'' resources are being updated using the previous update''s results. ' ,
+		'process_empire_resources_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
index d9e3cfe..152f83f 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
@@ -18,28 +18,31 @@ DECLARE
 	tu_rec		RECORD;
 BEGIN
 	-- Lock empires for update and planets for share
-	PERFORM e.name_id FROM sys.updates su
-			INNER JOIN emp.updates eu ON eu.update_id = su.id
-			INNER JOIN emp.empires e ON eu.empire_id = e.name_id
+	PERFORM e.name_id
+		FROM sys.updates _upd_sys
+			INNER JOIN emp.empires_updates eu
+				USING ( updtgt_id , updtype_id , update_id )
+			INNER JOIN emp.empires e USING ( name_id )
 			INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
 			INNER JOIN verse.planets p ON p.name_id = ep.planet_id
-		WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-			AND su.gu_type = 'EMPIRE_RESEARCH'
+		WHERE _upd_sys.update_last = c_tick
+			AND _upd_sys.update_state = 'PROCESSING'
 		FOR UPDATE OF e
 		FOR SHARE OF ep , p;
 
 	-- Process empires
 	FOR rec IN SELECT e.name_id AS id , ( v.status = 'PROCESSED' ) AS on_vacation ,
 						sum( p.population ) AS population
-					FROM sys.updates su
-						INNER JOIN emp.updates eu ON eu.update_id = su.id
-						INNER JOIN emp.empires e ON eu.empire_id = e.name_id
+					FROM sys.updates _upd_sys
+						INNER JOIN emp.empires_updates eu
+							USING ( updtgt_id , updtype_id , update_id )
+						INNER JOIN emp.empires e USING ( name_id )
 						INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
 						INNER JOIN verse.planets p ON p.name_id = ep.planet_id
 						INNER JOIN naming.empire_names en ON en.id = e.name_id
 						LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
-					WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-						AND su.gu_type = 'EMPIRE_RESEARCH'
+					WHERE _upd_sys.update_last = c_tick
+						AND _upd_sys.update_state = 'PROCESSING'
 					GROUP BY e.name_id , v.status
 	LOOP
 		-- Insert any missing tech line
@@ -83,4 +86,9 @@ BEGIN
 		END LOOP;
 	END LOOP;
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Empires' , 'EmpireResearch' ,
+		'Empire research points are being attributed to technologies.' ,
+		'process_empire_research_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql b/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql
index 7bcaa9f..aa1e849 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql
@@ -25,11 +25,13 @@ BEGIN
 	bld_dr := sys.get_constant( 'game.debt.buildings');
 
 	FOR empire, debt IN SELECT e.name_id AS id , e.debt
-					FROM sys.updates su
-						INNER JOIN emp.updates eu ON eu.update_id = su.id
-						INNER JOIN emp.empires e ON eu.empire_id = e.name_id
-					WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-						AND su.gu_type = 'EMPIRE_DEBT' AND e.debt > 0
+					FROM sys.updates _upd_sys
+						INNER JOIN emp.empires_updates eu
+							USING ( updtgt_id , updtype_id , update_id )
+						INNER JOIN emp.empires e USING ( name_id )
+					WHERE _upd_sys.update_last = c_tick
+						AND _upd_sys.update_state = 'PROCESSING'
+						AND e.debt > 0
 					FOR UPDATE
 	LOOP
 		PERFORM sys.write_log( 'EmpireDebt' , 'DEBUG'::log_level , 'Handling debt for empire #'
@@ -59,4 +61,9 @@ BEGIN
 		PERFORM verse.handle_debt( empire , upkeep , debt , bld_dr );
 	END LOOP;
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Empires' , 'EmpireDebt' ,
+		'The effects of empires'' debts are being computed.' ,
+		'process_empire_debt_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql b/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql
index c9dd59e..b5a939b 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql
@@ -7,7 +7,7 @@
 -- --------------------------------------------------------
 
 
-CREATE OR REPLACE FUNCTION sys.process_planet_fleet_arrivals_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_fleet_arrivals_updates( c_tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -18,14 +18,16 @@ DECLARE
 	f_ids	BIGINT[];
 BEGIN
 	-- Lock all records
-	PERFORM f.id FROM sys.updates su
-			INNER JOIN verse.updates vu ON vu.update_id = su.id
-			INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+	PERFORM 1
+		FROM sys.updates su
+			INNER JOIN verse.planets_updates vu
+				USING ( update_id , updtype_id , updtgt_id )
+			INNER JOIN verse.planets p
+				USING ( name_id )
 			INNER JOIN fleets.fleets f ON f.location_id = p.name_id
 			INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
 			INNER JOIN emp.empires e ON e.name_id = f.owner_id
-		WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_FLEET_ARRIVALS'
+		WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 					AND f.status = 'AVAILABLE' AND fm.time_left = 1
 		FOR UPDATE;
 
@@ -34,8 +36,10 @@ BEGIN
 					f.owner_id AS fleet_owner , ( v.status = 'PROCESSED' AND b.id IS NULL ) AS on_vacation ,
 					bool_or( f.attacking ) AS attacking
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN fleets.fleets f ON f.location_id = p.name_id
 					INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
 					LEFT OUTER JOIN emp.planets ep ON ep.planet_id = p.name_id
@@ -43,9 +47,8 @@ BEGIN
 					LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
 					LEFT OUTER JOIN battles.battles b
 						ON b.location_id = p.name_id AND b.last_tick IS NULL
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-							AND su.gu_type = 'PLANET_FLEET_ARRIVALS'
-							AND f.status = 'AVAILABLE' AND fm.time_left = 1
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
+						AND f.status = 'AVAILABLE' AND fm.time_left = 1
 				GROUP BY p.name_id , ep.empire_id , f.owner_id , v.status , b.id
 	LOOP
 		-- Fleets owned by the planet's owner are never attacking, same for fleets arriving on
@@ -103,16 +106,17 @@ BEGIN
 		SELECT f.location_id , ln.name , f.owner_id , fon.name ,
 				f.name , fs.power , f.attacking , fm.source_id , sn.name
 			FROM sys.updates su
-				INNER JOIN verse.updates vu ON vu.update_id = su.id
-				INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 				INNER JOIN fleets.fleets f ON f.location_id = p.name_id
 				INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
 				INNER JOIN fleets.stats_view fs ON fs.id = f.id
 				INNER JOIN naming.empire_names fon ON fon.id = f.owner_id
 				INNER JOIN naming.map_names ln ON ln.id = f.location_id
 				INNER JOIN naming.map_names sn ON sn.id = fm.source_id
-			WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-						AND su.gu_type = 'PLANET_FLEET_ARRIVALS'
+			WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 						AND f.status = 'AVAILABLE' AND fm.time_left = 1;
 
 	-- Delete movement records, set redeployment penalties, update battles
@@ -120,15 +124,16 @@ BEGIN
 					f.attacking AS attacking , b.id AS battle ,
 					f.location_id AS location
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN fleets.fleets f ON f.location_id = p.name_id
 					INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
 					INNER JOIN fleets.stats_view fs ON fs.id = f.id
 					LEFT OUTER JOIN battles.battles b
 						ON b.location_id = p.name_id AND b.last_tick IS NULL 
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-							AND su.gu_type = 'PLANET_FLEET_ARRIVALS'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 							AND f.status = 'AVAILABLE' AND fm.time_left = 1
 	LOOP
 		DELETE FROM fleets.movements
@@ -145,4 +150,10 @@ BEGIN
 	-- Send fleet arrival events
 	PERFORM events.commit_fleet_arrivals( c_tick );
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+
+SELECT sys.register_update_type( 'Planets' , 'FleetArrivals' ,
+		'Fleets which were one update away are arriving at their destinations.' ,
+		'process_fleet_arrivals_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql b/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql
index 184aa0a..2151325 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql
@@ -7,7 +7,7 @@
 -- --------------------------------------------------------
 
 
-CREATE OR REPLACE FUNCTION sys.process_planet_fleet_movements_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_fleet_movements_updates( c_tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -18,13 +18,14 @@ DECLARE
 BEGIN
 	-- Lock all records
 	PERFORM f.id FROM sys.updates su
-			INNER JOIN verse.updates vu ON vu.update_id = su.id
-			INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+			INNER JOIN verse.planets_updates vu
+				USING ( update_id , updtype_id , updtgt_id )
+			INNER JOIN verse.planets p
+				USING ( name_id )
 			INNER JOIN fleets.fleets f ON f.location_id = p.name_id
 			INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
 			INNER JOIN emp.empires e ON e.name_id = f.owner_id
-		WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-			AND su.gu_type = 'PLANET_FLEET_MOVEMENTS'
+		WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 			AND f.status = 'AVAILABLE'
 		FOR UPDATE;
 
@@ -36,8 +37,10 @@ BEGIN
 					rp.system_id AS is_ref_point_system , rp.orbit AS is_ref_point_orbit ,
 					rps.x AS is_ref_point_x , rps.y AS is_ref_point_y
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN verse.systems s ON s.id = p.system_id
 					INNER JOIN fleets.fleets f ON f.location_id = p.name_id
 					INNER JOIN fleets.ships fs ON fs.fleet_id = f.id
@@ -46,8 +49,7 @@ BEGIN
 					LEFT OUTER JOIN fleets.ms_system isms ON isms.movement_id = f.id
 					LEFT OUTER JOIN verse.planets rp ON isms.ref_point_id = rp.name_id
 					LEFT OUTER JOIN verse.systems rps ON rps.id = rp.system_id
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_FLEET_MOVEMENTS'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 					AND f.status = 'AVAILABLE' AND m.state_time_left = 1
 				GROUP BY f.id , s.x , s.y , s.id , isms.ref_point_id , isms.outwards ,
 					isms.past_ref_point , rp.system_id , rp.orbit , rps.x , rps.y
@@ -98,13 +100,19 @@ BEGIN
 			time_left = time_left - 1
 		WHERE fleet_id IN (
 			SELECT f.id FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN fleets.fleets f ON f.location_id = p.name_id
 					INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
 					INNER JOIN emp.empires e ON e.name_id = f.owner_id
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_FLEET_MOVEMENTS'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 					AND f.status = 'AVAILABLE' );
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'FleetMovements' ,
+		'Fleets are moving.' ,
+		'process_fleet_movements_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql b/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql
index 7249954..c7b1fca 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql
@@ -7,7 +7,7 @@
 -- --------------------------------------------------------
 
 
-CREATE OR REPLACE FUNCTION sys.process_planet_fleet_status_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_fleet_status_updates( c_tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -18,26 +18,27 @@ DECLARE
 BEGIN
 	-- Lock all records
 	PERFORM f.id FROM sys.updates su
-			INNER JOIN verse.updates vu ON vu.update_id = su.id
-			INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+			INNER JOIN verse.planets_updates vu
+				USING ( update_id , updtype_id , updtgt_id )
+			INNER JOIN verse.planets p
+				USING ( name_id )
 			INNER JOIN fleets.fleets f ON f.location_id = p.name_id
 			INNER JOIN emp.empires e ON e.name_id = f.owner_id
-		WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_FLEET_STATUS'
+		WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 		FOR UPDATE;
 
 	-- Fleet deployments
 	FOR rec IN SELECT f.id AS fleet , f.owner_id AS owner , f.location_id AS location ,
 						b.id AS battle
 					FROM sys.updates su
-						INNER JOIN verse.updates vu ON vu.update_id = su.id
-						INNER JOIN fleets.fleets f ON f.location_id = vu.planet_id
+						INNER JOIN verse.planets_updates vu
+							USING ( update_id , updtype_id , updtgt_id )
+						INNER JOIN fleets.fleets f ON f.location_id = vu.name_id
 							AND f.status = 'DEPLOYING' AND f.penalty = 1
 						INNER JOIN emp.empires e ON e.name_id = f.owner_id
 						LEFT OUTER JOIN battles.battles b
 							ON b.location_id = f.location_id AND b.last_tick IS NULL 
-					WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-						AND su.gu_type = 'PLANET_FLEET_STATUS'
+					WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 	LOOP
 		-- Add fleet to battle (will be ignored if battle is NULL)
 		PERFORM battles.add_fleet( rec.battle , rec.fleet , TRUE , c_tick );
@@ -62,16 +63,21 @@ BEGIN
 
 	-- Fleets that must become available
 	UPDATE fleets.fleets f SET status = 'AVAILABLE' , penalty = 0
-		FROM sys.updates su , verse.updates vu
-		WHERE vu.update_id = su.id AND f.location_id = vu.planet_id
-			AND f.penalty = 1 AND su.status = 'PROCESSING'
-			AND su.gu_type = 'PLANET_FLEET_STATUS' AND su.last_tick = c_tick;
+		FROM sys.updates su , verse.planets_updates vu
+		WHERE vu.update_id = su.update_id AND f.location_id = vu.name_id
+			AND f.penalty = 1
+			AND su.update_state = 'PROCESSING' AND su.update_last = c_tick;
 
 	-- Fleets that still have a penalty
 	UPDATE fleets.fleets f SET penalty = penalty - 1
-		FROM sys.updates su , verse.updates vu
-		WHERE vu.update_id = su.id AND f.location_id = vu.planet_id
-			AND f.penalty > 1 AND su.status = 'PROCESSING'
-			AND su.gu_type = 'PLANET_FLEET_STATUS' AND su.last_tick = c_tick;
+		FROM sys.updates su , verse.planets_updates vu
+		WHERE vu.update_id = su.update_id AND f.location_id = vu.name_id
+			AND f.penalty > 1
+			AND su.update_state = 'PROCESSING' AND su.update_last = c_tick;
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'FleetStatus' ,
+		'Fleet states (e.g. "deploying", "unavailable", etc...) are being  updated.' ,
+		'process_fleet_status_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql b/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql
index d944f65..2cfb2e9 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql
@@ -7,7 +7,7 @@
 -- --------------------------------------------------------
 
 
-CREATE OR REPLACE FUNCTION sys.process_planet_battle_start_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_battle_start_updates( c_tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -17,12 +17,14 @@ DECLARE
 BEGIN
 	FOR p_id IN SELECT p.name_id
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					LEFT OUTER JOIN battles.battles b
 						ON b.location_id = p.name_id AND b.last_tick IS NULL
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_BATTLE_START' AND b.location_id IS NULL
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
+					AND b.location_id IS NULL
 				FOR UPDATE OF p
 	LOOP
 		IF battles.check_start( p_id ) THEN
@@ -32,10 +34,13 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
+SELECT sys.register_update_type( 'Planets' , 'BattleStart' ,
+		'Battles are being started.' ,
+		'process_battle_start_updates'
+	);
 
 
-
-CREATE OR REPLACE FUNCTION sys.process_planet_battle_main_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_battle_main_updates( c_tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -63,13 +68,14 @@ BEGIN
 	FOR rec IN SELECT b.id AS battle , b.first_tick AS first_tick ,
 					b.location_id AS location , ( ph.current / p.population )::REAL AS happiness
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
 					INNER JOIN battles.battles b
 						ON b.location_id = p.name_id AND b.last_tick IS NULL
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_BATTLE_MAIN'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 				FOR UPDATE OF p , b
 	LOOP
 		PERFORM sys.write_log( 'BattleUpdate' , 'DEBUG'::log_level , 'Handling battle #' || rec.battle
@@ -115,10 +121,14 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
+SELECT sys.register_update_type( 'Planets' , 'BattleCompute' ,
+		'In-progress battles are being updated.' ,
+		'process_battle_main_updates'
+	);
 
 
 
-CREATE OR REPLACE FUNCTION sys.process_planet_battle_end_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_battle_end_updates( c_tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -129,12 +139,13 @@ DECLARE
 BEGIN
 	FOR rec IN SELECT b.id AS battle , b.location_id AS location
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN battles.battles b
 						ON b.location_id = p.name_id AND b.last_tick IS NULL
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_BATTLE_END'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 				FOR UPDATE OF p , b
 	LOOP
 		IF battles.get_fleets_power( rec.battle , c_tick , TRUE ) = 0 THEN
@@ -179,3 +190,7 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
+SELECT sys.register_update_type( 'Planets' , 'BattleEnd' ,
+		'Finished battles are being ended.' ,
+		'process_battle_end_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql b/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql
index 85128b7..1cdb636 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql
@@ -16,24 +16,31 @@ DECLARE
 	p_id	INT;
 BEGIN
 	-- Lock all records
-	PERFORM p.name_id FROM sys.updates su
-			INNER JOIN verse.updates vu ON vu.update_id = su.id
-			INNER JOIN verse.planets p ON p.name_id = vu.planet_id
-			INNER JOIN emp.planets ep ON p.name_id = vu.planet_id
-			INNER JOIN emp.empires e ON e.name_id = ep.empire_id
-			INNER JOIN emp.abandon a ON a.planet_id = p.name_id
-		WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-			AND su.gu_type = 'PLANET_ABANDON'
+	PERFORM p.name_id
+		FROM sys.updates su
+			INNER JOIN verse.planets_updates vu
+				USING ( update_id , updtype_id , updtgt_id )
+			INNER JOIN verse.planets p
+				USING ( name_id )
+			INNER JOIN emp.planets ep
+				ON ep.planet_id = p.name_id 
+			INNER JOIN emp.empires e
+				ON e.name_id = ep.empire_id
+			INNER JOIN emp.abandon a
+				ON a.planet_id = p.name_id
+		WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 		FOR UPDATE;
 
 	-- Handle planets where time has run out
 	FOR p_id IN SELECT p.name_id
 					FROM sys.updates su
-						INNER JOIN verse.updates vu ON vu.update_id = su.id
-						INNER JOIN verse.planets p ON p.name_id = vu.planet_id
-						INNER JOIN emp.abandon a ON a.planet_id = p.name_id
-					WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-							AND su.gu_type = 'PLANET_ABANDON' AND a.time_left = 1
+						INNER JOIN verse.planets_updates vu
+							USING ( update_id , updtype_id , updtgt_id )
+						INNER JOIN verse.planets p
+							USING ( name_id )
+							INNER JOIN emp.abandon a ON a.planet_id = p.name_id
+					WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
+						AND a.time_left = 1
 	LOOP
 		PERFORM emp.leave_planet( p_id );
 	END LOOP;
@@ -41,8 +48,14 @@ BEGIN
 	-- Update all abandon records
 	UPDATE emp.abandon a SET time_left = a.time_left - 1
 		FROM sys.updates su
-			INNER JOIN verse.updates vu ON vu.update_id = su.id
-		WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-			AND su.gu_type = 'PLANET_ABANDON' AND a.planet_id = vu.planet_id;
+			INNER JOIN verse.planets_updates vu
+				USING ( update_id , updtype_id , updtgt_id )
+		WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
+			AND a.planet_id = vu.name_id;
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'PlanetAbandon' ,
+		'Planets are being abandoned.' ,
+		'process_planet_abandon_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql b/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql
index 37470af..e9281bb 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql
@@ -46,8 +46,10 @@ BEGIN
 					qi.destroy AS destroy , qi.building_id AS building ,
 					b.work AS req_work , b.cost AS req_cost
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN emp.planets ep ON ep.planet_id = p.name_id
 					INNER JOIN emp.empires e ON e.name_id = ep.empire_id
 					INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
@@ -56,8 +58,7 @@ BEGIN
 					INNER JOIN tech.buildables b ON b.name_id = qi.building_id
 					INNER JOIN naming.empire_names en ON en.id = e.name_id
 					LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_CONSTRUCTION'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 					AND ( v.account_id IS NULL OR v.status <> 'PROCESSED' )
 				ORDER BY e.name_id , p.name_id , qi.queue_order
 				FOR UPDATE OF p , e , q , qi
@@ -215,3 +216,8 @@ BEGIN
 	END IF;
 END;
 $$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'CivilianConstruction' ,
+		'Civilian build queues are being processed.' ,
+		'process_planet_construction_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql b/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql
index f3c250a..1645fac 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql
@@ -44,8 +44,10 @@ BEGIN
 					qi.queue_order AS qorder , qi.amount AS amount ,
 					qi.ship_id AS ship , s.work AS req_work , s.cost AS req_cost
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN emp.planets ep ON ep.planet_id = p.name_id
 					INNER JOIN emp.empires e ON e.name_id = ep.empire_id
 					INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
@@ -54,8 +56,7 @@ BEGIN
 					INNER JOIN tech.buildables s ON s.name_id = qi.ship_id
 					INNER JOIN naming.empire_names en ON en.id = e.name_id
 					LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_MILITARY'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 					AND ( v.account_id IS NULL OR v.status <> 'PROCESSED' )
 				ORDER BY e.name_id , p.name_id , qi.queue_order
 				FOR UPDATE OF p , e , q , qi
@@ -225,3 +226,8 @@ BEGIN
 	DROP TABLE blt_ships;
 END;
 $$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'MilitaryConstruction' ,
+		'Military build queues are being processed.' ,
+		'process_planet_military_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql b/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql
index 2a805f8..16a3d77 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql
@@ -37,11 +37,12 @@ BEGIN
 					ph.target AS target , ph.current AS happy_pop ,
 					( ph.current / p.population )::REAL AS current
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_POPULATION'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 				FOR UPDATE OF p, ph
 	LOOP
 		IF round( rec.target / rel_ch ) = round( rec.current / rel_ch ) THEN
@@ -101,4 +102,9 @@ BEGIN
 		END IF;
 	END LOOP;
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'PlanetPopulation' ,
+		'Planet populations are being updated.' ,
+		'process_planet_population_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql b/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql
index 9e59a4b..a0a6db3 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql
@@ -16,7 +16,7 @@
  * Parameters:
  *		_tick		The identifier of the game update
  */
-CREATE OR REPLACE FUNCTION sys.process_planet_res_regen_updates( _tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_res_regen_updates( _tick BIGINT )
 	RETURNS VOID
 	STRICT VOLATILE
 	SECURITY INVOKER
@@ -31,17 +31,21 @@ AS $process_planet_res_regen_updates$
 			)
 
 		FROM sys.updates _upd_sys
-			INNER JOIN verse.updates _upd_verse
-				ON _upd_verse.update_id = _upd_sys.id
+			INNER JOIN verse.planets_updates _upd_verse
+				USING ( update_id , updtype_id , updtgt_id )
 
-		WHERE _upd_sys.last_tick = $1
-			AND _upd_sys.status = 'PROCESSING'
-			AND _upd_sys.gu_type = 'PLANET_RES_REGEN'
-			AND _provider.planet_id = _upd_verse.planet_id;
+		WHERE _upd_sys.update_last = $1
+			AND _upd_sys.update_state = 'PROCESSING'
+			AND _provider.planet_id = _upd_verse.name_id;
 
 $process_planet_res_regen_updates$ LANGUAGE SQL;
 
 
 REVOKE EXECUTE
-	ON FUNCTION sys.process_planet_res_regen_updates( BIGINT )
+	ON FUNCTION sys.process_res_regen_updates( BIGINT )
 	FROM PUBLIC;
+
+SELECT sys.register_update_type( 'Planets' , 'ResourceRegeneration' ,
+		'Resource providers are being regenerated.' ,
+		'process_res_regen_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql b/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql
index cbcc432..1822d7f 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql
@@ -20,13 +20,14 @@ BEGIN
 					( ph.current / p.population )::REAL AS happiness ,
 					( ea.planet_id IS NULL ) AS produces_income
 				FROM sys.updates su
-					INNER JOIN verse.updates vu ON vu.update_id = su.id
-					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+					INNER JOIN verse.planets_updates vu
+						USING ( update_id , updtype_id , updtgt_id )
+					INNER JOIN verse.planets p
+						USING ( name_id )
 					INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
 					INNER JOIN verse.planet_money pm ON pm.planet_id = p.name_id
 					LEFT OUTER JOIN emp.abandon ea ON ea.planet_id = p.name_id
-				WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-					AND su.gu_type = 'PLANET_MONEY'
+				WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
 				FOR UPDATE OF p, pm
 	LOOP
 		IF rec.produces_income THEN
@@ -42,4 +43,9 @@ BEGIN
 			WHERE planet_id = rec.id;
 	END LOOP;
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'PlanetMoney' ,
+		'Planets'' income and upkeep are being updated.' ,
+		'process_planet_money_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
index b641762..6f9c53f 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
@@ -127,12 +127,12 @@ AS $gu_pmc_get_data$
 			pmc_total AS total_weight
 
 		FROM sys.updates _upd_sys
-			INNER JOIN verse.updates _upd_verse
-				ON _upd_sys.id = _upd_verse.update_id
+			INNER JOIN verse.planets_updates _upd_verse
+				USING ( update_id , updtype_id , updtgt_id )
 			INNER JOIN verse.planets _planet
-				ON _planet.name_id = _upd_verse.planet_id
+				USING ( name_id )
 			INNER JOIN verse.resource_providers _resprov
-				USING ( planet_id )
+				ON planet_id = name_id
 			INNER JOIN verse.planet_resources _pres
 				USING ( planet_id , resource_name_id )
 			LEFT OUTER JOIN (
@@ -141,10 +141,10 @@ AS $gu_pmc_get_data$
 								pmc_total , _happ.current AS happiness
 		
 							FROM sys.updates _upd_sys
-								INNER JOIN verse.updates _upd_verse
-									ON _upd_sys.id = _upd_verse.update_id
+								INNER JOIN verse.planets_updates _upd_verse
+									USING ( update_id , updtype_id , updtgt_id )
 								INNER JOIN emp.planets _emp_planet
-									USING ( planet_id ) 
+									ON _emp_planet.planet_id = _upd_verse.name_id 
 								INNER JOIN emp.empires _emp
 									ON _emp_planet.empire_id = _emp.name_id
 								INNER JOIN emp.mining_settings _emset
@@ -161,17 +161,15 @@ AS $gu_pmc_get_data$
 										) AS _pmset
 									USING ( empire_id , planet_id , resource_name_id )
 	
-							WHERE _upd_sys.last_tick = $1
-								AND _upd_sys.status = 'PROCESSING'
-								AND _upd_sys.gu_type = 'PLANET_MINING'
+							WHERE _upd_sys.update_last = $1
+								AND _upd_sys.update_state = 'PROCESSING'
 
 							FOR SHARE OF _emp_planet , _emp , _emset , _happ 
 					) AS _owner
 				USING ( planet_id , resource_name_id )
 	
-		WHERE _upd_sys.last_tick = $1
-			AND _upd_sys.status = 'PROCESSING'
-			AND _upd_sys.gu_type = 'PLANET_MINING'
+		WHERE _upd_sys.update_last = $1
+			AND _upd_sys.update_state = 'PROCESSING'
 	
 		FOR UPDATE OF _resprov , _pres
 		FOR SHARE OF _planet ;
@@ -286,3 +284,8 @@ $process_planet_mining_updates$ LANGUAGE PLPGSQL;
 REVOKE EXECUTE
 	ON FUNCTION sys.process_planet_mining_updates( BIGINT )
 	FROM PUBLIC;
+
+SELECT sys.register_update_type( 'Planets' , 'PlanetMining' ,
+		'Resources are being extracted from mines.' ,
+		'process_planet_mining_updates'
+	);
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/010-target-definition.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/010-target-definition.sql
new file mode 100644
index 0000000..5438a50
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/010-target-definition.sql
@@ -0,0 +1,70 @@
+/*
+ * Tests of the update target definitions
+ */
+BEGIN;
+	-- Make sure we get rid of all existing definitions first
+	DELETE FROM sys.update_types;
+	DELETE FROM sys.update_targets;
+
+	-- Create a test schema & table which will be used as the target
+	CREATE SCHEMA test
+		CREATE TABLE test(
+			test_id		SERIAL NOT NULL PRIMARY KEY
+		)
+		CREATE TABLE test2(
+			test_id		SERIAL NOT NULL PRIMARY KEY
+		);
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 8 );
+
+	SELECT diag_test_name( 'Update target definitions - Failure if schema/table do not exist' );
+	SELECT throws_ok(
+			$$ INSERT INTO sys.update_targets(
+					updtgt_name , updtgt_schema , updtgt_table
+				) VALUES (
+					'Test' , 'test' , 'does_not_exist'
+				); $$ ,
+			23503
+		);
+
+	SELECT diag_test_name( 'Update target definitions - Successful insertion' );
+	SELECT lives_ok( $$
+		INSERT INTO sys.update_targets(
+				updtgt_name , updtgt_schema , updtgt_table
+			) VALUES (
+				'Test' , 'test' , 'test'
+			);
+	$$ );
+	SELECT diag_test_name( 'Update target definitions - Specific update table - Exists after insertion' );
+	SELECT has_table( 'test' , 'test_updates' , 'No specific update table' );
+	SELECT diag_test_name( 'Update target definitions - Specific update table - Primary key' );
+	SELECT index_is_primary( 'test' , 'test_updates' , 'test_updates_pkey' );
+	SELECT diag_test_name( 'Update target definitions - Specific update table - Primary key definition' );
+	SELECT has_index( 'test' , 'test_updates' , 'test_updates_pkey' ,
+		ARRAY[ 'updtgt_id' , 'updtype_id' , 'update_id' , 'test_id' ] );
+
+	SELECT diag_test_name( 'Update target definitions - Duplicate name' );
+	SELECT throws_ok( $$
+		INSERT INTO sys.update_targets(
+				updtgt_name , updtgt_schema , updtgt_table
+			) VALUES (
+				'Test' , 'test' , 'test2'
+			);
+	$$ , 23505 );
+
+	SELECT diag_test_name( 'Update target definitions - Duplicate table reference' );
+	SELECT throws_ok( $$
+		INSERT INTO sys.update_targets(
+				updtgt_name , updtgt_schema , updtgt_table
+			) VALUES (
+				'Test 2' , 'test' , 'test'
+			);
+	$$ , 23505 );
+
+	SELECT diag_test_name( 'Update target definitions - Deletion drops the specific update table' );
+	DELETE FROM sys.update_targets;
+	SELECT hasnt_table( 'test' , 'test_updates' , 'Specific update table still exists' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/020-type-definitions.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/020-type-definitions.sql
new file mode 100644
index 0000000..895aa72
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/020-type-definitions.sql
@@ -0,0 +1,150 @@
+/*
+ * Tests of the update type definitions
+ */
+BEGIN;
+	-- Make sure we get rid of all existing definitions first
+	DELETE FROM sys.update_types;
+	DELETE FROM sys.update_targets;
+
+	-- Create a test schema & tables which will be used as targets
+	CREATE SCHEMA test
+		CREATE TABLE test(
+			test_id		SERIAL NOT NULL PRIMARY KEY
+		);
+
+	-- Create target definitions
+	INSERT INTO sys.update_targets(
+			updtgt_id , updtgt_name , updtgt_schema , updtgt_table
+		) VALUES (
+			1 , 'Test 1' , 'test' , 'test'
+		);
+
+	-- Create a dummy stored procedure in the sys schema
+	CREATE FUNCTION sys.dummy_update_func( c_tick BIGINT )
+			RETURNS INT LANGUAGE SQL AS 'SELECT 1;';
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 14 );
+	
+	SELECT diag_test_name( 'Update types - Definition with invalid target' );
+	SELECT throws_ok( $$
+		INSERT INTO sys.update_types(
+			updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+		) VALUES (
+			1 , 5 , 'Test' , '' , 1
+		);
+	$$ , 23503 );
+	
+	SELECT diag_test_name( 'Update types - Definition with invalid stored procedure name' );
+	SELECT throws_ok( $$
+		INSERT INTO sys.update_types(
+			updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering , updtype_proc_name
+		) VALUES (
+			1 , 5 , 'Test' , '' , 1 , 'does_not_exist'
+		);
+	$$ , 'P0001' );
+	
+	SELECT diag_test_name( 'Update types - Successful insertion with no procedure name' );
+	SELECT lives_ok( $$
+		INSERT INTO sys.update_types(
+			updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+		) VALUES (
+			1 , 1 , 'Test' , '' , 1
+		);
+	$$ );
+	DELETE FROM sys.update_types;
+	
+	SELECT diag_test_name( 'Update types - Successful insertion with a procedure name' );
+	SELECT lives_ok( $$
+		INSERT INTO sys.update_types(
+			updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering , updtype_proc_name
+		) VALUES (
+			1 , 1 , 'Test' , '' , 1 , 'dummy_update_func'
+		);
+	$$ );
+	
+	SELECT diag_test_name( 'Update types - Setting an existing row''s stored procedure to something invalid fails' );
+	SELECT throws_ok( $$
+		UPDATE sys.update_types
+			SET updtype_proc_name = 'does_not_exist'
+			WHERE updtype_id = 1
+	$$ , 'P0001' );
+
+	SELECT diag_test_name( 'Update types - Failure on duplicate name' );
+	SELECT throws_ok( $$
+		INSERT INTO sys.update_types(
+			updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+		) VALUES (
+			2 , 1 , 'Test' , '' , 1
+		);
+	$$ , 23505 );
+
+	SELECT diag_test_name( 'Update types - Failure on duplicate stored procedure' );
+	SELECT throws_ok( $$
+		INSERT INTO sys.update_types(
+			updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering , updtype_proc_name
+		) VALUES (
+			3 , 1 , 'Test 2' , '' , 1 , 'dummy_update_func'
+		);
+	$$ , 23505 );
+
+	SELECT diag_test_name( 'Update types - Target deletion fails if type definitions exist' );
+	SELECT throws_ok( $$ DELETE FROM sys.update_targets WHERE updtgt_id = 1 $$ , 23503 );
+
+	DELETE FROM sys.update_types;
+
+	INSERT INTO sys.update_types(
+		updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+	) VALUES (
+			1 , 1 , 'Test 1' , '' , 1
+		) , (
+			2 , 1 , 'Test 3' , '' , 75
+		) , (
+			3 , 1 , 'Test 2' , '' , 2
+		);
+	SELECT diag_test_name( 'Update types - Ordering column is updated' );
+	SELECT set_eq(
+		$$ SELECT updtype_id , updtype_ordering FROM sys.update_types $$ ,
+		$$ VALUES ( 1 , 2 ) , ( 3 , 4 ) , ( 2 , 6 ) $$
+	);
+	DELETE FROM sys.update_types;
+	
+	
+	INSERT INTO test.test VALUES ( 1 ) , ( 2 );
+	INSERT INTO sys.update_types(
+		updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+	) VALUES (
+			1 , 1 , 'Test 1' , '' , 1
+	);
+	SELECT diag_test_name( 'Update types - Type creation adds specific rows for missing items' );
+	SELECT set_eq(
+		$$ SELECT updtgt_id , updtype_id , test_id FROM test.test_updates $$ ,
+		$$ VALUES ( 1 , 1 , 1 ) , ( 1 , 1 , 2 ) $$
+	);
+	SELECT diag_test_name( 'Update types - Type creation adds genertic rows for missing items' );
+	SELECT set_eq(
+		$$ SELECT test_id , update_state , update_last
+				FROM test.test_updates
+					INNER JOIN sys.updates USING ( updtgt_id , updtype_id , update_id ) $$ ,
+		$$ VALUES ( 1 , 'FUTURE'::sys.update_state_type , -1 ) ,
+				( 2 , 'FUTURE'::sys.update_state_type , '-1' ) $$
+	);
+	
+	INSERT INTO test.test VALUES ( 3 );
+	SELECT diag_test_name( 'Update types - New items have automatically inserted update rows' );
+	SELECT set_eq(
+		$$ SELECT test_id , updtype_id , update_state , update_last
+				FROM test.test_updates
+					INNER JOIN sys.updates USING ( updtgt_id , updtype_id , update_id )
+				WHERE test_id = 3 $$ ,
+		$$ VALUES ( 3 , 1 , 'FUTURE'::sys.update_state_type , -1 ) $$
+	);
+
+	DELETE FROM sys.update_types WHERE updtype_id = 1;
+	SELECT diag_test_name( 'Update types - Type deletion cascades to specific rows' );
+	SELECT is_empty( $$ SELECT * FROM test.test_updates $$ );
+	SELECT diag_test_name( 'Update types - Type deletion cascades to generic rows' );
+	SELECT is_empty( $$ SELECT * FROM sys.updates $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
index 72671df..6520694 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
@@ -16,7 +16,7 @@ BEGIN;
 	SELECT _create_emp_names( 1 , 'testEmp' );
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 8 );
+	SELECT plan( 7 );
 	
 	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) ,
 				_get_map_name( 'testPlanet1' ) ,
@@ -57,16 +57,5 @@ BEGIN;
 		FROM emp.resources
 		WHERE empire_id = _get_emp_name( 'testEmp1' );
 
-	SELECT diag_test_name( 'emp.create_empire() - Empire update records' );
-	SELECT is( _eur.quantity , _utv.quantity)
-		FROM (
-				SELECT COUNT(*) AS quantity FROM emp.updates
-					WHERE empire_id = _get_emp_name( 'testEmp1' )
-			) AS _eur , (
-				SELECT COUNT(*) AS quantity
-					FROM unnest( enum_range( NULL::update_type ) ) AS _row
-					WHERE _row::TEXT LIKE 'EMPIRE_%'
-			) AS _utv;
-
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/010-start-tick.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/010-start-tick.sql
new file mode 100644
index 0000000..85e5731
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/010-start-tick.sql
@@ -0,0 +1,53 @@
+/*
+ * Test the sys.start_tick() function
+ */
+BEGIN;
+	-- Delete all registered update types and targets
+	DELETE FROM sys.update_types;
+	DELETE FROM sys.update_targets;
+	
+	-- Create a new update target / type which will be used in tests
+	CREATE SCHEMA test
+		CREATE TABLE test( test_id INT NOT NULL PRIMARY KEY );
+	INSERT INTO sys.update_targets VALUES ( 1 , 'Test' , 'test' , 'test' );
+	INSERT INTO sys.update_types VALUES ( 1 , 1 , 'Test' , 2 , '' , NULL , NULL );
+	
+	-- Insert a few update rows
+	INSERT INTO test.test VALUES ( 1 ) , ( 2 ) , ( 3 );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 5 );
+	
+	UPDATE sys.status SET maintenance_start = now( ) , current_tick = NULL;
+	SELECT diag_test_name( 'sys.start_tick() - Ticks do not start if maintenance mode is active' );
+	SELECT ok( sys.start_tick( ) IS NULL );
+	
+	UPDATE sys.status SET maintenance_start = NULL , current_tick = next_tick;
+	SELECT diag_test_name( 'sys.start_tick() - Ticks do not start if current_tick is set' );
+	SELECT ok( sys.start_tick( ) IS NULL );
+	
+	UPDATE sys.status SET current_tick = NULL , next_tick = 2;
+	UPDATE sys.updates SET update_state = 'PROCESSED' , update_last = -1;
+	UPDATE sys.updates su SET update_last = 514
+		FROM test.test_updates tu
+		WHERE su.update_id = tu.update_id
+			AND tu.test_id = 3;
+	SELECT diag_test_name( 'sys.start_tick() - Returns next tick identifier when ticks can start' );
+	SELECT is( sys.start_tick( ) , 2::BIGINT );
+	SELECT diag_test_name( 'sys.start_tick() - System status is updated' );
+	SELECT set_eq(
+		$$ SELECT current_tick , next_tick FROM sys.status $$ ,
+		$$ VALUES ( 2 , 3 ) $$
+	);
+	SELECT diag_test_name( 'sys.start_tick() - Update rows are marked for processing when necessary' );
+	SELECT set_eq(
+		$$ SELECT test_id
+			FROM test.test_updates
+				INNER JOIN sys.updates
+					USING ( update_id , updtgt_id , updtype_id )
+			WHERE update_state = 'FUTURE' AND update_last = 2 $$ ,
+		$$ VALUES ( 1 ) , ( 2 ) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/020-check-stuck-tick.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
new file mode 100644
index 0000000..cc5eaf9
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
@@ -0,0 +1,60 @@
+/*
+ * Tests of the sys.check_stuck_tick() function
+ */
+BEGIN;
+	-- Delete all registered update types and targets
+	DELETE FROM sys.update_types;
+	DELETE FROM sys.update_targets;
+	
+	-- Create a new update target / type which will be used in tests
+	CREATE SCHEMA test
+		CREATE TABLE test( test_id INT NOT NULL PRIMARY KEY );
+	INSERT INTO sys.update_targets VALUES ( 1 , 'Test' , 'test' , 'test' );
+	INSERT INTO sys.update_types VALUES ( 1 , 1 , 'Test' , 2 , '' , NULL , NULL );
+	
+	-- Insert a few update rows
+	INSERT INTO test.test VALUES ( 1 ) , ( 2 ) , ( 3 );
+
+	-- Replace sys.end_tick() with a function that inserts into some table
+	CREATE TABLE end_tick_calls( tick_id BIGINT NOT NULL );
+	CREATE OR REPLACE FUNCTION sys.end_tick( tick_id BIGINT )
+		RETURNS VOID LANGUAGE SQL
+		AS 'INSERT INTO end_tick_calls VALUES( $1 );';
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 8 );
+	
+	UPDATE sys.status SET maintenance_start = NULL , current_tick = NULL;
+	SELECT diag_test_name( 'sys.check_stuck_tick() - Returns NULL if no tick is in progress' );
+	SELECT ok( sys.check_stuck_tick() IS NULL );
+	SELECT diag_test_name( 'sys.check_stuck_tick() - Does not call sys.end_tick() if no tick is in progress' );
+	SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+	DELETE FROM end_tick_calls;
+
+	UPDATE sys.status SET maintenance_start = now( ) , current_tick = 2;
+	SELECT diag_test_name( 'sys.check_stuck_tick() - Returns NULL if maintenance mode is enabled' );
+	SELECT ok( sys.check_stuck_tick() IS NULL );
+	SELECT diag_test_name( 'sys.check_stuck_tick() - Does not call sys.end_tick() if maintenance mode is enabled' );
+	SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+	DELETE FROM end_tick_calls;
+
+	UPDATE sys.status SET maintenance_start = NULL , current_tick = 2;
+	UPDATE sys.updates SET update_state = 'PROCESSED' , update_last = 2;
+	SELECT diag_test_name( 'sys.check_stuck_tick() - Returns NULL if a tick is in progress but there are no updates left' );
+	SELECT ok( sys.check_stuck_tick() IS NULL );
+	SELECT diag_test_name( 'sys.check_stuck_tick() - Calls sys.end_tick() if a tick is in progress but there are no updates left' );
+	SELECT set_eq(
+		$$ SELECT * FROM end_tick_calls $$ ,
+		$$ VALUES ( 2 ) $$
+	);
+	DELETE FROM end_tick_calls;
+
+	UPDATE sys.status SET maintenance_start = NULL , current_tick = 2;
+	UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 2;
+	SELECT diag_test_name( 'sys.check_stuck_tick() - Returns tick identifier if a tick is in progress and there are updates left' );
+	SELECT is( sys.check_stuck_tick() , 2::BIGINT );
+	SELECT diag_test_name( 'sys.check_stuck_tick() - Does not call sys.end_tick() if a tick is in progress and there are updates left' );
+	SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/030-process-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/030-process-updates.sql
new file mode 100644
index 0000000..ab25ff1
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/030-process-updates.sql
@@ -0,0 +1,179 @@
+/*
+ * Tests for the sys.process_updates() function
+ */
+BEGIN;
+	-- Delete all registered update types and targets
+	DELETE FROM sys.update_types;
+	DELETE FROM sys.update_targets;
+	
+	-- Create a new update target / type which will be used in tests
+	CREATE SCHEMA test
+		CREATE TABLE test( test_id INT NOT NULL PRIMARY KEY );
+	INSERT INTO sys.update_targets VALUES ( 1 , 'Test' , 'test' , 'test' );
+	INSERT INTO sys.update_types VALUES ( 1 , 1 , 'Test 1' , 2 , '' , NULL , NULL );
+	INSERT INTO sys.update_types VALUES ( 2 , 1 , 'Test 2' , 4 , '' , NULL , NULL );
+
+	-- Replace sys.end_tick() with a function that inserts into some table
+	CREATE TABLE end_tick_calls( tick_id BIGINT NOT NULL );
+	CREATE OR REPLACE FUNCTION sys.end_tick( tick_id BIGINT )
+		RETURNS VOID LANGUAGE SQL
+		AS 'INSERT INTO end_tick_calls VALUES( $1 );';
+
+	-- Create a sys.process_dummy_update() function that inserts into some other table
+	CREATE TABLE process_update_calls( tick_id BIGINT NOT NULL );
+	CREATE OR REPLACE FUNCTION sys.process_dummy_update( tick_id BIGINT )
+		RETURNS VOID LANGUAGE SQL
+		AS 'INSERT INTO process_update_calls VALUES( $1 );';
+
+	-- Set the general batch size to 1
+	SELECT sys.uoc_constant( 'game.batchSize' , '(test)' , 'Game updates' , 1 );
+	
+	-- Insert a few entries in the test table
+	INSERT INTO test.test
+		SELECT * FROM generate_series( 1 , 10 );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 21 );
+	
+	/*
+	 * Case:
+	 *		Updates have not been processed yet,
+	 *
+	 * Expected results:
+	 *		1) New updates of the first type have been marked for processing,
+	 *		2) The quantity of updates marked for processing corresponds to game.batchSize
+	 */
+	UPDATE sys.updates
+		SET update_state = 'FUTURE' , update_last = 1;
+	SELECT diag_test_name( 'sys.process_updates() - General batch size - Return value' );
+	SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , 'Test 1'::TEXT ) );
+	SELECT diag_test_name( 'sys.process_updates() - General batch size - Update type' );
+	SELECT set_eq(
+		$$ SELECT DISTINCT updtype_id FROM sys.updates WHERE update_state = 'PROCESSING'; $$ ,
+		$$ VALUES ( 1 ) $$
+	);
+	SELECT diag_test_name( 'sys.process_updates() - General batch size - Update count' );
+	SELECT is( COUNT(*)::INT , 1 ) FROM sys.updates WHERE update_state = 'PROCESSING';
+	SELECT diag_test_name( 'sys.process_updates() - General batch size - sys.end_tick() not called' );
+	SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+	DELETE FROM end_tick_calls;
+
+	/*
+	 * Case:
+	 *		Updates have not been processed yet,
+	 *		Update type has a specific batch size,
+	 *
+	 * Expected results:
+	 *		1) New updates have been marked for processing,
+	 *		2) The quantity of updates marked for processing corresponds to the specific batch size 
+	 *		3) sys.end_tick() has not been called 
+	 */
+	UPDATE sys.update_types SET updtype_batch_size = 2 WHERE updtype_id = 1;
+	UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 1;
+	SELECT diag_test_name( 'sys.process_updates() - Specific batch size - Return value' );
+	SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , 'Test 1'::TEXT ) );
+	SELECT diag_test_name( 'sys.process_updates() - Specific batch size - Update type' );
+	SELECT set_eq(
+		$$ SELECT DISTINCT updtype_id FROM sys.updates WHERE update_state = 'PROCESSING'; $$ ,
+		$$ VALUES ( 1 ) $$
+	);
+	SELECT diag_test_name( 'sys.process_updates() - Specific batch size - Update count' );
+	SELECT is( COUNT(*)::INT , 2 ) FROM sys.updates WHERE update_state = 'PROCESSING';
+	SELECT diag_test_name( 'sys.process_updates() - Specific batch size - sys.end_tick() not called' );
+	SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+	DELETE FROM end_tick_calls;
+
+	/*
+	 * Case:
+	 *		Some but not all updates of the first type have been processed,
+	 *		Batch size is greater than the quantity of updates left to process for the type.
+	 *
+	 * Expected results:
+	 *		1) All updates of the first type are marked either for processing or as processed.
+	 *		2) sys.end_tick() has not been called
+	 */
+	UPDATE sys.update_types SET updtype_batch_size = 2 WHERE updtype_id = 1;
+	UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 1;
+	UPDATE sys.updates
+		SET update_state = 'PROCESSED'
+		WHERE update_id IN (
+			SELECT update_id FROM sys.updates WHERE updtype_id = 1 LIMIT 9
+		);
+	UPDATE sys.update_types SET updtype_batch_size = 2 WHERE updtype_id = 1;
+	SELECT diag_test_name( 'sys.process_updates() - End of type updates - Return value' );
+	SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , 'Test 1'::TEXT ) );
+	SELECT diag_test_name( 'sys.process_updates() - End of type updates - Update type' );
+	SELECT set_eq(
+		$$ SELECT DISTINCT updtype_id FROM sys.updates WHERE update_state <> 'FUTURE'; $$ ,
+		$$ VALUES ( 1 ) $$
+	);
+	SELECT diag_test_name( 'sys.process_updates() - End of type updates - Update count' );
+	SELECT is( COUNT(*)::INT , 10 ) FROM sys.updates WHERE update_state <> 'FUTURE';
+	SELECT diag_test_name( 'sys.process_updates() - End of type updates - sys.end_tick() not called' );
+	SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+	DELETE FROM end_tick_calls;
+
+	/*
+	 * Case:
+	 *		All updates of the first type have been processed.
+	 *		No updates of the second type have been processed.
+	 *
+	 * Expected results:
+	 *		1) Some updates from the second type have been marked for processing.
+	 *		2) sys.end_tick() has not been called 
+	 */
+	UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 1;
+	UPDATE sys.updates
+		SET update_state = 'PROCESSED'
+		WHERE updtype_id = 1;
+	SELECT diag_test_name( 'sys.process_updates() - Update type transition - Return value' );
+	SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , 'Test 2'::TEXT ) );
+	SELECT diag_test_name( 'sys.process_updates() - Update type transition - Update type' );
+	SELECT set_eq(
+		$$ SELECT DISTINCT updtype_id FROM sys.updates WHERE update_state = 'PROCESSING'; $$ ,
+		$$ VALUES ( 2 ) $$
+	);
+	SELECT diag_test_name( 'sys.process_updates() - Update type transition - Update count' );
+	SELECT is( COUNT(*)::INT , 1 ) FROM sys.updates WHERE update_state = 'PROCESSING';
+	SELECT diag_test_name( 'sys.process_updates() - Update type transition - sys.end_tick() not called' );
+	SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+	DELETE FROM end_tick_calls;
+
+	/*
+	 * Case:
+	 *		There no updates to process
+	 *		Updates are marked as 'PROCESSING'
+	 *
+	 * Expected results:
+	 *		1) _has_more return value is FALSE,
+	 *		2) All updates are marked as 'PROCESSED'
+	 *		3) sys.end_tick() has been called.
+	 */
+	UPDATE sys.updates SET update_state = 'PROCESSING' , update_last = 1;
+	SELECT diag_test_name( 'sys.process_updates() - No updates left - Return value' );
+	SELECT ok( sys.process_updates( 1 ) = ROW( FALSE , NULL::TEXT ) );
+	SELECT diag_test_name( 'sys.process_updates() - No updates left - All updates processed' );
+	SELECT is_empty( $$ SELECT * FROM sys.updates WHERE update_state <> 'PROCESSED' $$ );
+	SELECT diag_test_name( 'sys.process_updates() - No updates left - sys.end_tick() called' );
+	SELECT set_eq( $$ SELECT * FROM end_tick_calls $$ , $$ VALUES ( 1 ) $$ );
+	DELETE FROM end_tick_calls;
+	
+	/*
+	 * Case:
+	 *		The update type has an in-base processing function
+	 *
+	 * Expected result:
+	 *		1) _process_externally return value is NULL
+	 *		2) The processing function has been called.
+	 */
+	UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 1;
+	UPDATE sys.update_types
+		SET updtype_batch_size = NULL , updtype_proc_name = 'process_dummy_update'
+		WHERE updtype_id = 1;
+	SELECT diag_test_name( 'sys.process_updates() - In-base processing - Return value' );
+	SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , NULL::TEXT ) );
+	SELECT diag_test_name( 'sys.process_updates() - In-base processing - Processing function called' );
+	SELECT set_eq( $$ SELECT * FROM process_update_calls $$ , $$ VALUES ( 1 ) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/003-core-update-targets/010-presence.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/003-core-update-targets/010-presence.sql
new file mode 100644
index 0000000..595b988
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/003-core-update-targets/010-presence.sql
@@ -0,0 +1,17 @@
+/*
+ * Check that core update targets have been defined correctly
+ */
+BEGIN;
+	SELECT plan( 3 );
+
+	SELECT diag_test_name( 'Update targets - Definitions present' );
+	SELECT is( COUNT(*)::INT , 2 ) FROM sys.update_targets;
+
+	SELECT diag_test_name( 'Update targets - verse.planets_updates exists' );
+	SELECT has_table( 'verse' , 'planets_updates' , 'Missing planets update table' );
+
+	SELECT diag_test_name( 'Update targets - emp.empires_updates exists' );
+	SELECT has_table( 'emp' , 'empires_updates' , 'Missing empires update table' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
index 2b4f57a..5e670c7 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
@@ -2,6 +2,12 @@
  * Test the sys.process_empire_resources_updates() function
  */
 BEGIN;
+	/* Start by deleting all update types except for the one
+	 * we're interested in.
+	 */
+	DELETE FROM sys.update_types
+		WHERE updtype_name <> 'EmpireResources';
+
 	/* We need:
 	 *  - three types of resources,
 	 *  - two empire with resources records, and the corresponding update
@@ -35,14 +41,15 @@ BEGIN;
 
 	/* Make sure update 0 is set to be processed for testEmp1 */
 	UPDATE sys.updates
-		SET status = 'PROCESSING' , last_tick = 0;
+		SET update_state = 'PROCESSING' , update_last = 0;
 
 	/* Create testEmp2 empire, make sure it will not be processed */
 	SELECT emp.create_empire( _get_emp_name( 'testEmp2' ) , _get_map_name( 'testPlanet2' ) , 1 );
 	UPDATE sys.updates _su
-		SET status = 'PROCESSED' , last_tick = 0
-		FROM emp.updates _eu
-			WHERE _eu.update_id = _su.id AND _eu.empire_id = _get_emp_name( 'testEmp2' );
+		SET update_state = 'PROCESSED' , update_last = 0
+		FROM emp.empires_updates _eu
+		WHERE _eu.update_id = _su.update_id
+			AND _eu.name_id = _get_emp_name( 'testEmp2' );
 
 	/****** TESTS BEGIN HERE ******/
 	SELECT plan( 19 );
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
index 54385b4..72c4188 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
@@ -2,6 +2,11 @@
  * Test for the sys.process_planet_res_regen_updates() function
  */
 BEGIN;
+	/* Start by deleting all update types except for the one
+	 * we're interested in.
+	 */
+	DELETE FROM sys.update_types
+		WHERE updtype_name <> 'ResourceRegeneration';
 
 	/* We need to create a natural resource and a pair of planets */
 	\i utils/strings.sql
@@ -34,23 +39,19 @@ BEGIN;
 			 0.5 , 0.5
 		);
 	
-	/* Insert update records for the planets. Planets 1 and 2 will be processed,
+	/* Modify update records for the planets. Planets 1 and 2 will be processed,
 	 * planet testPlanet3 will not.
 	 */
-	INSERT INTO sys.updates ( id , gu_type , status , last_tick )
-		VALUES ( 1 , 'PLANET_RES_REGEN' , 'PROCESSING' , 0 );
-	INSERT INTO verse.updates ( update_id , planet_id )
-		VALUES ( 1 , _get_map_name( 'testPlanet1' ) );
-
-	INSERT INTO sys.updates ( id , gu_type , status , last_tick )
-		VALUES ( 2 , 'PLANET_RES_REGEN' , 'PROCESSING' , 0 );
-	INSERT INTO verse.updates ( update_id , planet_id )
-		VALUES ( 2 , _get_map_name( 'testPlanet2' ) );
-
-	INSERT INTO sys.updates ( id , gu_type , status , last_tick )
-		VALUES ( 3 , 'PLANET_RES_REGEN' , 'PROCESSED' , 0 );
-	INSERT INTO verse.updates ( update_id , planet_id )
-		VALUES ( 3 , _get_map_name( 'testPlanet3' ) );
+	UPDATE sys.updates su
+		SET update_state = 'PROCESSING' , update_last = 0
+		FROM verse.planets_updates vu
+		WHERE vu.update_id = su.update_id
+			AND vu.name_id <>  _get_map_name( 'testPlanet3' );
+	UPDATE sys.updates su
+		SET update_state = 'PROCESSED' , update_last = 0
+		FROM verse.planets_updates vu
+		WHERE vu.update_id = su.update_id
+			AND vu.name_id = _get_map_name( 'testPlanet3' );
 
 	/* Helper function that gets a provider's current quantity */
 	CREATE FUNCTION _get_quantity( _planet TEXT )
@@ -65,7 +66,7 @@ BEGIN;
 	SELECT plan( 6 );
 
 
-	SELECT sys.process_planet_res_regen_updates( 10 );
+	SELECT sys.process_res_regen_updates( 10 );
 	SELECT diag_test_name( 'PLANET_RES_REGEN update - Wrong update identifier (1/3)' );
 	SELECT ok( _get_quantity( 'testPlanet1' ) = 50 );
 	SELECT diag_test_name( 'PLANET_RES_REGEN update - Wrong update identifier (2/3)' );
@@ -74,7 +75,7 @@ BEGIN;
 	SELECT ok( _get_quantity( 'testPlanet3' ) = 50 );
 
 
-	SELECT sys.process_planet_res_regen_updates( 0 );
+	SELECT sys.process_res_regen_updates( 0 );
 	SELECT diag_test_name( 'PLANET_RES_REGEN update - Processed planet with quantity < max.' );
 	SELECT ok( _get_quantity( 'testPlanet1' ) > 50 );
 	SELECT diag_test_name( 'PLANET_RES_REGEN update - Processed planet with quantity = max.' );
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/010-update-targets.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/010-update-targets.sql
new file mode 100644
index 0000000..70343c3
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/010-update-targets.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on sys.update_targets
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'sys.update_targets - No INSERT privilege' );
+	SELECT throws_ok(
+		$$ INSERT INTO sys.update_targets DEFAULT VALUES; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.update_targets - No UPDATE privilege' );
+	SELECT throws_ok(
+		$$ UPDATE sys.update_targets SET updtgt_id = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.update_targets - No SELECT privilege' );
+	SELECT throws_ok(
+		$$ SELECT * FROM sys.update_targets; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.update_targets - No DELETE privilege' );
+	SELECT throws_ok(
+		$$ DELETE FROM sys.update_targets; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/020-update-types.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/020-update-types.sql
new file mode 100644
index 0000000..fc33d56
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/020-update-types.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on sys.update_types
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'sys.update_types - No INSERT privilege' );
+	SELECT throws_ok(
+		$$ INSERT INTO sys.update_types DEFAULT VALUES; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.update_types - No UPDATE privilege' );
+	SELECT throws_ok(
+		$$ UPDATE sys.update_types SET updtgt_id = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.update_types - No SELECT privilege' );
+	SELECT throws_ok(
+		$$ SELECT * FROM sys.update_types; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.update_types - No DELETE privilege' );
+	SELECT throws_ok(
+		$$ DELETE FROM sys.update_types; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/030-updates.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/030-updates.sql
new file mode 100644
index 0000000..cc86cb3
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/030-updates.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on sys.updates
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'sys.updates - No INSERT privilege' );
+	SELECT throws_ok(
+		$$ INSERT INTO sys.updates DEFAULT VALUES; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.updates - No UPDATE privilege' );
+	SELECT throws_ok(
+		$$ UPDATE sys.updates SET updtgt_id = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.updates - No SELECT privilege' );
+	SELECT throws_ok(
+		$$ SELECT * FROM sys.updates; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'sys.updates - No DELETE privilege' );
+	SELECT throws_ok(
+		$$ DELETE FROM sys.updates; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/010-get-table-pkey.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/010-get-table-pkey.sql
new file mode 100644
index 0000000..ef222fc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/010-get-table-pkey.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.get_table_pkey()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.get_table_pkey() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.get_table_pkey( 'a' , 'b' ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/020-tgf-speupd-before-insert.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/020-tgf-speupd-before-insert.sql
new file mode 100644
index 0000000..dae5ca2
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/020-tgf-speupd-before-insert.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_speupd_before_insert()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_speupd_before_insert() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_speupd_before_insert( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/030-tgf-speupd-after-delete.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/030-tgf-speupd-after-delete.sql
new file mode 100644
index 0000000..431a387
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/030-tgf-speupd-after-delete.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_speupd_after_delete()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_speupd_after_delete() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_speupd_after_delete( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/040-insert-missing-updates.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/040-insert-missing-updates.sql
new file mode 100644
index 0000000..2b22f82
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/040-insert-missing-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.insert_missing_updates()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.insert_missing_updates() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.insert_missing_updates( 1 ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/050-tgf-insert-missing-updates.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/050-tgf-insert-missing-updates.sql
new file mode 100644
index 0000000..26f9c10
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/050-tgf-insert-missing-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_insert_missing_updates()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_insert_missing_updates() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_insert_missing_updates( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/060-tgf-updtype-after-insert-row.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/060-tgf-updtype-after-insert-row.sql
new file mode 100644
index 0000000..5fce32c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/060-tgf-updtype-after-insert-row.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_updtype_after_insert_row()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_updtype_after_insert_row() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_updtype_after_insert_row( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/070-tgf-check-update-type-proc.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/070-tgf-check-update-type-proc.sql
new file mode 100644
index 0000000..064dbdc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/070-tgf-check-update-type-proc.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_check_update_type_proc()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_check_update_type_proc() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_check_update_type_proc( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/080-tgf-reorder-update-types.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/080-tgf-reorder-update-types.sql
new file mode 100644
index 0000000..bb27646
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/080-tgf-reorder-update-types.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_reorder_update_types()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_reorder_update_types() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_reorder_update_types( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/090-tgf-updtgt-before-insert.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/090-tgf-updtgt-before-insert.sql
new file mode 100644
index 0000000..5b6ea64
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/090-tgf-updtgt-before-insert.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_updtgt_before_insert()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_updtgt_before_insert() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_updtgt_before_insert( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/100-tgf-updtgt-after-insert.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/100-tgf-updtgt-after-insert.sql
new file mode 100644
index 0000000..6da4cfa
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/100-tgf-updtgt-after-insert.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_updtgt_after_insert()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_updtgt_after_insert() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_updtgt_after_insert( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/110-tgf-updtgt-after-delete.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/110-tgf-updtgt-after-delete.sql
new file mode 100644
index 0000000..0d1c554
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/110-tgf-updtgt-after-delete.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_updtgt_after_delete()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.tgf_updtgt_after_delete() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.tgf_updtgt_after_delete( ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/120-register-update-type.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/120-register-update-type.sql
new file mode 100644
index 0000000..f25b59e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/120-register-update-type.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.register_update_type()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.register_update_type() - No EXECUTE privilege' );
+	SELECT throws_ok( $$ SELECT sys.register_update_type( '' , '' , '' , 'x' ) $$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/010-start-tick.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/010-start-tick.sql
new file mode 100644
index 0000000..cb763fb
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/010-start-tick.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.start_tick()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.start_tick() - EXECUTE privilege' );
+	SELECT lives_ok( 'SELECT sys.start_tick( )' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/020-check-stuck-tick.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
new file mode 100644
index 0000000..5799b95
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.check_stuck_tick()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.check_stuck_tick() - EXECUTE privilege' );
+	SELECT lives_ok( 'SELECT sys.check_stuck_tick( )' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/030-process-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/030-process-updates.sql
new file mode 100644
index 0000000..5416d6c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/030-process-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.process_updates()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.process_updates() - EXECUTE privilege' );
+	SELECT lives_ok( 'SELECT sys.process_updates( 1 )' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
deleted file mode 100644
index b08eb50..0000000
--- a/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Test privileges on sys.process_planet_res_regen_updates()
- */
-BEGIN;
-	SELECT plan( 1 );
-
-	SELECT diag_test_name( 'sys.process_planet_res_regen_updates() - Privileges' );
-	SELECT throws_ok( 'SELECT sys.process_planet_res_regen_updates( 1 )' , 42501 );
-	
-	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-res-regen-updates.sql
new file mode 100644
index 0000000..cb4bcd6
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-res-regen-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.process_res_regen_updates()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.process_res_regen_updates() - Privileges' );
+	SELECT throws_ok( 'SELECT sys.process_res_regen_updates( 1 )' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
index 97df95f..57d62d3 100644
--- a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
+++ b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
@@ -21,13 +21,13 @@
  * - Finally, we need a planet that matches the criterias but isn't
  *   scheduled for an update at the time. 
  *
- * FIXME: for now, locking is NOT tested. I simply have no idea how to do
- *        it, at least not without creating an extra database and that
- *        kind of stuff.
- *
  * FIXME: cannot test where the happiness column comes from until values
  *		  all use DOUBLE PRECISION.
  */
+
+DELETE FROM sys.update_types
+	WHERE updtype_name <> 'PlanetMining';
+
 \i utils/strings.sql
 \i utils/resources.sql
 \i utils/accounts.sql
@@ -41,17 +41,7 @@ SELECT _create_emp_names( 4 , 'empire' );
 INSERT INTO emp.empires ( name_id , cash )
 	SELECT id , 0 FROM naming.empire_names;
 
-/* Planet #1 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
-	VALUES ( 1 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
-	VALUES ( 1 , _get_map_name( 'planet1' ) );
-
 /* Planet #2 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
-	VALUES ( 2 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
-	VALUES ( 2 , _get_map_name( 'planet2' ) );
 INSERT INTO verse.resource_providers (
 		planet_id , resource_name_id , resprov_quantity_max ,
 		resprov_quantity , resprov_difficulty , resprov_recovery
@@ -64,10 +54,6 @@ INSERT INTO verse.resource_providers (
 	);
 
 /* Planet #3 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
-	VALUES ( 3 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
-	VALUES ( 3 , _get_map_name( 'planet3' ) );
 INSERT INTO verse.resource_providers (
 		planet_id , resource_name_id , resprov_quantity_max ,
 		resprov_quantity , resprov_difficulty , resprov_recovery
@@ -90,10 +76,6 @@ INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
 	);
 
 /* Planet #4 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
-	VALUES ( 4 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
-	VALUES ( 4 , _get_map_name( 'planet4' ) );
 INSERT INTO verse.resource_providers (
 		planet_id , resource_name_id , resprov_quantity_max ,
 		resprov_quantity , resprov_difficulty , resprov_recovery
@@ -123,10 +105,6 @@ INSERT INTO emp.planet_mining_settings(
 		);
 
 /* Planet #5 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
-	VALUES ( 5 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
-	VALUES ( 5 , _get_map_name( 'planet5' ) );
 INSERT INTO verse.planet_happiness ( planet_id , current , target )
 	VALUES ( _get_map_name( 'planet5' ) , 0.5 , 0.5 );
 INSERT INTO emp.planets ( empire_id , planet_id )
@@ -139,10 +117,6 @@ INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
 	);
 
 /* Planet #6 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
-	VALUES ( 6 , 'PLANET_MINING' , 'PROCESSED' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
-	VALUES ( 6 , _get_map_name( 'planet6' ) );
 INSERT INTO verse.resource_providers (
 		planet_id , resource_name_id , resprov_quantity_max ,
 		resprov_quantity , resprov_difficulty , resprov_recovery
@@ -168,3 +142,16 @@ INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
 INSERT INTO verse.planet_resources ( planet_id , resource_name_id , pres_income , pres_upkeep )
 	SELECT p.name_id , r.resource_name_id , 42 , 0
 		FROM verse.planets p CROSS JOIN defs.resources r;
+
+/* Modify update records for the planets. Planet 6 is not to be processed.
+ */
+UPDATE sys.updates su
+	SET update_state = 'PROCESSING' , update_last = 0
+	FROM verse.planets_updates vu
+	WHERE vu.update_id = su.update_id
+		AND vu.name_id <>  _get_map_name( 'planet6' );
+UPDATE sys.updates su
+	SET update_state = 'PROCESSED' , update_last = 0
+	FROM verse.planets_updates vu
+	WHERE vu.update_id = su.update_id
+		AND vu.name_id = _get_map_name( 'planet6' );
diff --git a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/GameUpdateResult.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/GameUpdateResult.java
new file mode 100644
index 0000000..5388528
--- /dev/null
+++ b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/GameUpdateResult.java
@@ -0,0 +1,82 @@
+package com.deepclone.lw.sqld.sys;
+
+
+/**
+ * The result of a game update
+ * 
+ * <p>
+ * This class is used to transmit the results of the game updates processor. The results may
+ * indicate:
+ * <ul>
+ * <li>a finished game update cycle that needs no further processing,
+ * <li>an in-progress update cycle for which further in-base processing is needed,
+ * <li>an in-progress update cycle where some processing must be done in the server.
+ * </ul>
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class GameUpdateResult
+{
+
+	/** Whether the update cycle is finished */
+	private final boolean finished;
+
+	/**
+	 * Name of the type of processing that needs to be run on the server, or <code>null</code> if
+	 * the update is finished or was processed in-base
+	 */
+	private final String localExecution;
+
+
+	/**
+	 * Initialise a "finished" game update result
+	 * 
+	 * <p>
+	 * This constructor will set {@link #finished} to <code>true</code> and {@link #localExecution}
+	 * to <code>null</code>.
+	 */
+	public GameUpdateResult( )
+	{
+		this.finished = true;
+		this.localExecution = null;
+	}
+
+
+	/**
+	 * Initialise an "incomplete" game update result
+	 * 
+	 * <p>
+	 * This constructor will set {@link #finished} to <code>false</code> and {@link #localExecution}
+	 * to the parameter's value.
+	 * 
+	 * @param localExecution
+	 *            the name of the type of processing to be executed on the server, or
+	 *            <code>null</code> if the update was processed in-base.
+	 */
+	public GameUpdateResult( String localExecution )
+	{
+		this.finished = false;
+		this.localExecution = localExecution;
+	}
+
+
+	/**
+	 * @return <code>true</code> if the update cycle was completed or <code>false</code> if further
+	 *         processing is required.
+	 */
+	public boolean isFinished( )
+	{
+		return this.finished;
+	}
+
+
+	/**
+	 * @return Name of the type of processing that needs to be run on the server, or
+	 *         <code>null</code> if the update is finished or was processed in-base
+	 */
+	public String getLocalExecution( )
+	{
+		return this.localExecution;
+	}
+
+}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
deleted file mode 100644
index 86736b4..0000000
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.deepclone.lw.interfaces.game;
-
-
-public interface UpdatesDAO
-{
-	public boolean processUpdates( long tickId );
-}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateProcessor.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateProcessor.java
new file mode 100644
index 0000000..9008c4f
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateProcessor.java
@@ -0,0 +1,75 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * Game update processor
+ * 
+ * <p>
+ * The game update processor allows game update cycles to be executed. It also provides the ability
+ * to terminated an existing update cycle.
+ * 
+ * <p>
+ * The game update processor includes a locking mechanism which can be used to prevent two
+ * components (for example the game update ticker task and the administration interface) from
+ * executing game updates simultaneously.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public interface GameUpdateProcessor
+{
+
+	/**
+	 * Try locking the game update processor
+	 * 
+	 * <p>
+	 * Test and set the processor's lock. This lock is not re-entrant and should only be called once
+	 * per locking thread.
+	 * 
+	 * @return <code>true</code> if the lock was acquired, <code>false</code> it some other
+	 *         component had already locked the processor.
+	 */
+	public boolean tryLock( );
+
+
+	/**
+	 * Unlock the game update processor
+	 * 
+	 * <p>
+	 * Reset the processor's lock. This method should be called from the thread which actually
+	 * locked the processor in the first place, although that will not be checked.
+	 * 
+	 * @throws IllegalStateException
+	 *             if the processor is not locked.
+	 */
+	public void unlock( )
+			throws IllegalStateException;
+
+
+	/**
+	 * End the previous update cycle
+	 * 
+	 * <p>
+	 * Check if an update cycle had already started. If such is the case, execute all remaining
+	 * update batches.
+	 * 
+	 * @return <code>true</code> if an update cycle was processed or if the system is under
+	 *         maintenance, <code>false</code> otherwise.
+	 */
+	public boolean endPreviousCycle( );
+
+
+	/**
+	 * Execute a full update cycle
+	 * 
+	 * <p>
+	 * Check for an update cycle that was already started. If one exists, finish it. Otherwise,
+	 * start a new cycle and execute all batches in separate transactions.
+	 * 
+	 * <p>
+	 * Update cycles will not be processed if the system is under maintenance. In addition, if the
+	 * system enters maintenance mode while updates are being processed, the processing will stop
+	 * after the current batch.
+	 */
+	public void executeUpdateCycle( );
+
+}
\ No newline at end of file
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdateBatchProcessor.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdateBatchProcessor.java
new file mode 100644
index 0000000..a295193
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdateBatchProcessor.java
@@ -0,0 +1,42 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * Update batch processor interface
+ * 
+ * <p>
+ * This interface must be implemented by all components which provide support for server-side game
+ * updates processing. The components will be gathered automatically when the server initialises.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public interface UpdateBatchProcessor
+{
+
+	/**
+	 * Obtain the component's supported update type
+	 * 
+	 * <p>
+	 * This method must be implemented to return a string which corresponds to the name of the
+	 * update type in the database's <code>sys.update_types</code> registry table.
+	 * 
+	 * @return the update type supported by the component
+	 */
+	public String getUpdateType( );
+
+
+	/**
+	 * Process a batch of updates
+	 * 
+	 * <p>
+	 * This method is called by the main update processor in the transaction during which items from
+	 * the batch will have been marked for processing. It should process the update, but it must not
+	 * modify the update rows in the <code>sys.updates</code> table.
+	 * 
+	 * @param tickId
+	 *            the identifier of the current game update cycle
+	 */
+	public void processBatch( long tickId );
+
+}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java
new file mode 100644
index 0000000..7c48c40
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java
@@ -0,0 +1,34 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+import com.deepclone.lw.sqld.sys.GameUpdateResult;
+
+
+
+/**
+ * Game updates data access
+ * 
+ * <p>
+ * This interface allows access to the data and stored procedure which constitute the game update
+ * sub-system.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public interface UpdatesDAO
+{
+
+	/**
+	 * Run a batch of game updates
+	 * 
+	 * <p>
+	 * Execute a batch of game updates for the current game update cycle. The value returned by this
+	 * method determines whether the caller should call the method again, and if so whether it needs
+	 * to do some in-server processing.
+	 * 
+	 * @param tickId
+	 *            the identifier of the current game update cycle
+	 * 
+	 * @return the results of the game update processor
+	 */
+	public GameUpdateResult processUpdates( long tickId );
+}
diff --git a/legacyworlds-server-main/pom.xml b/legacyworlds-server-main/pom.xml
index 30a4788..1e4af1b 100644
--- a/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server-main/pom.xml
@@ -50,6 +50,10 @@
 			<artifactId>legacyworlds-server-beans-system</artifactId>
 			<groupId>com.deepclone.lw</groupId>
 		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-beans-updates</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-user</artifactId>
 			<groupId>com.deepclone.lw</groupId>
diff --git a/legacyworlds-server-main/src/main/resources/lw-server.xml b/legacyworlds-server-main/src/main/resources/lw-server.xml
index 40e41af..cf7c8ea 100644
--- a/legacyworlds-server-main/src/main/resources/lw-server.xml
+++ b/legacyworlds-server-main/src/main/resources/lw-server.xml
@@ -24,4 +24,10 @@
 	<import resource="configuration/system-beans.xml" />
 	<import resource="configuration/user-beans.xml" />
 
+	<!-- Load both the "final" component configuration file and the "simple" 
+		implementation from B6M1. -->
+	<import resource="configuration/game.xml" />
+	<import resource="configuration/simple-beans.xml" />
+
+
 </beans>
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/DummyBatchProcessor.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/DummyBatchProcessor.java
new file mode 100644
index 0000000..bc5c718
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/DummyBatchProcessor.java
@@ -0,0 +1,62 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+
+
+
+/**
+ * A dummy batch processor used in game update tests
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class DummyBatchProcessor
+		implements UpdateBatchProcessor
+{
+	/** The name that will be returned by {@link #getUpdateType()} */
+	private final String updateType;
+
+	/** Ticks for which the {@link #processBatch(long)} was called */
+	private final HashSet< Long > ticks = new HashSet< Long >( );
+
+
+	/**
+	 * Set the update type
+	 * 
+	 * @param updateType
+	 *            the update type
+	 */
+	public DummyBatchProcessor( String updateType )
+	{
+		this.updateType = updateType;
+	}
+
+
+	/**
+	 * @return whatever type name was set when the instance was created
+	 */
+	@Override
+	public String getUpdateType( )
+	{
+		return this.updateType;
+	}
+
+
+	/** Adds the specified tick identifier to the set of ticks */
+	@Override
+	public void processBatch( long tickId )
+	{
+		this.ticks.add( tickId );
+	}
+
+
+	/** @return the set of ticks for which {@link #processBatch(long)} was called */
+	Collection< Long > getTicks( )
+	{
+		return this.ticks;
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockGameUpdateProcessor.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockGameUpdateProcessor.java
new file mode 100644
index 0000000..d719661
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockGameUpdateProcessor.java
@@ -0,0 +1,91 @@
+package com.deepclone.lw.beans.updates;
+
+
+import com.deepclone.lw.interfaces.game.updates.GameUpdateProcessor;
+
+
+
+/**
+ * A mock game update processor
+ * 
+ * <p>
+ * This mock component keeps track of which of its methods have been called. It can also simulate
+ * runtime errors during tick processing.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class MockGameUpdateProcessor
+		implements GameUpdateProcessor
+{
+	private boolean tryLockCalled = false;
+	private boolean unlockCalled = false;
+	private boolean endCycleCalled = false;
+	private boolean executeCalled = false;
+
+	private boolean failExecute = false;
+
+
+	public boolean wasTryLockCalled( )
+	{
+		return this.tryLockCalled;
+	}
+
+
+	public boolean wasUnlockCalled( )
+	{
+		return this.unlockCalled;
+	}
+
+
+	public boolean wasEndCycleCalled( )
+	{
+		return this.endCycleCalled;
+	}
+
+
+	public boolean wasExecuteCalled( )
+	{
+		return this.executeCalled;
+	}
+
+
+	public void setFailExecute( boolean failExecute )
+	{
+		this.failExecute = failExecute;
+	}
+
+
+	@Override
+	public boolean tryLock( )
+	{
+		this.tryLockCalled = true;
+		return true;
+	}
+
+
+	@Override
+	public void unlock( )
+	{
+		this.unlockCalled = true;
+	}
+
+
+	@Override
+	public boolean endPreviousCycle( )
+	{
+		this.endCycleCalled = true;
+		return false;
+	}
+
+
+	@Override
+	public void executeUpdateCycle( )
+	{
+		this.executeCalled = true;
+		if ( this.failExecute ) {
+			throw new RuntimeException( "fail" );
+		}
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockRegistry.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockRegistry.java
new file mode 100644
index 0000000..60ac8fc
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockRegistry.java
@@ -0,0 +1,41 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.util.HashMap;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+
+
+
+/**
+ * A mock processor registry
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class MockRegistry
+		implements ServerProcessorRegistry
+{
+
+	/** The map of "processors" to return */
+	private final HashMap< String , UpdateBatchProcessor > processors = new HashMap< String , UpdateBatchProcessor >( );
+
+
+	/**
+	 * Add a processor to the registry
+	 * 
+	 * @param processor
+	 *            the processor to add
+	 */
+	public void put( UpdateBatchProcessor processor )
+	{
+		this.processors.put( processor.getUpdateType( ) , processor );
+	}
+
+
+	@Override
+	public UpdateBatchProcessor getProcessorFor( String type )
+	{
+		return this.processors.get( type );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockUpdatesDAO.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockUpdatesDAO.java
new file mode 100644
index 0000000..5cf16d6
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockUpdatesDAO.java
@@ -0,0 +1,32 @@
+package com.deepclone.lw.beans.updates;
+
+
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.sqld.sys.GameUpdateResult;
+
+
+
+/**
+ * Mock updates DAO used in tests
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class MockUpdatesDAO
+		implements UpdatesDAO
+{
+	/** The index to read at the next call to {@link #processUpdates(long)} */
+	public int index = 0;
+
+	/** The values to return as the update type to process */
+	public String[] values = new String[] { };
+
+
+	@Override
+	public GameUpdateResult processUpdates( long tickId )
+	{
+		if ( this.index >= this.values.length ) {
+			return new GameUpdateResult( );
+		}
+		return new GameUpdateResult( this.values[ this.index++ ] );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateProcessorBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateProcessorBean.java
new file mode 100644
index 0000000..d6ff2da
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateProcessorBean.java
@@ -0,0 +1,240 @@
+package com.deepclone.lw.beans.updates;
+
+
+import static org.junit.Assert.*;
+
+import java.sql.Timestamp;
+import java.util.Date;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+import com.deepclone.lw.interfaces.sys.MaintenanceData;
+import com.deepclone.lw.testing.MockLogger;
+import com.deepclone.lw.testing.MockSystemStatus;
+import com.deepclone.lw.testing.MockTransactionManager;
+
+
+
+/**
+ * Tests for {@link GameUpdateProcessorBean}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestGameUpdateProcessorBean
+{
+
+	/** The mock logger */
+	private MockLogger logger;
+
+	/** Mock updates access interface */
+	private MockUpdatesDAO updatesDAO;
+
+	/** Mock processor registry */
+	private MockRegistry registry;
+
+	/** Mock system status component */
+	private MockSystemStatus system;
+
+	/** The instance under test */
+	private GameUpdateProcessorBean gup;
+
+	/**
+	 * A fake batch processor which actually enables maintenance mode.
+	 * 
+	 * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+	 */
+	private static class MaintenanceEnabler
+			implements UpdateBatchProcessor
+	{
+
+		/** Mock system status component */
+		private MockSystemStatus system;
+
+
+		/**
+		 * Set the mock system status component
+		 * 
+		 * @param system
+		 *            the mock system status component
+		 */
+		MaintenanceEnabler( MockSystemStatus system )
+		{
+			this.system = system;
+		}
+
+
+		@Override
+		public String getUpdateType( )
+		{
+			return "maintenance";
+		}
+
+
+		@Override
+		public void processBatch( long tickId )
+		{
+			this.system.setMaintenance( new MaintenanceData( new Timestamp( new Date( ).getTime( ) ) , new Timestamp(
+					new Date( ).getTime( ) ) , "no reason" ) );
+		}
+
+	}
+
+
+	@Before
+	public void setUp( )
+	{
+		this.logger = new MockLogger( );
+		this.updatesDAO = new MockUpdatesDAO( );
+		this.updatesDAO.values = new String[] {
+			null
+		};
+		this.system = new MockSystemStatus( );
+		this.registry = new MockRegistry( );
+		this.registry.put( new MaintenanceEnabler( this.system ) );
+
+		this.gup = new GameUpdateProcessorBean( );
+		this.gup.setLogger( this.logger );
+		this.gup.setUpdatesDAO( this.updatesDAO );
+		this.gup.setRegistry( this.registry );
+		this.gup.setTransactionManager( new MockTransactionManager( ) );
+		this.gup.setSystemStatus( this.system );
+	}
+
+
+	/**
+	 * Try locking the processor
+	 */
+	@Test
+	public void testLocking( )
+	{
+		assertTrue( this.gup.tryLock( ) );
+		assertFalse( this.gup.tryLock( ) );
+	}
+
+
+	/** Try unlocking the processor */
+	@Test
+	public void testUnlocking( )
+	{
+		this.gup.tryLock( );
+		this.gup.unlock( );
+		assertTrue( this.gup.tryLock( ) );
+	}
+
+
+	/** Try unlocking the processor when it's not locked */
+	@Test( expected = IllegalStateException.class )
+	public void testUnlockingWithNoLock( )
+	{
+		this.gup.unlock( );
+	}
+
+
+	/**
+	 * {@link GameUpdateProcessorBean#endPreviousCycle()} returns <code>false</code> when there is
+	 * no "stuck" tick
+	 */
+	@Test
+	public void testEndCycleNoStuckTick( )
+	{
+		assertFalse( this.gup.endPreviousCycle( ) );
+	}
+
+
+	/**
+	 * {@link GameUpdateProcessorBean#endPreviousCycle()} returns <code>true</code> when maintenance
+	 * mode is enabled, but the update is not processed
+	 */
+	@Test
+	public void testEndCycleMaintenance( )
+	{
+		this.system.setCSTBehaviour( -2 );
+		assertTrue( this.gup.endPreviousCycle( ) );
+		assertEquals( 0 , this.updatesDAO.index );
+	}
+
+
+	/**
+	 * {@link GameUpdateProcessorBean#endPreviousCycle()} returns <code>true</code> when there is a
+	 * stuck tick, and the update is processed
+	 */
+	@Test
+	public void testEndCycle( )
+	{
+		this.system.setCSTBehaviour( 0 );
+		assertTrue( this.gup.endPreviousCycle( ) );
+		assertEquals( 1 , this.updatesDAO.index );
+	}
+
+
+	/**
+	 * {@link GameUpdateProcessorBean#executeUpdateCycle()} does not start a tick if there was a
+	 * stuck tick, but the previous tick is processed.
+	 */
+	@Test
+	public void testProcessWithStuckTick( )
+	{
+		this.system.setCSTBehaviour( 0 );
+		this.gup.executeUpdateCycle( );
+		assertFalse( this.system.wasStartTickCalled( ) );
+		assertEquals( 1 , this.updatesDAO.index );
+	}
+
+
+	/**
+	 * {@link GameUpdateProcessorBean#executeUpdateCycle()} does not start a tick if maintenance
+	 * mode was enabled from the start
+	 */
+	@Test
+	public void testProcessWithStuckTickAndMaintenance( )
+	{
+		this.system.setCSTBehaviour( -2 );
+		this.gup.executeUpdateCycle( );
+		assertFalse( this.system.wasStartTickCalled( ) );
+		assertEquals( 0 , this.updatesDAO.index );
+	}
+
+
+	/**
+	 * {@link GameUpdateProcessorBean#executeUpdateCycle()} does not start a tick if maintenance
+	 * mode is enabled between the check for stuck ticks and the new tick's start.
+	 */
+	@Test
+	public void testProcessWithMaintenance( )
+	{
+		this.system.setSTBehaviour( -2 );
+		this.gup.executeUpdateCycle( );
+		assertTrue( this.system.wasStartTickCalled( ) );
+		assertEquals( 0 , this.updatesDAO.index );
+	}
+
+
+	/**
+	 * {@link GameUpdateProcessorBean#executeUpdateCycle()} throws a runtime exception if there
+	 * seems to be a tick running when the new tick is supposed to start.
+	 */
+	@Test( expected = RuntimeException.class )
+	public void testProcessWithBadState( )
+	{
+		this.system.setSTBehaviour( -1 );
+		this.gup.executeUpdateCycle( );
+	}
+
+
+	/**
+	 * {@link GameUpdateProcessorBean#executeUpdateCycle()} stops processing mid-way if maintenance
+	 * mode becomes enabled
+	 */
+	@Test
+	public void testProcessWithMaintenanceEnabledMidWay( )
+	{
+		this.updatesDAO.values = new String[] {
+				null , "maintenance" , null
+		};
+		this.gup.executeUpdateCycle( );
+		assertTrue( this.system.wasStartTickCalled( ) );
+		assertEquals( 2 , this.updatesDAO.index );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTaskBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTaskBean.java
new file mode 100644
index 0000000..1f97148
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTaskBean.java
@@ -0,0 +1,93 @@
+package com.deepclone.lw.beans.updates;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
+import com.deepclone.lw.testing.MockTicker;
+
+
+
+/**
+ * Tests for {@link GameUpdateTaskBean}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestGameUpdateTaskBean
+{
+	/** The mock ticker */
+	private MockTicker ticker;
+
+	/** The mock game update processor */
+	private MockGameUpdateProcessor processor;
+
+	/** The game update task */
+	private GameUpdateTaskBean gut;
+
+
+	/**
+	 * Initialise the mock components and the game update task instance
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.ticker = new MockTicker( Frequency.MINUTE , "Game update" , GameUpdateTaskBean.class );
+		this.processor = new MockGameUpdateProcessor( );
+
+		this.gut = new GameUpdateTaskBean( );
+		this.gut.setTicker( this.ticker );
+		this.gut.setGameUpdateProcessor( this.processor );
+	}
+
+
+	/**
+	 * Check the component's initialisation
+	 */
+	@Test
+	public void testInitialisation( )
+	{
+		this.gut.afterPropertiesSet( );
+		assertTrue( this.processor.wasTryLockCalled( ) );
+		assertTrue( this.processor.wasUnlockCalled( ) );
+		assertTrue( this.processor.wasEndCycleCalled( ) );
+		assertFalse( this.processor.wasExecuteCalled( ) );
+	}
+
+
+	/**
+	 * Check the component's run() method
+	 */
+	@Test
+	public void testNormalRun( )
+	{
+		this.gut.run( );
+		assertTrue( this.processor.wasTryLockCalled( ) );
+		assertTrue( this.processor.wasUnlockCalled( ) );
+		assertFalse( this.processor.wasEndCycleCalled( ) );
+		assertTrue( this.processor.wasExecuteCalled( ) );
+	}
+
+
+	/**
+	 * Make sure the component is unlocked even if the execution fails
+	 */
+	@Test
+	public void testFailedRun( )
+	{
+		this.processor.setFailExecute( true );
+		try {
+			this.gut.run( );
+			fail( "Mock processor failed to fail" );
+		} catch ( RuntimeException e ) {
+			// EMPTY
+		}
+		assertTrue( this.processor.wasTryLockCalled( ) );
+		assertTrue( this.processor.wasUnlockCalled( ) );
+		assertFalse( this.processor.wasEndCycleCalled( ) );
+		assertTrue( this.processor.wasExecuteCalled( ) );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTransaction.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTransaction.java
new file mode 100644
index 0000000..2484435
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTransaction.java
@@ -0,0 +1,86 @@
+package com.deepclone.lw.beans.updates;
+
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+
+
+/**
+ * Tests for {@link GameUpdateTransaction}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestGameUpdateTransaction
+{
+
+	/** The mock updates DAO */
+	private MockUpdatesDAO updatesDAO;
+
+	/** The dummy batch processor used in the tests */
+	private DummyBatchProcessor processor;
+
+	/** The mock processor registry */
+	private MockRegistry registry;
+
+	/** The game update transaction used as a "guinea pig" */
+	private GameUpdateTransaction transaction;
+
+
+	/** Set up the mock objects and the guinea pig transaction instance */
+	@Before
+	public void setUp( )
+	{
+		this.updatesDAO = new MockUpdatesDAO( );
+		this.registry = new MockRegistry( );
+		this.registry.put( this.processor = new DummyBatchProcessor( "test" ) );
+		this.transaction = new GameUpdateTransaction( this.updatesDAO , this.registry , 1 );
+	}
+
+
+	/** Test return value when the stored procedure says that no more updates are to be processed */
+	@Test
+	public void testWhenFinished( )
+	{
+		assertTrue( this.transaction.doInTransaction( null ) );
+	}
+
+
+	/** Test return value when the stored procedure says that more updates are to be processed */
+	@Test
+	public void testWhenHasMore( )
+	{
+		this.updatesDAO.values = new String[] {
+			null
+		};
+		assertFalse( this.transaction.doInTransaction( null ) );
+	}
+
+
+	/** Test what happens when the stored procedure indicates the need for local processing */
+	@Test
+	public void testLocalProcessing( )
+	{
+		this.updatesDAO.values = new String[] {
+			"test"
+		};
+		assertFalse( this.transaction.doInTransaction( null ) );
+		assertEquals( 1 , this.processor.getTicks( ).size( ) );
+		assertTrue( this.processor.getTicks( ).contains( 1L ) );
+	}
+
+
+	/**
+	 * Test what happens when the stored procedure indicates the need for local processing but the
+	 * specified processor does not exist
+	 */
+	@Test( expected = UnsupportedUpdateException.class )
+	public void testMissingProcessor( )
+	{
+		this.updatesDAO.values = new String[] {
+			"missing processor"
+		};
+		this.transaction.doInTransaction( null );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestServerProcessorRegistryBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestServerProcessorRegistryBean.java
new file mode 100644
index 0000000..65fca67
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestServerProcessorRegistryBean.java
@@ -0,0 +1,62 @@
+package com.deepclone.lw.beans.updates;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.BeanInitializationException;
+
+
+
+/**
+ * Tests for {@link ServerProcessorRegistryBean}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestServerProcessorRegistryBean
+{
+	/** The "guinea pig" instance */
+	private ServerProcessorRegistryBean registry;
+
+
+	@Before
+	public void setUp( )
+	{
+		this.registry = new ServerProcessorRegistryBean( );
+		this.registry.postProcessAfterInitialization( new DummyBatchProcessor( "test" ) , "test" );
+	}
+
+
+	/** The registry returns <code>null</code> when the type has no processor */
+	@Test
+	public void testMissingReturnsNull( )
+	{
+		assertNull( this.registry.getProcessorFor( "does not exist" ) );
+	}
+
+
+	/** The registry returns the processor when there is one */
+	@Test
+	public void testExistingReturnsProcessor( )
+	{
+		assertNotNull( this.registry.getProcessorFor( "test" ) );
+	}
+
+
+	/** The registry ignores objects which are not batch processors */
+	@Test
+	public void testIgnoresOtherObjects( )
+	{
+		this.registry.postProcessAfterInitialization( "test" , "test" );
+	}
+
+
+	/** The registry crashes when two batch processors are defined for the same type */
+	@Test( expected = BeanInitializationException.class )
+	public void testFailsIfDuplicate( )
+	{
+		this.registry.postProcessAfterInitialization( new DummyBatchProcessor( "test" ) , "test" );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockLogger.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockLogger.java
new file mode 100644
index 0000000..a047453
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockLogger.java
@@ -0,0 +1,34 @@
+package com.deepclone.lw.testing;
+
+
+import java.util.HashMap;
+
+import com.deepclone.lw.interfaces.eventlog.Logger;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+
+
+
+/**
+ * A mock Logger component
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class MockLogger
+		implements Logger
+{
+	/** Map of existing system loggers */
+	private final HashMap< String , MockSystemLogger > loggers = new HashMap< String , MockSystemLogger >( );
+
+
+	@Override
+	public SystemLogger getSystemLogger( String component )
+	{
+		MockSystemLogger logger = this.loggers.get( component );
+		if ( logger == null ) {
+			this.loggers.put( component , logger = new MockSystemLogger( ) );
+		}
+		return logger;
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemLogger.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemLogger.java
new file mode 100644
index 0000000..5d15c20
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemLogger.java
@@ -0,0 +1,62 @@
+package com.deepclone.lw.testing;
+
+
+import com.deepclone.lw.cmd.admin.logs.LogLevel;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+
+
+
+/**
+ * A mock logger for components
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class MockSystemLogger
+		implements SystemLogger
+{
+	/** How many messages were logged */
+	private int counter = 0;
+
+
+	/** @return the amount of messages that were logged */
+	public int getCounter( )
+	{
+		return counter;
+	}
+
+
+	/**
+	 * Set the message counter
+	 * 
+	 * @param counter
+	 *            the counter's new value
+	 */
+	public void setCounter( int counter )
+	{
+		this.counter = counter;
+	}
+
+
+	@Override
+	public SystemLogger log( LogLevel level , String message )
+	{
+		this.counter++;
+		return this;
+	}
+
+
+	@Override
+	public SystemLogger log( LogLevel level , String message , Throwable exception )
+	{
+		this.counter++;
+		return this;
+	}
+
+
+	@Override
+	public SystemLogger flush( )
+	{
+		return this;
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemStatus.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemStatus.java
new file mode 100644
index 0000000..52bb8f8
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemStatus.java
@@ -0,0 +1,148 @@
+package com.deepclone.lw.testing;
+
+
+import com.deepclone.lw.interfaces.sys.MaintenanceData;
+import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
+import com.deepclone.lw.interfaces.sys.SystemStatus;
+import com.deepclone.lw.interfaces.sys.TickStatusException;
+
+
+
+/**
+ * A mock system status component
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class MockSystemStatus
+		implements SystemStatus
+{
+	/** Maintenance data to return when {@link #checkMaintenance()} is called */
+	private MaintenanceData maintenance = null;
+
+	/** Value that determines the behaviour of {@link #startTick()} */
+	private long stValue = 0;
+
+	/** Value that determines the behaviour of {@link #checkStuckTick()} */
+	private long cstValue = -1;
+
+	/** Was {@link #startTick()} called? */
+	private boolean startTickCalled = false;
+
+
+	/**
+	 * Set the maintenance data to return when {@link #checkMaintenance()} is called
+	 * 
+	 * @param maintenance
+	 *            the data to return from {@link #checkMaintenance()}
+	 */
+	public void setMaintenance( MaintenanceData maintenance )
+	{
+		this.maintenance = maintenance;
+	}
+
+
+	/**
+	 * Set the value that determines the behaviour of {@link #checkStuckTick()}
+	 * 
+	 * <ul>
+	 * <li>-2 will cause a {@link MaintenanceStatusException},
+	 * <li>-1 will cause it to return <code>null</code>,
+	 * <li>any other value will be returned.
+	 * </ul>
+	 * 
+	 * @param cstValue
+	 *            the value
+	 */
+	public void setCSTBehaviour( long cstValue )
+	{
+		this.cstValue = cstValue;
+	}
+
+
+	/**
+	 * Set the value that determines the behaviour of {@link #startTick()}
+	 * 
+	 * <ul>
+	 * <li>-2 will cause a {@link MaintenanceStatusException},
+	 * <li>-1 will cause a {@link TickStatusException},
+	 * <li>any other value will be returned.
+	 * </ul>
+	 * 
+	 * @param cstValue
+	 *            the value
+	 */
+	public void setSTBehaviour( long stValue )
+	{
+		this.stValue = stValue;
+	}
+
+
+	/**
+	 * @return <code>true</code> if {@link #startTick()} was called.
+	 */
+	public boolean wasStartTickCalled( )
+	{
+		return startTickCalled;
+	}
+
+
+	@Override
+	public MaintenanceData checkMaintenance( )
+	{
+		return this.maintenance;
+	}
+
+
+	@Override
+	public void startMaintenance( int adminId , String reason , int duration )
+			throws MaintenanceStatusException
+	{
+		// EMPTY - ignored
+	}
+
+
+	@Override
+	public void updateMaintenance( int adminId , int durationFromNow )
+			throws MaintenanceStatusException
+	{
+		// EMPTY - ignored
+	}
+
+
+	@Override
+	public void endMaintenance( int adminId )
+			throws MaintenanceStatusException
+	{
+		// EMPTY - ignored
+	}
+
+
+	@Override
+	public long startTick( )
+			throws TickStatusException , MaintenanceStatusException
+	{
+		this.startTickCalled = true;
+		if ( this.stValue == -1 ) {
+			throw new TickStatusException( );
+		}
+		if ( this.stValue == -2 ) {
+			throw new MaintenanceStatusException( );
+		}
+		return this.stValue;
+	}
+
+
+	@Override
+	public Long checkStuckTick( )
+			throws MaintenanceStatusException
+	{
+		if ( this.cstValue == -1 ) {
+			return null;
+		}
+		if ( this.cstValue == -2 ) {
+			throw new MaintenanceStatusException( );
+		}
+		return this.cstValue;
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTicker.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTicker.java
new file mode 100644
index 0000000..6dc8fe9
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTicker.java
@@ -0,0 +1,82 @@
+package com.deepclone.lw.testing;
+
+
+import com.deepclone.lw.interfaces.sys.Ticker;
+
+import static org.junit.Assert.*;
+
+
+
+/**
+ * A mock ticker component
+ * 
+ * <p>
+ * This mock component can be used to make sure that another component registers some task as
+ * expected.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class MockTicker
+		implements Ticker
+{
+	/** The task's expected frequency */
+	private final Frequency expectedFrequency;
+
+	/** The task's expected name */
+	private final String expectedName;
+
+	/** The task's expected type */
+	private final Class< ? > expectedType;
+
+
+	/**
+	 * Initialise the mock ticker's expectations
+	 * 
+	 * @param expectedFrequency
+	 *            the task's expected frequency
+	 * @param expectedName
+	 *            the task's expected name
+	 * @param expectedType
+	 *            the task's expected type
+	 */
+	public MockTicker( Frequency expectedFrequency , String expectedName , Class< ? > expectedType )
+	{
+		this.expectedFrequency = expectedFrequency;
+		this.expectedName = expectedName;
+		this.expectedType = expectedType;
+	}
+
+
+	@Override
+	public void registerTask( Frequency frequency , String name , Runnable task )
+	{
+		assertEquals( this.expectedFrequency , frequency );
+		assertEquals( this.expectedName , name );
+		assertTrue( this.expectedType.isInstance( task ) );
+	}
+
+
+	@Override
+	public void pause( )
+			throws IllegalStateException
+	{
+		// EMPTY - ignored
+	}
+
+
+	@Override
+	public void unpause( )
+			throws IllegalStateException
+	{
+		// EMPTY - ignored
+	}
+
+
+	@Override
+	public boolean isActive( )
+	{
+		return true;
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTransactionManager.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTransactionManager.java
new file mode 100644
index 0000000..3b68085
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTransactionManager.java
@@ -0,0 +1,43 @@
+package com.deepclone.lw.testing;
+
+
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+
+
+
+/**
+ * A mock transaction manager
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class MockTransactionManager
+		implements PlatformTransactionManager
+{
+
+	@Override
+	public TransactionStatus getTransaction( TransactionDefinition definition )
+			throws TransactionException
+	{
+		return null;
+	}
+
+
+	@Override
+	public void commit( TransactionStatus status )
+			throws TransactionException
+	{
+		// EMPTY
+	}
+
+
+	@Override
+	public void rollback( TransactionStatus status )
+			throws TransactionException
+	{
+		// EMPTY
+	}
+
+}
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index 86d3192..bd3479b 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -168,6 +168,11 @@
 				<groupId>com.deepclone.lw</groupId>
 				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-updates</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
 			<dependency>
 				<artifactId>legacyworlds-server-beans-user</artifactId>
 				<groupId>com.deepclone.lw</groupId>

From 597429fadf83bd66bc2983d232eabcb46fc4048b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 4 Feb 2012 10:43:12 +0100
Subject: [PATCH 39/94] In-game resources views

* Added session records to carry resource information over to the
clients

* Added SQL support code for the various views

* Added interface and implementation of the resource information access
component

* Hooked resources information queries into both the empire and planet
management component

* Added resources display to planet and overview pages
---
 .../resources/AbstractResourceMapper.java     |  61 ++++++
 .../game/resources/EmpireResourceMapper.java  |  72 +++++++
 .../game/resources/PlanetResourceMapper.java  | 105 ++++++++++
 .../ResourcesInformationDAOBean.java          |  99 ++++++++++
 .../configuration/game/resources-beans.xml    |   8 +
 .../lw/beans/empire/EmpireManagementBean.java |  16 +-
 .../lw/beans/map/PlanetsManagementBean.java   |  15 +-
 .../src/main/resources/configuration/game.xml |   8 +
 .../parts/040-functions/025-resources.sql     |  56 ++++++
 .../parts/040-functions/040-empire.sql        | 110 ++++++++++-
 .../040-functions/145-resource-providers.sql  | 136 +++++++++++++
 .../050-resource-category-weight-view.sql     |  47 +++++
 .../060-ordered-resources-view.sql            |  57 ++++++
 .../040-empire/020-planet-resources-view.sql  |  56 ++++++
 .../040-empire/030-resources-view.sql         | 169 ++++++++++++++++
 .../040-get-planet-resources.sql              | 187 ++++++++++++++++++
 .../050-resource-category-weight-view.sql     |  11 ++
 .../060-ordered-resources-view.sql            |  11 ++
 .../040-empire/020-planet-resources-view.sql  |  11 ++
 .../040-empire/030-resources-view.sql         |  11 ++
 .../040-get-planet-resources.sql              |  13 ++
 .../db-structure/tests/utils/strings.sql      |  39 ++--
 .../resources/ResourcesInformationDAO.java    |  43 ++++
 .../src/main/resources/lw-server.xml          |   4 +-
 .../resources/TestAbstractResourceMapper.java | 126 ++++++++++++
 .../resources/TestEmpireResourceMapper.java   | 104 ++++++++++
 .../resources/TestPlanetResourceMapper.java   | 131 ++++++++++++
 .../deepclone/lw/testing/MockResultSet.java   |  22 +++
 .../player/gdata/AbstractResourceRecord.java  | 109 ++++++++++
 .../gdata/empire/EmpireResourceRecord.java    | 152 ++++++++++++++
 .../cmd/player/gdata/empire/OverviewData.java | 131 +++++++++++-
 .../player/gdata/planets/PlanetOwnView.java   | 175 +++++++++++++++-
 .../gdata/planets/PlanetResourceRecord.java   | 125 ++++++++++++
 .../gdata/planets/ResourceProviderRecord.java | 117 +++++++++++
 .../gdata/TestAbstractResourceRecord.java     |  88 +++++++++
 .../empire/TestEmpireResourceRecord.java      | 125 ++++++++++++
 .../planets/TestPlanetResourceRecord.java     |  82 ++++++++
 .../planets/TestResourceProviderRecord.java   |  83 ++++++++
 .../Content/Raw/WEB-INF/fm/en/game.ftl        |   9 +-
 .../Raw/WEB-INF/fm/en/types/overview.ftl      |  57 ++++++
 .../Raw/WEB-INF/fm/en/types/planet.ftl        | 107 +++++++++-
 .../Content/Raw/WEB-INF/fm/fr/game.ftl        |   9 +-
 .../Raw/WEB-INF/fm/fr/types/overview.ftl      |  57 ++++++
 .../Raw/WEB-INF/fm/fr/types/planet.ftl        | 101 ++++++++++
 .../Content/Raw/WEB-INF/fm/layout/lists.ftl   |   8 +-
 45 files changed, 3211 insertions(+), 52 deletions(-)
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/AbstractResourceMapper.java
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/EmpireResourceMapper.java
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/PlanetResourceMapper.java
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesInformationDAOBean.java
 create mode 100644 legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources-beans.xml
 create mode 100644 legacyworlds-server-beans-simple/src/main/resources/configuration/game.xml
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/050-resource-category-weight-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/060-ordered-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/020-planet-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/030-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/040-get-planet-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/050-resource-category-weight-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/060-ordered-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/020-planet-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/030-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/040-get-planet-resources.sql
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesInformationDAO.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestAbstractResourceMapper.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestEmpireResourceMapper.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestPlanetResourceMapper.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/AbstractResourceRecord.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/EmpireResourceRecord.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetResourceRecord.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/ResourceProviderRecord.java
 create mode 100644 legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/TestAbstractResourceRecord.java
 create mode 100644 legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/empire/TestEmpireResourceRecord.java
 create mode 100644 legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/planets/TestPlanetResourceRecord.java
 create mode 100644 legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/planets/TestResourceProviderRecord.java

diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/AbstractResourceMapper.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/AbstractResourceMapper.java
new file mode 100644
index 0000000..39e7847
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/AbstractResourceMapper.java
@@ -0,0 +1,61 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.cmd.player.gdata.AbstractResourceRecord;
+
+
+
+/**
+ * Base class for resource information row mappers
+ * 
+ * <p>
+ * This class can be used to map resource information rows (for either planets or empires). It
+ * provides a method which maps the resource information's descriptive fields, and implements
+ * Spring's {@link RowMapper} for the correct type.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ * @param <RTYPE>
+ *            a resource information record based on {@link AbstractResourceRecord}.
+ */
+abstract class AbstractResourceMapper< RTYPE extends AbstractResourceRecord >
+		implements RowMapper< RTYPE >
+{
+
+	/**
+	 * Extract a resource information row's descriptive fields
+	 * 
+	 * <p>
+	 * This method will extract the descriptive fields from a resource information row and store
+	 * them in a resource information instance. It extracts:
+	 * <ul>
+	 * <li>the resource's text identifier,
+	 * <li>the resource's internationalised name,
+	 * <li>the resource's internationalised description,
+	 * <li>the resource's internationalised category name, if there is one.
+	 * </ul>
+	 * 
+	 * @param resource
+	 *            the resource information record
+	 * @param rs
+	 *            the result set with the correct row selected
+	 * 
+	 * @throws SQLException
+	 *             if a SQLException is encountered getting column values
+	 */
+	protected final void getResourceDescription( RTYPE resource , ResultSet rs )
+			throws SQLException
+	{
+		resource.setIdentifier( rs.getString( "resource_identifier" ) );
+		resource.setTitle( rs.getString( "resource_name" ) );
+		resource.setDescription( rs.getString( "resource_description" ) );
+		resource.setCategory( rs.getString( "resource_category" ) );
+
+	}
+
+}
diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/EmpireResourceMapper.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/EmpireResourceMapper.java
new file mode 100644
index 0000000..71e6b7c
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/EmpireResourceMapper.java
@@ -0,0 +1,72 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import com.deepclone.lw.cmd.player.gdata.empire.EmpireResourceRecord;
+
+
+
+/**
+ * Row mapper for empire resources
+ * 
+ * <p>
+ * This class is responsible for converting empire resource information rows into instances of the
+ * corresponding class, {@link EmpireResourceRecord}.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class EmpireResourceMapper
+		extends AbstractResourceMapper< EmpireResourceRecord >
+{
+
+	/**
+	 * Map a row from <code>emp.resources_view</code>
+	 * 
+	 * <p>
+	 * This method extracts the resource's description and the fields specific to empire resource
+	 * information from the row, returning the information as an {@link EmpireResourceRecord}
+	 * instance.
+	 */
+	@Override
+	public EmpireResourceRecord mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		EmpireResourceRecord resource = new EmpireResourceRecord( );
+		this.getResourceDescription( resource , rs );
+		this.getEmpireFields( resource , rs );
+		return resource;
+	}
+
+
+	/**
+	 * Read empire-specific resource information from a row
+	 * 
+	 * <p>
+	 * This method extracts the stockpiled quantity as well as income and upkeep from the row. If a
+	 * mining setting is set, it extracts it as well.
+	 * 
+	 * @param resource
+	 *            the empire resource record
+	 * @param rs
+	 *            the result set with the correct row selected
+	 * 
+	 * @throws SQLException
+	 *             if a SQLException is encountered getting column values
+	 */
+	private void getEmpireFields( EmpireResourceRecord resource , ResultSet rs )
+			throws SQLException
+	{
+		resource.setStockpiled( rs.getLong( "empres_possessed" ) );
+		resource.setPlanetUpkeep( rs.getLong( "planets_upkeep" ) );
+		resource.setIncome( rs.getLong( "planets_income" ) );
+		resource.setFleetUpkeep( rs.getLong( "fleets_upkeep" ) );
+
+		int miningPriority = rs.getInt( "empmset_weight" );
+		if ( !rs.wasNull( ) ) {
+			resource.setMiningPriority( miningPriority );
+		}
+	}
+
+}
diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/PlanetResourceMapper.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/PlanetResourceMapper.java
new file mode 100644
index 0000000..f35426d
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/PlanetResourceMapper.java
@@ -0,0 +1,105 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import com.deepclone.lw.cmd.player.gdata.planets.PlanetResourceRecord;
+import com.deepclone.lw.cmd.player.gdata.planets.ResourceProviderRecord;
+
+
+
+/**
+ * Row mapper for planet resources
+ * 
+ * <p>
+ * This class maps rows obtained from the planet resources information stored procedure into
+ * instances of {@link PlanetResourceRecord} which can be sent to a client.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class PlanetResourceMapper
+		extends AbstractResourceMapper< PlanetResourceRecord >
+{
+
+	/**
+	 * Map a row from <code>emp.get_planet_resources( )</code>
+	 * 
+	 * <p>
+	 * This method extracts fields that are always present, generating the
+	 * {@link PlanetResourceRecord} instance and setting it accordingly, then reads resource
+	 * provider columns and adds a {@link ResourceProviderRecord} instance to the record if
+	 * necessary.
+	 */
+	@Override
+	public PlanetResourceRecord mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		PlanetResourceRecord resource = getResourceFields( rs );
+		getResourceProvider( rs , resource );
+		return resource;
+	}
+
+
+	/**
+	 * Map common fields into a {@link PlanetResourceRecord} instance
+	 * 
+	 * <p>
+	 * This method creates the instance then reads descriptive fields, then extracts the income,
+	 * upkeep and investment fields.
+	 * 
+	 * @param rs
+	 *            the JDBC result set that contains the row being extracted
+	 * 
+	 * @return the new {@link PlanetResourceRecord} instance with its common fields set
+	 * 
+	 * @throws SQLException
+	 *             if a SQLException is encountered getting column values
+	 */
+	private PlanetResourceRecord getResourceFields( ResultSet rs )
+			throws SQLException
+	{
+		PlanetResourceRecord resource = new PlanetResourceRecord( );
+		this.getResourceDescription( resource , rs );
+		resource.setIncome( rs.getLong( "pres_income" ) );
+		resource.setUpkeep( rs.getLong( "pres_upkeep" ) );
+		resource.setInvested( rs.getLong( "pres_invested" ) );
+		return resource;
+	}
+
+
+	/**
+	 * Map resource provider fields if they are present.
+	 * 
+	 * <p>
+	 * Check if the record includes resource provider information. If it does, extract the fields'
+	 * values into a {@link ResourceProviderRecord} instance and add it to the
+	 * {@link PlanetResourceRecord}.
+	 * 
+	 * @param rs
+	 *            the JDBC result set that contains the row being extracted
+	 * @param resource
+	 *            the {@link PlanetResourceRecord} instance to add information to
+	 * 
+	 * @throws SQLException
+	 *             if a SQLException is encountered getting column values
+	 */
+	private void getResourceProvider( ResultSet rs , PlanetResourceRecord resource )
+			throws SQLException
+	{
+		long capacity = rs.getLong( "resprov_capacity" );
+		if ( rs.wasNull( ) ) {
+			return;
+		}
+
+		ResourceProviderRecord provider = new ResourceProviderRecord( );
+
+		provider.setCapacity( capacity );
+		provider.setQuantity( rs.getLong( "resprov_quantity" ) );
+		provider.setDifficulty( rs.getInt( "resprov_difficulty" ) );
+		provider.setPriority( rs.getInt( "mset_weight" ) );
+
+		resource.setResourceProvider( provider );
+	}
+
+}
diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesInformationDAOBean.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesInformationDAOBean.java
new file mode 100644
index 0000000..651d4e5
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesInformationDAOBean.java
@@ -0,0 +1,99 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import com.deepclone.lw.cmd.player.gdata.empire.EmpireResourceRecord;
+import com.deepclone.lw.cmd.player.gdata.planets.PlanetResourceRecord;
+import com.deepclone.lw.interfaces.game.resources.ResourcesInformationDAO;
+
+
+
+/**
+ * Resource information access component
+ * 
+ * <p>
+ * This component's goal is to read information about resources from the database and return the
+ * records in some usable format.
+ * 
+ * <p>
+ * It does not contain any method that actually change the database.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+class ResourcesInformationDAOBean
+		implements ResourcesInformationDAO
+{
+
+	/** SQL query that fetches a planet's resources information */
+	private static final String Q_PLANET_RESOURCES = "SELECT * FROM emp.get_planet_resources( ? )";
+
+	/** SQL query that fetches an empire's resources information */
+	private static final String Q_EMPIRE_RESOURCES = "SELECT * FROM emp.resources_view WHERE empire_id = ?";
+
+	/** Row mapper for planet resources */
+	private final PlanetResourceMapper mPlanetResource;
+
+	/** Row mapper for empire resources */
+	private final EmpireResourceMapper mEmpireResource;
+
+	/** Spring JDBC interface */
+	private JdbcTemplate dTemplate;
+
+
+	/** Initialise the necessary row mappers */
+	public ResourcesInformationDAOBean( )
+	{
+		this.mPlanetResource = new PlanetResourceMapper( );
+		this.mEmpireResource = new EmpireResourceMapper( );
+	}
+
+
+	/**
+	 * Dependency injector that sets the data source
+	 * 
+	 * @param dataSource
+	 *            the data source
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.dTemplate = new JdbcTemplate( dataSource );
+	}
+
+
+	/**
+	 * Run the planet resources information query and extract the data
+	 * 
+	 * <p>
+	 * This implementation simply executes a query using the <code>emp.get_planet_resources()</code>
+	 * stored procedure and maps the resulting rows into a list of {@link PlanetResourceRecord}
+	 * instances.
+	 */
+	@Override
+	public List< PlanetResourceRecord > getPlanetInformation( int planet )
+	{
+		return this.dTemplate.query( Q_PLANET_RESOURCES , this.mPlanetResource , planet );
+	}
+
+
+	/**
+	 * Run the empire resources information query and extract the data
+	 * 
+	 * <p>
+	 * This implementation executes a query on <code>emp.resources_view</code> for a specific
+	 * empire, and maps the resulting rows into a list of {@link EmpireResourceRecord} instances.
+	 */
+	@Override
+	public List< EmpireResourceRecord > getEmpireInformation( int empire )
+	{
+		return this.dTemplate.query( Q_EMPIRE_RESOURCES , this.mEmpireResource , empire );
+	}
+
+}
diff --git a/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources-beans.xml b/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources-beans.xml
new file mode 100644
index 0000000..2c36ed9
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources-beans.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+	<bean id="resourcesInformationDAO" class="com.deepclone.lw.beans.game.resources.ResourcesInformationDAOBean" />
+
+</beans>
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
index 18a9571..2d769f3 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
@@ -25,6 +25,7 @@ import com.deepclone.lw.interfaces.game.BattlesCache;
 import com.deepclone.lw.interfaces.game.BattlesDAO;
 import com.deepclone.lw.interfaces.game.EmpireDAO;
 import com.deepclone.lw.interfaces.game.EmpireManagement;
+import com.deepclone.lw.interfaces.game.resources.ResourcesInformationDAO;
 import com.deepclone.lw.interfaces.naming.NamingDAO;
 import com.deepclone.lw.interfaces.prefs.AccountPreferences;
 import com.deepclone.lw.interfaces.prefs.PreferencesDAO;
@@ -46,6 +47,7 @@ public class EmpireManagementBean
 	private EmpireDAO empireDao;
 	private PreferencesDAO prefsDao;
 	private BattlesDAO battlesDao;
+	private ResourcesInformationDAO resourcesInformationDao;
 
 
 	@Autowired( required = true )
@@ -83,6 +85,13 @@ public class EmpireManagementBean
 	}
 
 
+	@Autowired( required = true )
+	public void setResourcesInformationDao( ResourcesInformationDAO resourcesInformationDao )
+	{
+		this.resourcesInformationDao = resourcesInformationDao;
+	}
+
+
 	@Override
 	public Integer getEmpireId( EmailAddress address )
 	{
@@ -103,8 +112,9 @@ public class EmpireManagementBean
 		AccountPreferences prefs = this.prefsDao.getPreferences( generalInformation.getAccountId( ) );
 		boolean rlTime = prefs.getPreference( "useRLTime" , Boolean.class );
 
-		return new GamePageData( generalInformation.getName( ) , generalInformation.getStatus( ) , generalInformation
-				.getTag( ) , generalInformation.getCash( ) , generalInformation.getNextTick( ) , planets , rlTime );
+		return new GamePageData( generalInformation.getName( ) , generalInformation.getStatus( ) ,
+				generalInformation.getTag( ) , generalInformation.getCash( ) , generalInformation.getNextTick( ) ,
+				planets , rlTime );
 	}
 
 
@@ -150,6 +160,8 @@ public class EmpireManagementBean
 			battles.add( entry );
 		}
 
+		overview.setEconomy( this.resourcesInformationDao.getEmpireInformation( empireId ) );
+
 		return new EmpireResponse( this.getGeneralInformation( empireId ) , overview , research , battles );
 	}
 
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
index 709fe4b..0e364ca 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
@@ -19,6 +19,7 @@ import com.deepclone.lw.cmd.player.planets.ViewPlanetResponse;
 import com.deepclone.lw.interfaces.game.EmpireManagement;
 import com.deepclone.lw.interfaces.game.PlanetDAO;
 import com.deepclone.lw.interfaces.game.PlanetsManagement;
+import com.deepclone.lw.interfaces.game.resources.ResourcesInformationDAO;
 import com.deepclone.lw.interfaces.naming.NamingDAO;
 import com.deepclone.lw.sqld.game.PlanetData;
 import com.deepclone.lw.sqld.game.PlanetData.AccessType;
@@ -36,6 +37,7 @@ public class PlanetsManagementBean
 	private EmpireManagement empireManagement;
 	private PlanetDAO planetDao;
 	private NamingDAO namingDao;
+	private ResourcesInformationDAO resourcesInformationDao;
 
 
 	@Autowired( required = true )
@@ -59,6 +61,13 @@ public class PlanetsManagementBean
 	}
 
 
+	@Autowired( required = true )
+	public void setResourcesInformationDao( ResourcesInformationDAO resourcesInformationDao )
+	{
+		this.resourcesInformationDao = resourcesInformationDao;
+	}
+
+
 	private PlanetOrbitalView getOrbitalView( int empireId , int planetId , AccessType access )
 	{
 		if ( access == AccessType.BASIC ) {
@@ -107,6 +116,8 @@ public class PlanetsManagementBean
 		view.setbBuildings( this.planetDao.getAvailableBuildings( planetId ) );
 		view.setbShips( this.planetDao.getAvailableShips( planetId ) );
 
+		view.setResources( this.resourcesInformationDao.getPlanetInformation( planetId ) );
+
 		return view;
 	}
 
@@ -199,8 +210,8 @@ public class PlanetsManagementBean
 		if ( one == null ) {
 			return new ViewPlanetResponse( planetId , page , this.getBasicView( basic ) , orbital , owner );
 		}
-		return new RenamePlanetResponse( planetId , page , this.getBasicView( basic ) , orbital , owner , name , one
-				.toString( ) );
+		return new RenamePlanetResponse( planetId , page , this.getBasicView( basic ) , orbital , owner , name ,
+				one.toString( ) );
 	}
 
 
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/game.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/game.xml
new file mode 100644
index 0000000..952f3f6
--- /dev/null
+++ b/legacyworlds-server-beans-simple/src/main/resources/configuration/game.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+	<import resource="game/resources-beans.xml" />
+
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
index 3a47212..4a7fa06 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
@@ -505,3 +505,59 @@ GRANT EXECUTE
 			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
 			DOUBLE PRECISION )
 	TO :dbuser;
+
+
+
+/*
+ * View of resource category weights
+ * 
+ * This view computes the average resource weight per category for all
+ * resource definitions.
+ * 
+ * Fields:
+ *		resource_category_id		The category's identifier
+ *		resource_category_weight	The average weight of resource defintions
+ *										in the category.
+ */
+DROP VIEW IF EXISTS defs.resource_category_weight_view CASCADE;
+CREATE VIEW defs.resource_category_weight_view
+	AS SELECT resource_category_id ,
+				AVG( resource_weight ) AS resource_category_weight
+			FROM defs.resources
+			WHERE resource_category_id IS NOT NULL
+			GROUP BY resource_category_id;
+
+
+
+/*
+ * Ordered resource definitions
+ * 
+ * This view contains the name, category and description identifier for all
+ * resource definitions, ordered based on the category's average weight and
+ * the resource's own weight.
+ * 
+ * Fields:
+ *		resource_name_id		The identifier of the resource's name
+ *		resource_category_id	The identifier of the category's name, or NULL
+ *									if the resource is not in a category
+ *		resource_description_id	The identifier of the resource's description
+ *		resource_ordering		The index of the resource in a sorted view
+ */
+DROP VIEW IF EXISTS defs.ordered_resources_view CASCADE;
+CREATE VIEW defs.ordered_resources_view
+	AS SELECT resource_name_id , resource_category_id , resource_description_id ,
+				row_number( ) OVER(
+					ORDER BY (
+						CASE
+							WHEN resource_category_id IS NULL THEN
+								resource_weight
+							ELSE
+								resource_category_weight
+						END ) , resource_weight
+				) AS resource_ordering
+
+			FROM defs.resources
+				LEFT OUTER JOIN defs.resource_category_weight_view
+					USING ( resource_category_id )
+			
+			;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 63ac90e..cb30603 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -668,4 +668,112 @@ CREATE VIEW emp.enemy_lists
 					INNER JOIN emp.alliances a ON a.id = el.alliance_id
 		) AS x;
 
-GRANT SELECT ON emp.enemy_lists TO :dbuser;
\ No newline at end of file
+GRANT SELECT ON emp.enemy_lists TO :dbuser;
+
+
+/*
+ * Planets income and upkeep totals
+ * 
+ * This view computes the totals of planets' incomes and upkeeps for each
+ * empire and resource type.
+ * 
+ * FIXME: time-related factor is hardcoded
+ * 
+ * Fields:
+ *		empire_id			The empire's identifier
+ *		resource_name_id	The identifier of the resource type
+ *		planets_income		The planets' income over 12h RT / 1 month GT,
+ *								rounded down
+ *		planets_upkeep		The planets' upkeep over 12h RT / 1 month GT,
+ *								rounded up
+ */
+DROP VIEW IF EXISTS emp.planet_resources_view CASCADE;
+CREATE VIEW emp.planet_resources_view
+	AS SELECT
+			empire_id , resource_name_id ,
+			FLOOR( SUM( pres_income ) * 720.0 )::BIGINT AS planets_income ,
+			CEIL( SUM( pres_upkeep ) * 720.0 )::BIGINT AS planets_upkeep
+		FROM emp.planets
+			LEFT OUTER JOIN verse.planet_resources
+				USING ( planet_id )
+		GROUP BY empire_id , resource_name_id;
+
+
+/*
+ * Empire resources view
+ * 
+ * This view contains all resource-related information for each empire and
+ * resource type.
+ * 
+ * FIXME: fleets upkeep is set to 0 at the moment.
+ * 
+ * Fields:
+ *		empire_id				The empire's identifier
+ *		resource_identifier		The text identifier of the resource
+ *		resource_name			The internationalised name of the resource
+ *		resource_description	The internationalised description of the
+ *									resource
+ *		resource_category		The internationalised category of the resource,
+ *									or NULL if the resource is not in a
+ *									category.
+ *		empres_possessed		The empire's stockpile of this type of
+ *									resource, rounded down
+ *		empmset_weight			The empire-wide mining setting for the
+ *									resource type, or NULL if this is a basic
+ *									resource
+ *		planets_income			The planets' total income
+ *		planets_upkeep			The planets' total upkeep
+ *		fleets_upkeep			The fleets' total upkeep
+ */
+DROP VIEW IF EXISTS emp.resources_view CASCADE;
+CREATE VIEW emp.resources_view
+	AS SELECT
+			empire_id ,
+			_r_name_str.name AS resource_identifier ,
+			_r_name.translated_string AS resource_name ,
+			_r_desc.translated_string AS resource_description ,
+			_r_cat.translated_string AS resource_category ,
+			FLOOR( empres_possessed )::BIGINT AS empres_possessed ,
+			empmset_weight ,
+			( CASE
+				WHEN planets_income IS NULL THEN
+					0::BIGINT
+				ELSE
+					planets_income
+			END ) AS planets_income ,
+			( CASE
+				WHEN planets_upkeep IS NULL THEN
+					0::BIGINT
+				ELSE
+					planets_upkeep
+			END ) AS planets_upkeep ,
+			0::BIGINT AS fleets_upkeep
+
+		FROM defs.ordered_resources_view
+			INNER JOIN emp.resources
+				USING ( resource_name_id )
+			INNER JOIN naming.empire_names _name
+				ON _name.id = empire_id
+			INNER JOIN users.credentials _creds
+				ON _creds.address_id = _name.owner_id
+			INNER JOIN defs.strings _r_name_str
+				ON _r_name_str.id = resource_name_id
+			INNER JOIN defs.translations _r_name
+				ON _r_name.string_id = resource_name_id
+					AND _r_name.lang_id = _creds.language_id
+			INNER JOIN defs.translations _r_desc
+				ON _r_desc.string_id = resource_description_id
+					AND _r_desc.lang_id = _creds.language_id
+			LEFT OUTER JOIN defs.translations _r_cat
+				ON _r_cat.string_id = resource_category_id
+					AND _r_cat.lang_id = _creds.language_id
+			LEFT OUTER JOIN emp.mining_settings
+				USING ( empire_id , resource_name_id )
+			LEFT OUTER JOIN emp.planet_resources_view
+				USING ( empire_id , resource_name_id )
+
+		ORDER BY resource_ordering;
+		
+GRANT SELECT
+	ON emp.resources_view
+	TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
index 0dfccb2..74b758b 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
@@ -139,3 +139,139 @@ REVOKE EXECUTE
 		DOUBLE PRECISION , DOUBLE PRECISION )
 	FROM PUBLIC;
 
+
+/*
+ * Planet resources type
+ * 
+ * This type is used to transmit a planet's resources information to the game
+ * server. It contains the resource's description, the planet's economic data
+ * and, if there is a resource provider on the planet, the provider's
+ * information and mining priority.
+ */
+DROP TYPE IF EXISTS emp.planet_resources_type CASCADE;
+CREATE TYPE emp.planet_resources_type AS (
+	/* Text identifier of the resource */
+	resource_identifier		TEXT ,
+	
+	/* Internationalised name of the resource */
+	resource_name			TEXT ,
+	
+	/* Internationalised description of the resource */
+	resource_description	TEXT ,
+	
+	/* Internationalised name of the category the resource is a part of, or
+	 * NULL if the resource is not in any category.
+	 */
+	resource_category		TEXT ,
+	
+	/* The planet's income for this resource, over a period of 12h RT/ 1 month
+	 * GT.
+	 */
+	pres_income				BIGINT ,
+	
+	/* The planet's upkeep for this resource, over a period of 12h RT/ 1 month
+	 * GT.
+	 */
+	pres_upkeep				BIGINT ,
+	
+	/* The current quantity of this resource invested in the planet's build
+	 * queues.
+	 */
+	pres_invested			BIGINT ,
+	
+	/** The capacity of the resource provider, if there is one, or NULL if
+	 * there is no provider.
+	 */
+	resprov_capacity		BIGINT ,
+	
+	/** The quantity of resources in the resource provider, if there is one,
+	 * or NULL if there is no provider.
+	 */
+	resprov_quantity		BIGINT ,
+	
+	/** The extraction difficulty of the resource provider as a percentage, or
+	 * NULL if there is no provider.
+	 */
+	resprov_difficulty		INT ,
+	
+	/* The mining priority for the resource in question, or NULL if there is no
+	 * resource provider.
+	 */
+	mset_weight				INT
+);
+
+
+
+/*
+ * Access all available information about a planet's resources
+ *
+ * This function retrieves resource information about an empire-owned planet,
+ * and converts it to the format used in the game server (rounded quantities,
+ * difficulty as percentage, internationalised strings).
+ * 
+ * FIXME:
+ *		1) pres_invested is always set to 0 in the output
+ *		2) time-related computations use hardcoded values
+ * 
+ * Parameters:
+ *		_planet		The planet's identifier
+ *
+ * Returns:
+ *		N/A			Resource information records, ordered using resource
+ *						weights.
+ */
+DROP FUNCTION IF EXISTS emp.get_planet_resources( INT );
+CREATE FUNCTION emp.get_planet_resources( _planet INT )
+	RETURNS SETOF emp.planet_resources_type
+	STRICT STABLE
+	SECURITY DEFINER
+AS $get_planet_resources$
+
+	SELECT _name_str.name AS resource_identifier ,
+			_name_trans.translated_string AS resource_name ,
+			_desc_trans.translated_string AS resource_description ,
+			_cat_trans.translated_string AS resource_category ,
+			FLOOR( pres_income * 720.0 )::BIGINT AS pres_income ,
+			CEIL( pres_upkeep * 720.0 )::BIGINT AS pres_upkeep ,
+			0::BIGINT AS pres_invested ,
+			ROUND( resprov_quantity_max )::BIGINT AS resprov_capacity ,
+			ROUND( resprov_quantity )::BIGINT AS resprov_quantity ,
+			ROUND( 100.0 * resprov_difficulty )::INT AS resprov_difficulty ,
+			mset_weight
+
+		FROM defs.ordered_resources_view
+			INNER JOIN verse.planet_resources USING ( resource_name_id )
+			INNER JOIN emp.planets USING ( planet_id )
+			INNER JOIN naming.empire_names _emp_name
+				ON _emp_name.id = empire_id
+			INNER JOIN users.credentials _user
+				ON _emp_name.owner_id = _user.address_id
+			INNER JOIN defs.strings _name_str
+				ON _name_str.id = resource_name_id
+			INNER JOIN defs.translations _name_trans
+				ON _name_trans.string_id = resource_name_id
+					AND _name_trans.lang_id = _user.language_id
+			INNER JOIN defs.translations _desc_trans
+				ON _desc_trans.string_id = resource_description_id
+					AND _desc_trans.lang_id = _user.language_id
+			LEFT OUTER JOIN defs.translations _cat_trans
+				ON _cat_trans.string_id = resource_category_id
+					AND _cat_trans.lang_id = _user.language_id
+			LEFT OUTER JOIN verse.resource_providers
+				USING ( planet_id , resource_name_id )
+			LEFT OUTER JOIN emp.mining_settings_view
+				USING ( planet_id , resource_name_id )
+
+	WHERE planet_id = $1
+	
+	ORDER BY resource_ordering;
+
+$get_planet_resources$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.get_planet_resources( INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION emp.get_planet_resources( INT )
+	TO :dbuser;
+
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/050-resource-category-weight-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/050-resource-category-weight-view.sql
new file mode 100644
index 0000000..f5a59ba
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/050-resource-category-weight-view.sql
@@ -0,0 +1,47 @@
+/*
+ * Tests for defs.resource_category_weight_view
+ */
+BEGIN;
+	/*
+	 * We need a few resources, with a known average per category. Some of the
+	 * resources must not belong to any category.
+	 */
+	\i utils/strings.sql
+	SELECT _create_test_strings( 5 , 'resource' );
+	SELECT _create_test_strings( 5 , 'resDesc' );
+	SELECT _create_test_strings( 2 , 'resCat' );
+	
+	INSERT INTO defs.resources(
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_get_string( 'resource1' ) , _get_string( 'resDesc1' ) ,
+			_get_string( 'resCat1' ) , 2
+		) , (
+			_get_string( 'resource2' ) , _get_string( 'resDesc2' ) ,
+			_get_string( 'resCat1' ) , 4
+		) , (
+			_get_string( 'resource3' ) , _get_string( 'resDesc3' ) ,
+			_get_string( 'resCat2' ) , 3
+		) , (
+			_get_string( 'resource4' ) , _get_string( 'resDesc4' ) ,
+			_get_string( 'resCat2' ) , 5
+		) , (
+			_get_string( 'resource5' ) , _get_string( 'resDesc5' ) ,
+			NULL , 150
+		);
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.resource_category_weight_view - Resulting set contains correct values' );
+	SELECT set_eq(
+		$$ SELECT * FROM defs.resource_category_weight_view $$ ,
+		$$ VALUES (
+			_get_string( 'resCat1' ) , 3
+		) , (
+			_get_string( 'resCat2' ) , 4
+		) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/060-ordered-resources-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/060-ordered-resources-view.sql
new file mode 100644
index 0000000..aecee23
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/060-ordered-resources-view.sql
@@ -0,0 +1,57 @@
+/*
+ * Tests for defs.ordered_resources_view
+ */
+BEGIN;
+
+	/* 
+	 * We need:
+	 *  - one resource without category with weight 1,
+	 *  - one resource with category 1 and weight 2,
+	 *  - one resource with weight 4 and no category,
+	 *  - two resourcew with weights 3 and 7 and category 2.
+	 */
+	\i utils/strings.sql
+	SELECT _create_test_strings( 5 , 'resource' );
+	SELECT _create_test_strings( 5 , 'resDesc' );
+	SELECT _create_test_strings( 2 , 'resCat' );
+	
+	INSERT INTO defs.resources(
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_get_string( 'resource1' ) , _get_string( 'resDesc1' ) ,
+			NULL , 1
+		) , (
+			_get_string( 'resource2' ) , _get_string( 'resDesc2' ) ,
+			_get_string( 'resCat1' ) , 2
+		) , (
+			_get_string( 'resource3' ) , _get_string( 'resDesc3' ) ,
+			NULL , 4
+		) , (
+			_get_string( 'resource4' ) , _get_string( 'resDesc4' ) ,
+			_get_string( 'resCat2' ) , 3
+		) , (
+			_get_string( 'resource5' ) , _get_string( 'resDesc5' ) ,
+			_get_string( 'resCat2' ) , 7
+		);
+
+	SELECT plan( 1 );
+	SELECT diag_test_name( 'defs.ordered_resources_view - Resources are in the correct order' );
+	SELECT set_eq(
+		$$ SELECT resource_name_id , resource_ordering
+				FROM defs.ordered_resources_view $$ ,
+		$$ VALUES (
+			_get_string( 'resource1' ) , 1 
+		) , (
+			_get_string( 'resource2' ) , 2 
+		) , (
+			_get_string( 'resource3' ) , 3 
+		) , (
+			_get_string( 'resource4' ) , 4 
+		) , (
+			_get_string( 'resource5' ) , 5 
+		) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/020-planet-resources-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/020-planet-resources-view.sql
new file mode 100644
index 0000000..3130b92
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/020-planet-resources-view.sql
@@ -0,0 +1,56 @@
+/*
+ * Tests for emp.planet_resources_view
+ */
+BEGIN;
+	/*
+	 * Create two empires, one with 2 planets, the other without. Add 2 planet
+	 * resources records.
+	 * 
+	 * Disable all foreign keys to avoid a lot of work.
+	 */
+
+	ALTER TABLE emp.planets
+		DROP CONSTRAINT fk_eplanets_empire ,
+		DROP CONSTRAINT fk_eplanets_planet;
+	ALTER TABLE verse.planet_resources
+		DROP CONSTRAINT fk_pres_planet ,
+		DROP CONSTRAINT fk_pres_resource;
+
+	INSERT INTO verse.planet_resources (
+			planet_id , resource_name_id , pres_income , pres_upkeep
+		) VALUES
+			( 1 , 1 , 1 , 2 ) , ( 1 , 2 , 3 , 4 ) ,
+			( 2 , 1 , 3 , 4 ) , ( 2 , 2 , 5 , 6 ) ,
+			( 3 , 1 , 0.1 / 720 , 0.4 / 720 ) ,
+			( 3 , 2 , 0.9 / 720, 0.9 / 720 );
+	INSERT INTO emp.planets( empire_id , planet_id )
+		VALUES ( 1 , 1 ) , ( 1 , 2 ) , ( 2 , 3 );
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 3 );
+
+	SELECT diag_test_name( 'emp.planet_resources_view - Sums' );
+	SELECT set_eq(
+		$$ SELECT * FROM emp.planet_resources_view WHERE empire_id = 1 $$ ,
+		$$ VALUES ( 1 , 1 , 4 * 720 , 6 * 720 ) , ( 1 , 2 , 8 * 720 , 10 * 720 ) $$
+	);
+
+	SELECT diag_test_name( 'emp.planet_resources_view - Incomes are rounded down' );
+	SELECT set_eq(
+		$$ SELECT resource_name_id , planets_income
+			FROM emp.planet_resources_view
+			WHERE empire_id = 2 $$ ,
+		$$ VALUES ( 1 , 0 ) , ( 2 , 0 ) $$
+	);
+
+	SELECT diag_test_name( 'emp.planet_resources_view - Upkeeps are rounded up' );
+	SELECT set_eq(
+		$$ SELECT resource_name_id , planets_upkeep
+			FROM emp.planet_resources_view
+			WHERE empire_id = 2 $$ ,
+		$$ VALUES ( 1 , 1 ) , ( 2 , 1 ) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/030-resources-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/030-resources-view.sql
new file mode 100644
index 0000000..8a38970
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/030-resources-view.sql
@@ -0,0 +1,169 @@
+/*
+ * Tests for emp.resources_view
+ */
+BEGIN;
+
+	/*
+	 * We will use a fake emp.planet_resources_view to avoid having to set
+	 * planet resources.
+	 * 
+	 * In terms of data, we need two resources (one with a category, the other
+	 * without, and two empires (one with "planet resources", the other
+	 * without). Both empires will have mining settings for one of the resource
+	 * types.
+	 */
+
+	\i utils/strings.sql
+	SELECT _create_test_strings( 2 , 'resource' , 'Resource name ' );
+	SELECT _create_test_strings( 2 , 'rDesc' , 'Resource description ' );
+	SELECT _create_test_strings( 1 , 'rCat' , 'Resource category ' );
+	
+	INSERT INTO defs.resources (
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_get_string( 'resource1' ) , _get_string( 'rDesc1' ) ,
+			_get_string( 'rCat1' ) , 2
+		) , (
+			_get_string( 'resource2' ) , _get_string( 'rDesc2' ) ,
+			NULL , 1
+		);
+
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	SELECT _create_emp_names( 2 , 'emp' );
+	INSERT INTO emp.empires( name_id , cash )
+		SELECT id , 0 FROM naming.empire_names; 
+
+	ALTER TABLE emp.mining_settings DROP CONSTRAINT fk_empmset_resource;
+	INSERT INTO emp.mining_settings ( empire_id , resource_name_id , empmset_weight )
+		SELECT id , _get_string( 'resource1' ) , row_number( ) OVER ()
+			FROM naming.empire_names
+			ORDER BY id;
+
+	INSERT INTO emp.resources ( empire_id , resource_name_id , empres_possessed )
+		SELECT name_id , resource_name_id , 0.4 * row_number( ) OVER ()
+			FROM emp.empires
+				CROSS JOIN defs.resources
+			ORDER BY name_id , resource_name_id;
+
+
+	CREATE TABLE fake_planet_resources_view (
+		empire_id			INT ,
+		resource_name_id	INT ,
+		planets_income		BIGINT ,
+		planets_upkeep		BIGINT
+	);
+	
+	CREATE OR REPLACE VIEW emp.planet_resources_view
+		AS SELECT * FROM fake_planet_resources_view;
+
+	INSERT INTO fake_planet_resources_view
+		VALUES (
+			_get_emp_name( 'emp1' ) , _get_string( 'resource1' ) , 1 , 2
+		) , (
+			_get_emp_name( 'emp1' ) , _get_string( 'resource2' ) , 3 , 4
+		);
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 13 );
+	
+	SELECT diag_test_name( 'emp.resources_view - One row per empire/resource combination' );
+	SELECT is( COUNT(*)::INT , 4 ) FROM emp.resources_view;
+	
+	SELECT diag_test_name( 'emp.resources_view - Resource ordering' );
+	SELECT set_eq(
+		$$ SELECT resource_identifier , row_number( ) OVER ( )
+			FROM emp.resources_view 
+			WHERE empire_id = _get_emp_name( 'emp1' ) $$ ,
+		$$ VALUES ( 'resource2' , 1 ) , ( 'resource1' , 2 ) $$
+	);
+	
+	SELECT diag_test_name( 'emp.resources_view - Name translation' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.resources_view
+			WHERE resource_name NOT LIKE 'Resource name %'
+	$$ );
+	
+	SELECT diag_test_name( 'emp.resources_view - Description translation' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.resources_view
+			WHERE resource_description NOT LIKE 'Resource description %'
+	$$ );
+	
+	SELECT diag_test_name( 'emp.resources_view - Category translation' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.resources_view
+			WHERE resource_identifier = 'resource1'
+				AND resource_category NOT LIKE 'Resource category %'
+	$$ );
+	
+	SELECT diag_test_name( 'emp.resources_view - NULL category -> NULL translation' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.resources_view
+			WHERE resource_identifier <> 'resource1'
+				AND resource_category IS NOT NULL
+	$$ );
+	
+	SELECT diag_test_name( 'emp.resources_view - Possessed quantities are rounded down' );
+	SELECT set_eq(
+		$$ SELECT empire_id , resource_identifier , empres_possessed
+			FROM emp.resources_view $$ ,
+		$$ VALUES (
+				_get_emp_name( 'emp1' ) , 'resource1' , 0
+			) , (
+				_get_emp_name( 'emp1' ) , 'resource2' , 0
+			) , (
+				_get_emp_name( 'emp2' ) , 'resource1' , 1
+			) , (
+				_get_emp_name( 'emp2' ) , 'resource2' , 1
+			) $$
+	);
+	
+	SELECT diag_test_name( 'emp.resources_view - Basic resources have NULL mining settings' );
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM emp.resources_view
+		WHERE resource_identifier = 'resource2'
+			AND empmset_weight IS NULL;
+	
+	SELECT diag_test_name( 'emp.resources_view - Mining settings for natural resources' );
+	SELECT set_eq(
+		$$ SELECT empire_id , empmset_weight
+			FROM emp.resources_view
+			WHERE resource_identifier = 'resource1' $$ ,
+		$$ VALUES ( _get_emp_name( 'emp1' ) , 1 ) ,
+			( _get_emp_name( 'emp2' ) , 2 ) $$
+	);
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM emp.resources_view
+		WHERE resource_identifier = 'resource1'
+			AND empmset_weight IS NOT NULL;
+	
+	SELECT diag_test_name( 'emp.resources_view - Planet upkeep/income is zero when there are no planets' );
+	SELECT is( COUNT(*)::INT , 2 )
+		FROM emp.resources_view
+		WHERE empire_id = _get_emp_name( 'emp2' )
+			AND planets_upkeep = 0
+			AND planets_income = 0;
+	
+	SELECT diag_test_name( 'emp.resources_view - Planet upkeep/income from planet resources view' );
+	SELECT set_eq(
+		$$ SELECT resource_identifier , planets_upkeep , planets_income
+			FROM emp.resources_view
+			WHERE empire_id = _get_emp_name( 'emp1' ) $$ ,
+		$$ VALUES ( 'resource1' , 2 , 1 ) , ( 'resource2' , 4 , 3 ) $$
+	);
+	
+	SELECT diag_test_name( 'emp.resources_view - FIXME - Fleets upkeep set to zero' );
+	SELECT is_empty(
+		$$ SELECT * FROM emp.resources_view
+			WHERE fleets_upkeep <> 0 $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/040-get-planet-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/040-get-planet-resources.sql
new file mode 100644
index 0000000..39a081b
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/040-get-planet-resources.sql
@@ -0,0 +1,187 @@
+/*
+ * Tests for the emp.get_planet_resources() function
+ */
+BEGIN;
+	/*
+	 * We need two planets, one being owned by some empire, the other being
+	 * neutral. Both planets' resource records must exist. Both planets will
+	 * also include resource providers which will serve as tests for the
+	 * various rounding which takes place.
+	 * 
+	 * To avoid having to define actual natural resources, we disable the
+	 * foreign keys on resource providers and mining settings. We can't do
+	 * that for the empire, tho: we need an actual account as the translations
+	 * must be looked up.
+	 */
+
+	\i utils/strings.sql
+	SELECT _create_test_strings( 3 , 'resource' , 'Resource name ' );
+	SELECT _create_test_strings( 3 , 'rDesc' , 'Resource description ' );
+	SELECT _create_test_strings( 1 , 'rCat' , 'Resource category ' );
+	
+	INSERT INTO defs.resources (
+			resource_name_id , resource_description_id ,
+			resource_category_id , resource_weight
+		) VALUES (
+			_get_string( 'resource1' ) , _get_string( 'rDesc1' ) ,
+			_get_string( 'rCat1' ) , 2
+		) , (
+			_get_string( 'resource2' ) , _get_string( 'rDesc2' ) ,
+			NULL , 1
+		) , (
+			_get_string( 'resource3' ) , _get_string( 'rDesc3' ) ,
+			NULL , 3
+		);
+
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	SELECT _create_emp_names( 1 , 'emp' );
+	INSERT INTO emp.empires( name_id , cash )
+		VALUES( _get_emp_name( 'emp1' ) , 0 );
+
+	ALTER TABLE emp.mining_settings DROP CONSTRAINT fk_empmset_resource;
+	INSERT INTO emp.mining_settings ( empire_id , resource_name_id )
+		SELECT _get_emp_name( 'emp1' ) , resource_name_id
+			FROM defs.resources;
+
+	\i utils/universe.sql
+	SELECT _create_raw_planets( 2 , 'planet' );
+	
+	INSERT INTO verse.planet_resources (
+			planet_id , resource_name_id , pres_income , pres_upkeep
+		) VALUES (
+			_get_map_name( 'planet1' ) , _get_string( 'resource1' ) ,
+			99.4 / 720.0 , 99.4 / 720.0
+		) , (
+			_get_map_name( 'planet1' ) , _get_string( 'resource2' ) ,
+			99.5 / 720.0 , 99.5 / 720.0
+		) , (
+			_get_map_name( 'planet1' ) , _get_string( 'resource3' ) ,
+			99.6 / 720.0 , 99.6 / 720.0
+		);
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+		SELECT _get_map_name( 'planet2' ) , resource_name_id
+			FROM defs.resources;
+
+	ALTER TABLE verse.resource_providers DROP CONSTRAINT fk_resprov_resource;
+	INSERT INTO verse.resource_providers(
+			planet_id , resource_name_id , resprov_quantity_max ,
+			resprov_quantity , resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'planet1' ) , _get_string( 'resource1' ) , 99.4 ,
+			99.4 , 0.494 , 0.5
+		) ,  (
+			_get_map_name( 'planet1' ) , _get_string( 'resource2' ) , 99.5 ,
+			99.5 , 0.495 , 0.5
+		) ,  (
+			_get_map_name( 'planet2' ) , _get_string( 'resource1' ) , 100 ,
+			100 , 0.5 , 0.5
+		);
+
+	INSERT INTO emp.planets ( empire_id , planet_id )
+		VALUES ( _get_emp_name( 'emp1' ) , _get_map_name( 'planet1' ) );
+
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 13 );
+
+	SELECT diag_test_name( 'emp.get_planet_resources() - No results on missing planets' );
+	SELECT is_empty( $$ SELECT * FROM emp.get_planet_resources( _get_bad_map_name( ) ) $$ );
+
+	SELECT diag_test_name( 'emp.get_planet_resources() - No results on neutral planets' );
+	SELECT is_empty( $$ SELECT * FROM emp.get_planet_resources( _get_map_name( 'planet2' ) ) $$ );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - One row per resource type' );
+	SELECT is( COUNT(*)::INT , 3 ) FROM emp.get_planet_resources( _get_map_name( 'planet1' ) );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - Row ordering' );
+	SELECT set_eq(
+		$$ SELECT resource_identifier , row_number() OVER ( )
+			FROM emp.get_planet_resources( _get_map_name( 'planet1' ) ) $$ ,
+		$$ VALUES (
+			'resource1' , 2
+		) , (
+			'resource2' , 1 
+		) , (
+			'resource3' , 3 
+		) $$
+	);
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - Name translation' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE resource_name NOT LIKE 'Resource name %'
+	$$ );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - Description translation' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE resource_description NOT LIKE 'Resource description %'
+	$$ );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - Category translation' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE resource_identifier = 'resource1'
+				AND resource_category NOT LIKE 'Resource category %'
+	$$ );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - NULL category -> NULL translation' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE resource_identifier <> 'resource1'
+				AND resource_category IS NOT NULL
+	$$ );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - Upkeep is valid and rounded up' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE pres_upkeep IS NULL OR pres_upkeep <> 100
+	$$ );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - Income is valid and rounded down' );
+	SELECT is_empty( $$
+		SELECT * FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE pres_income IS NULL OR pres_income <> 99
+	$$ );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - No mining-related fields when there is no resource provider' );
+	SELECT is_empty( $$
+		SELECT * FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE resource_identifier = 'resource3' AND NOT (
+					resprov_capacity IS NULL
+					AND resprov_quantity IS NULL 
+					AND resprov_difficulty IS NULL
+					AND mset_weight IS NULL
+				);
+	$$ );
+	
+	SELECT diag_test_name( 'emp.get_planet_resources() - Resource provider fields are present' );
+	SELECT is_empty( $$
+		SELECT * FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE resource_identifier <> 'resource3' AND (
+					resprov_capacity IS NULL
+					OR resprov_quantity IS NULL 
+					OR resprov_difficulty IS NULL
+					OR mset_weight IS NULL
+				);
+	$$ );
+
+	SELECT diag_test_name( 'emp.get_planet_resources() - Resource provider values' );
+	SELECT set_eq( $$
+		SELECT resource_identifier , resprov_capacity , resprov_quantity , resprov_difficulty
+			FROM emp.get_planet_resources( _get_map_name( 'planet1' ) )
+			WHERE resource_identifier <> 'resource3'
+	$$ , $$ VALUES (
+		'resource1' , 99 , 99 , 49
+	) , (
+		'resource2' , 100 , 100 , 50 
+	) $$ );
+
+	SELECT *  FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/050-resource-category-weight-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/050-resource-category-weight-view.sql
new file mode 100644
index 0000000..17df905
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/050-resource-category-weight-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on defs.resource_category_weight_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'defs.resource_category_weight_view - Privileges' );
+	SELECT throws_ok( 'SELECT * FROM defs.resource_category_weight_view' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/060-ordered-resources-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/060-ordered-resources-view.sql
new file mode 100644
index 0000000..a632f1f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/060-ordered-resources-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on defs.ordered_resources_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'defs.ordered_resources_view - Privileges' );
+	SELECT throws_ok( 'SELECT * FROM defs.ordered_resources_view' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/020-planet-resources-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/020-planet-resources-view.sql
new file mode 100644
index 0000000..619ad96
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/020-planet-resources-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on emp.planet_resources_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.planet_resources_view - Privileges' );
+	SELECT throws_ok( 'SELECT * FROM emp.planet_resources_view' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/030-resources-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/030-resources-view.sql
new file mode 100644
index 0000000..817d0e1
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/030-resources-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on emp.resources_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.resources_view - Privileges' );
+	SELECT lives_ok( 'SELECT * FROM emp.resources_view' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/040-get-planet-resources.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/040-get-planet-resources.sql
new file mode 100644
index 0000000..e2ed935
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/040-get-planet-resources.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on emp.get_planet_resources()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.get_planet_resources() - Privileges' );
+	SELECT lives_ok( $$
+		SELECT emp.get_planet_resources( 1 )
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/strings.sql b/legacyworlds-server-data/db-structure/tests/utils/strings.sql
index 33ead96..3303ae3 100644
--- a/legacyworlds-server-data/db-structure/tests/utils/strings.sql
+++ b/legacyworlds-server-data/db-structure/tests/utils/strings.sql
@@ -29,24 +29,11 @@ CREATE FUNCTION _get_string( TEXT ) RETURNS INT AS $$
 $$ LANGUAGE SQL;
 
 
-/*
- * Function that creates some quantity of test strings
- */
-CREATE FUNCTION _create_test_strings( _quantity INT )
-		RETURNS VOID
-	AS $$
-DECLARE
-	i	INT;
-BEGIN
-	PERFORM _create_test_strings( _quantity , 'test' );
-END;
-$$ LANGUAGE PLPGSQL;
-
-
 /*
  * Function that creates some quantity of test strings using a specific prefix
+ * and translation prefix.
  */
-CREATE FUNCTION _create_test_strings( _quantity INT , _prefix TEXT )
+CREATE FUNCTION _create_test_strings( _quantity INT , _prefix TEXT , _trans TEXT )
 		RETURNS VOID
 	AS $$
 DECLARE
@@ -59,7 +46,27 @@ BEGIN
 	LOOP
 		i := i + 1;
 		PERFORM defs.uoc_translation( 't' , _prefix || i::TEXT ,
-			'Test string #' || i::TEXT );
+			_trans || i::TEXT );
 	END LOOP;
 END;
 $$ LANGUAGE PLPGSQL;
+
+/*
+ * Function that creates some quantity of test strings using a specific prefix
+ */
+CREATE FUNCTION _create_test_strings( _quantity INT , _prefix TEXT )
+	RETURNS VOID
+AS $$
+	SELECT _create_test_strings( $1 , $2 , 'Test string #' );
+$$ LANGUAGE SQL;
+
+
+/*
+ * Function that creates some quantity of test strings
+ */
+CREATE FUNCTION _create_test_strings( _quantity INT )
+	RETURNS VOID
+AS $$
+	SELECT _create_test_strings( $1 , 'test' );
+$$ LANGUAGE SQL;
+
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesInformationDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesInformationDAO.java
new file mode 100644
index 0000000..5c88115
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesInformationDAO.java
@@ -0,0 +1,43 @@
+package com.deepclone.lw.interfaces.game.resources;
+
+
+import java.util.List;
+
+import com.deepclone.lw.cmd.player.gdata.empire.EmpireResourceRecord;
+import com.deepclone.lw.cmd.player.gdata.planets.PlanetResourceRecord;
+
+
+
+/**
+ * Resources information access component interface
+ * 
+ * <p>
+ * This interface defines the methods which execute the queries required to extract information
+ * about resources from the database.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public interface ResourcesInformationDAO
+{
+
+	/**
+	 * Obtain resources information for a planet
+	 * 
+	 * @param planet
+	 *            the planet to obtain information about
+	 * 
+	 * @return the list of planetary resource records
+	 */
+	public List< PlanetResourceRecord > getPlanetInformation( int planet );
+
+
+	/**
+	 * Obtain resources information for an empire
+	 * 
+	 * @param empire
+	 *            the empire to obtain information about
+	 * 
+	 * @return the list of empire economic records
+	 */
+	public List< EmpireResourceRecord > getEmpireInformation( int empire );
+}
diff --git a/legacyworlds-server-main/src/main/resources/lw-server.xml b/legacyworlds-server-main/src/main/resources/lw-server.xml
index cf7c8ea..081b882 100644
--- a/legacyworlds-server-main/src/main/resources/lw-server.xml
+++ b/legacyworlds-server-main/src/main/resources/lw-server.xml
@@ -13,14 +13,13 @@
 	<!-- Load transaction manager -->
 	<import resource="configuration/transaction-bean.xml" />
 
-	<!-- Load all server beans -->
+	<!-- Load all system and low-level beans -->
 	<import resource="configuration/accounts-beans.xml" />
 	<import resource="configuration/bt-beans.xml" />
 	<import resource="configuration/eventlog-beans.xml" />
 	<import resource="configuration/i18n-beans.xml" />
 	<import resource="configuration/mailer-beans.xml" />
 	<import resource="configuration/naming-beans.xml" />
-	<import resource="configuration/simple-beans.xml" />
 	<import resource="configuration/system-beans.xml" />
 	<import resource="configuration/user-beans.xml" />
 
@@ -29,5 +28,4 @@
 	<import resource="configuration/game.xml" />
 	<import resource="configuration/simple-beans.xml" />
 
-
 </beans>
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestAbstractResourceMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestAbstractResourceMapper.java
new file mode 100644
index 0000000..4a0cd03
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestAbstractResourceMapper.java
@@ -0,0 +1,126 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import static org.junit.Assert.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cmd.player.gdata.AbstractResourceRecord;
+import com.deepclone.lw.testing.MockResultSet;
+
+
+
+/**
+ * Tests of the {@link AbstractResourceMapper} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestAbstractResourceMapper
+{
+	/** Strings used for the various fields */
+	private static final String TEST_STRINGS[] = {
+			"1" , "2" , "3" , "4"
+	};
+
+	/**
+	 * An empty resource record used to test the {@link AbstractResourceMapper}
+	 * 
+	 * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+	 */
+	@SuppressWarnings( "serial" )
+	private static class EmptyResourceRecord
+			extends AbstractResourceRecord
+	{
+		// EMPTY
+	}
+
+	/**
+	 * Resource row mapper that calls
+	 * {@link AbstractResourceMapper#getResourceDescription(AbstractResourceRecord, ResultSet)}
+	 * 
+	 * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+	 * 
+	 */
+	private static class EmptyResourceMapper
+			extends AbstractResourceMapper< EmptyResourceRecord >
+	{
+
+		@Override
+		public EmptyResourceRecord mapRow( ResultSet rs , int rowNum )
+				throws SQLException
+		{
+			EmptyResourceRecord record = new EmptyResourceRecord( );
+			this.getResourceDescription( record , rs );
+			return record;
+		}
+
+	}
+
+	/** The result set fed to the resource row mapper */
+	private ResultSet resultSet;
+
+	/** The mapper used in the tests */
+	private EmptyResourceMapper mapper;
+
+
+	/**
+	 * Create the test mapper and a fake result set with two results: one that includes a category,
+	 * and another without category.
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.mapper = new EmptyResourceMapper( );
+
+		@SuppressWarnings( "unchecked" )
+		HashMap< String , Object > rows[] = new HashMap[ 2 ];
+		for ( int i = 0 ; i < 2 ; i++ ) {
+			HashMap< String , Object > row = new HashMap< String , Object >( );
+			row.put( "resource_identifier" , TEST_STRINGS[ 0 ] );
+			row.put( "resource_name" , TEST_STRINGS[ 1 ] );
+			row.put( "resource_description" , TEST_STRINGS[ 2 ] );
+			if ( i == 1 ) {
+				row.put( "resource_category" , TEST_STRINGS[ 3 ] );
+			}
+			rows[ i ] = row;
+		}
+
+		this.resultSet = MockResultSet.create( rows );
+	}
+
+
+	/** Test mapping a row with a NULL category */
+	@Test
+	public void testMapRowWithoutCategory( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 1 );
+		EmptyResourceRecord record = this.mapper.mapRow( this.resultSet , 1 );
+
+		assertEquals( TEST_STRINGS[ 0 ] , record.getIdentifier( ) );
+		assertEquals( TEST_STRINGS[ 1 ] , record.getTitle( ) );
+		assertEquals( TEST_STRINGS[ 2 ] , record.getDescription( ) );
+		assertNull( record.getCategory( ) );
+	}
+
+
+	/** Test mapping a row with a non-NULL category */
+	@Test
+	public void testMapRowWithCategory( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 2 );
+		EmptyResourceRecord record = this.mapper.mapRow( this.resultSet , 1 );
+
+		assertEquals( TEST_STRINGS[ 0 ] , record.getIdentifier( ) );
+		assertEquals( TEST_STRINGS[ 1 ] , record.getTitle( ) );
+		assertEquals( TEST_STRINGS[ 2 ] , record.getDescription( ) );
+		assertEquals( TEST_STRINGS[ 3 ] , record.getCategory( ) );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestEmpireResourceMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestEmpireResourceMapper.java
new file mode 100644
index 0000000..f9966ab
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestEmpireResourceMapper.java
@@ -0,0 +1,104 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import static org.junit.Assert.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cmd.player.gdata.empire.EmpireResourceRecord;
+import com.deepclone.lw.testing.MockResultSet;
+
+
+
+/**
+ * Tests of the {@link EmpireResourceMapper} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestEmpireResourceMapper
+{
+	private static final String TEST_STRING = "Test";
+
+	private static final long POSSESSED_VALUE = 2L;
+
+	private static final long P_INCOME_VALUE = 3L;
+
+	private static final long P_UPKEEP_VALUE = 4L;
+
+	private static final long F_UPKEEP_VALUE = 5L;
+
+	private static final Integer PRIORITY_VALUE = 6;
+
+	/** The fake result set fed to the mapper */
+	private ResultSet resultSet;
+
+	/** The mapper being tested */
+	private EmpireResourceMapper mapper;
+
+
+	@Before
+	public void setUp( )
+	{
+		this.mapper = new EmpireResourceMapper( );
+
+		@SuppressWarnings( "unchecked" )
+		HashMap< String , Object > rows[] = new HashMap[ 2 ];
+		for ( int i = 0 ; i < 2 ; i++ ) {
+			HashMap< String , Object > row = new HashMap< String , Object >( );
+
+			row.put( "resource_identifier" , TEST_STRING );
+
+			row.put( "empres_possessed" , POSSESSED_VALUE + i );
+			row.put( "planets_income" , P_INCOME_VALUE + i );
+			row.put( "planets_upkeep" , P_UPKEEP_VALUE + i );
+			row.put( "fleets_upkeep" , F_UPKEEP_VALUE + i );
+			if ( i == 1 ) {
+				row.put( "empmset_weight" , PRIORITY_VALUE + i );
+			}
+
+			rows[ i ] = row;
+		}
+		this.resultSet = MockResultSet.create( rows );
+	}
+
+
+	/** Test mapping a row that does not include a mining priority */
+	@Test
+	public void testMapWithoutPriority( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 1 );
+		EmpireResourceRecord record = this.mapper.mapRow( this.resultSet , 1 );
+
+		assertEquals( TEST_STRING , record.getIdentifier( ) );
+		assertEquals( POSSESSED_VALUE , record.getStockpiled( ) );
+		assertEquals( P_INCOME_VALUE , record.getIncome( ) );
+		assertEquals( P_UPKEEP_VALUE , record.getPlanetUpkeep( ) );
+		assertEquals( F_UPKEEP_VALUE , record.getFleetUpkeep( ) );
+		assertNull( record.getMiningPriority( ) );
+	}
+
+
+	/** Test mapping a row that includes a mining priority */
+	@Test
+	public void testMapWithPriority( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 2 );
+		EmpireResourceRecord record = this.mapper.mapRow( this.resultSet , 1 );
+
+		assertEquals( TEST_STRING , record.getIdentifier( ) );
+		assertEquals( 1 + POSSESSED_VALUE , record.getStockpiled( ) );
+		assertEquals( 1 + P_INCOME_VALUE , record.getIncome( ) );
+		assertEquals( 1 + P_UPKEEP_VALUE , record.getPlanetUpkeep( ) );
+		assertEquals( 1 + F_UPKEEP_VALUE , record.getFleetUpkeep( ) );
+		assertNotNull( record.getMiningPriority( ) );
+		assertEquals( 1 + PRIORITY_VALUE , (int) record.getMiningPriority( ) );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestPlanetResourceMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestPlanetResourceMapper.java
new file mode 100644
index 0000000..5f2990f
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestPlanetResourceMapper.java
@@ -0,0 +1,131 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cmd.player.gdata.planets.PlanetResourceRecord;
+import com.deepclone.lw.cmd.player.gdata.planets.ResourceProviderRecord;
+import com.deepclone.lw.testing.MockResultSet;
+
+
+
+/**
+ * Tests for {@link PlanetResourceMapper}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestPlanetResourceMapper
+{
+
+	/** Value used as the resource identifier */
+	private static final Object RID_VALUE = "resource id";
+
+	/** Value used as the income */
+	private static final long P_INCOME_VALUE = 1;
+
+	/** Value used as the upkeep */
+	private static final long P_UPKEEP_VALUE = 2;
+
+	/** Value used as the investment */
+	private static final long P_INVEST_VALUE = 3;
+
+	/** Value used as the resource provider's capacity */
+	private static final long RP_CAPACITY_VALUE = 5L;
+
+	/** Value used as the resource provider's current quantity */
+	private static final long RP_QUANTITY_VALUE = 6L;
+
+	/** Value used as the resource provider's difficulty */
+	private static final int RP_DIFF_VALUE = 7;
+
+	/** Value used as the mining setting */
+	private static final int MS_WEIGHT_VALUE = 8;
+
+	/** The mapper under test */
+	private PlanetResourceMapper mapper;
+
+	/** The fake result set fed to the mapper */
+	private ResultSet resultSet;
+
+
+	/**
+	 * Create the mapper and a result set to test it with.
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.mapper = new PlanetResourceMapper( );
+
+		@SuppressWarnings( "unchecked" )
+		HashMap< String , Object > rows[] = new HashMap[ 2 ];
+		for ( int i = 0 ; i < 2 ; i++ ) {
+			HashMap< String , Object > row = new HashMap< String , Object >( );
+
+			row.put( "resource_identifier" , RID_VALUE );
+
+			row.put( "pres_income" , P_INCOME_VALUE + i );
+			row.put( "pres_upkeep" , P_UPKEEP_VALUE + i );
+			row.put( "pres_invested" , P_INVEST_VALUE + i );
+			if ( i == 1 ) {
+				row.put( "resprov_capacity" , RP_CAPACITY_VALUE );
+				row.put( "resprov_quantity" , RP_QUANTITY_VALUE );
+				row.put( "resprov_difficulty" , RP_DIFF_VALUE );
+				row.put( "mset_weight" , MS_WEIGHT_VALUE );
+			}
+
+			rows[ i ] = row;
+		}
+		this.resultSet = MockResultSet.create( rows );
+	}
+
+
+	/**
+	 * Planet resource row with no resource provider
+	 */
+	@Test
+	public void testRowWithoutResourceProvider( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 1 );
+		PlanetResourceRecord row = this.mapper.mapRow( this.resultSet , 1 );
+
+		assertEquals( RID_VALUE , row.getIdentifier( ) );
+
+		assertEquals( P_INCOME_VALUE , row.getIncome( ) );
+		assertEquals( P_UPKEEP_VALUE , row.getUpkeep( ) );
+		assertEquals( P_INVEST_VALUE , row.getInvested( ) );
+
+		assertNull( row.getResourceProvider( ) );
+	}
+
+
+	/**
+	 * Planet resource row with a resource provider
+	 */
+	@Test
+	public void testRowWithResourceProvider( )
+			throws SQLException
+	{
+		this.resultSet.absolute( 2 );
+		PlanetResourceRecord row = this.mapper.mapRow( this.resultSet , 2 );
+
+		assertEquals( RID_VALUE , row.getIdentifier( ) );
+
+		assertEquals( P_INCOME_VALUE + 1 , row.getIncome( ) );
+		assertEquals( P_UPKEEP_VALUE + 1 , row.getUpkeep( ) );
+		assertEquals( P_INVEST_VALUE + 1 , row.getInvested( ) );
+
+		ResourceProviderRecord rp = row.getResourceProvider( );
+		assertNotNull( rp );
+		assertEquals( RP_CAPACITY_VALUE , rp.getCapacity( ) );
+		assertEquals( RP_QUANTITY_VALUE , rp.getQuantity( ) );
+		assertEquals( RP_DIFF_VALUE , rp.getDifficulty( ) );
+		assertEquals( MS_WEIGHT_VALUE , rp.getPriority( ) );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockResultSet.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockResultSet.java
index da5bd52..fc92bed 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockResultSet.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockResultSet.java
@@ -225,6 +225,28 @@ public class MockResultSet
 	}
 
 
+	/**
+	 * Return the object in some column of the current "row" as a long integer
+	 * 
+	 * @param columnName
+	 *            the column's name
+	 * 
+	 * @return the object from the fake results, as a long integer
+	 * 
+	 * @throws SQLException
+	 *             if no row is selected
+	 */
+	public long getLong( String columnName )
+			throws SQLException
+	{
+		Object object = this.getObject( columnName );
+		if ( object != null ) {
+			return (Long) object;
+		}
+		return 0;
+	}
+
+
 	/**
 	 * Return the object in some column of the current "row" as a double
 	 * 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/AbstractResourceRecord.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/AbstractResourceRecord.java
new file mode 100644
index 0000000..605a4ac
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/AbstractResourceRecord.java
@@ -0,0 +1,109 @@
+package com.deepclone.lw.cmd.player.gdata;
+
+
+import java.io.Serializable;
+
+
+
+public abstract class AbstractResourceRecord
+		implements Serializable
+{
+
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** The text identifier of the resource */
+	private String identifier;
+
+	/** The internationalised name of the resource's category */
+	private String category;
+
+	/** The internationalised name of the resource */
+	private String title;
+
+	/** The internationalised description of the resource */
+	private String description;
+
+
+	/** @return the text identifying the resource */
+	public String getIdentifier( )
+	{
+		return this.identifier;
+	}
+
+
+	/**
+	 * Set the text identifying the resource
+	 * 
+	 * @param identifier
+	 *            the text identifying the resource
+	 */
+	public void setIdentifier( String identifier )
+	{
+		this.identifier = identifier;
+	}
+
+
+	/** @return the internationalised title of the resource */
+	public String getTitle( )
+	{
+		return this.title;
+	}
+
+
+	/**
+	 * Set the internationalised title of the resource
+	 * 
+	 * @param title
+	 *            the internationalised title of the resource
+	 */
+	public void setTitle( String title )
+	{
+		this.title = title;
+	}
+
+
+	/** @return the internationalised name of the category of the resource */
+	public String getCategory( )
+	{
+		return this.category;
+	}
+
+
+	/**
+	 * Set the internationalised name of the category of the resource
+	 * 
+	 * @param category
+	 *            the internationalised name of the category of the resource
+	 */
+	public void setCategory( String category )
+	{
+		this.category = category;
+	}
+
+
+	/** @return the internationalised description of the resource */
+	public String getDescription( )
+	{
+		return this.description;
+	}
+
+
+	/**
+	 * Set the internationalised description of the resource
+	 * 
+	 * @param description
+	 *            the internationalised description of the resource
+	 */
+	public void setDescription( String description )
+	{
+		this.description = description;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/EmpireResourceRecord.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/EmpireResourceRecord.java
new file mode 100644
index 0000000..9a9bd64
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/EmpireResourceRecord.java
@@ -0,0 +1,152 @@
+package com.deepclone.lw.cmd.player.gdata.empire;
+
+
+import com.deepclone.lw.cmd.player.gdata.AbstractResourceRecord;
+
+
+
+/**
+ * Resource economic record
+ * 
+ * <p>
+ * This record describes a resource. It is used in the empire overview's economic information and
+ * for mining settings.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class EmpireResourceRecord
+		extends AbstractResourceRecord
+{
+
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** The income for this type of resource over 12h RT / 1 month GT */
+	private long income;
+
+	/** The planet upkeep for this type of resource over 12h RT / 1 month GT */
+	private long planetUpkeep;
+
+	/** The fleet upkeep for this type of resource over 12h RT / 1 month GT */
+	private long fleetUpkeep;
+
+	/** The amount of resources of this type possessed by the empire */
+	private long stockpiled;
+
+	/** The resource's mining priority, or <code>null</code> for basic resources */
+	private Integer miningPriority;
+
+
+	/** @return the income for this type of resource over a period of 12h RT / 1 month GT */
+	public long getIncome( )
+	{
+		return this.income;
+	}
+
+
+	/**
+	 * Set the income for this type of resource over a period of 12h RT / 1 month GT
+	 * 
+	 * @param income
+	 *            the income for this type of resource over a period of 12h RT / 1 month GT
+	 */
+	public void setIncome( long income )
+	{
+		this.income = income;
+	}
+
+
+	/** @return the upkeep for this type of resource over a period of 12h RT / 1 month GT on planets */
+	public long getPlanetUpkeep( )
+	{
+		return this.planetUpkeep;
+	}
+
+
+	/**
+	 * Set the upkeep for this type of resource over a period of 12h RT / 1 month GT on planets
+	 * 
+	 * @param planetUpkeep
+	 *            the upkeep for this type of resource over a period of 12h RT / 1 month GT on
+	 *            planets
+	 */
+	public void setPlanetUpkeep( long planetUpkeep )
+	{
+		this.planetUpkeep = planetUpkeep;
+	}
+
+
+	/** @return the upkeep for this type of resource over a period of 12h RT / 1 month GT on planets */
+	public long getFleetUpkeep( )
+	{
+		return this.fleetUpkeep;
+	}
+
+
+	/**
+	 * Set the fleet upkeep for this type of resource over a period of 12h RT / 1 month GT
+	 * 
+	 * @param fleetUpkeep
+	 *            the fleet upkeep for this type of resource over a period of 12h RT / 1 month GT
+	 */
+	public void setFleetUpkeep( long fleetUpkeep )
+	{
+		this.fleetUpkeep = fleetUpkeep;
+	}
+
+
+	/** @return the total upkeep (including both fleet and planet upkeep) */
+	public long getUpkeep( )
+	{
+		return this.fleetUpkeep + this.planetUpkeep;
+	}
+
+
+	/** @return the amount of resources of this type the empire possesses */
+	public long getStockpiled( )
+	{
+		return this.stockpiled;
+	}
+
+
+	/**
+	 * Set the amount of resources of this type the empire possesses
+	 * 
+	 * @param stockpiled
+	 *            the amount of resources of this type the empire possesses
+	 */
+	public void setStockpiled( long stockpiled )
+	{
+		this.stockpiled = stockpiled;
+	}
+
+
+	/**
+	 * @return the mining priority for this type of resource, or <code>null</code> if it is a basic
+	 *         resource
+	 */
+	public Integer getMiningPriority( )
+	{
+		return this.miningPriority;
+	}
+
+
+	/**
+	 * Set the mining priority for this type of resource
+	 * 
+	 * @param miningPriority
+	 *            the mining priority for this type of resource
+	 */
+	public void setMiningPriority( int miningPriority )
+	{
+		this.miningPriority = miningPriority;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/OverviewData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/OverviewData.java
index d82cc66..9858969 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/OverviewData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/OverviewData.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.gdata.empire;
 
 
 import java.io.Serializable;
+import java.util.List;
 
 
 
@@ -9,124 +10,234 @@ public class OverviewData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	/**
+	 * Serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M1 with ID 1
+	 * <li>Modified in B6M2, ID set to 2
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 2L;
 
+	/** Quantity of planets owned by the empire */
 	private long planets;
+
+	/** Amount of unread messages */
 	private int newMessages;
+
+	/** Total population in the empire */
 	private long population;
+
+	/** Average happiness of the population on the planets, as a percentage */
 	private int avgHappiness;
+
+	/** Total fleet power */
 	private long fleetPower;
+
+	/** Total monetary income from planets */
 	private long planetIncome;
+
+	/** Total monetary upkeep for planets */
 	private long planetUpkeep;
+
+	/** Total monetary fleet upkeep */
 	private long fleetUpkeep;
+
+	/** Total amount of money in the various queues */
 	private long investment;
 
+	/** Economic information */
+	private List< EmpireResourceRecord > economy;
 
+
+	/** @return the quantity of planets owned by the empire */
 	public long getPlanets( )
 	{
-		return planets;
+		return this.planets;
 	}
 
 
+	/**
+	 * Set the quantity of planets owned by the empire
+	 * 
+	 * @param planets
+	 *            the quantity of planets owned by the empire
+	 */
 	public void setPlanets( long planets )
 	{
 		this.planets = planets;
 	}
 
 
+	/** @return the quantity of unread messages */
 	public int getNewMessages( )
 	{
-		return newMessages;
+		return this.newMessages;
 	}
 
 
+	/**
+	 * Set the quantity of unread messages
+	 * 
+	 * @param newMessages
+	 *            the quantity of unread messages
+	 */
 	public void setNewMessages( int newMessages )
 	{
 		this.newMessages = newMessages;
 	}
 
 
+	/** @return the empire's total population */
 	public long getPopulation( )
 	{
-		return population;
+		return this.population;
 	}
 
 
+	/**
+	 * Set the empire's total population
+	 * 
+	 * @param population
+	 *            the empire's total population
+	 */
 	public void setPopulation( long population )
 	{
 		this.population = population;
 	}
 
 
+	/** @return the average happiness on the empire's planets */
 	public int getAvgHappiness( )
 	{
-		return avgHappiness;
+		return this.avgHappiness;
 	}
 
 
+	/**
+	 * Set the average happiness on the empire's planets
+	 * 
+	 * @param avgHappiness
+	 *            the average happiness on the empire's planets
+	 */
 	public void setAvgHappiness( int avgHappiness )
 	{
 		this.avgHappiness = avgHappiness;
 	}
 
 
+	/** @return the total fleet power */
 	public long getFleetPower( )
 	{
-		return fleetPower;
+		return this.fleetPower;
 	}
 
 
+	/**
+	 * Set the total fleet power
+	 * 
+	 * @param fleetPower
+	 *            the total fleet power
+	 */
 	public void setFleetPower( long fleetPower )
 	{
 		this.fleetPower = fleetPower;
 	}
 
 
+	/** @return the monetary income from the empire's planets */
 	public long getPlanetIncome( )
 	{
-		return planetIncome;
+		return this.planetIncome;
 	}
 
 
+	/**
+	 * Set the monetary income from the empire's planets
+	 * 
+	 * @param planetIncome
+	 *            the monetary income from the empire's planets
+	 */
 	public void setPlanetIncome( long planetIncome )
 	{
 		this.planetIncome = planetIncome;
 	}
 
 
+	/** @return the monetary upkeep for the empire's planets */
 	public long getPlanetUpkeep( )
 	{
-		return planetUpkeep;
+		return this.planetUpkeep;
 	}
 
 
+	/**
+	 * Set the monetary upkeep for the empire's planets
+	 * 
+	 * @param planetUpkeep
+	 *            the monetary upkeep for the empire's planets
+	 */
 	public void setPlanetUpkeep( long planetUpkeep )
 	{
 		this.planetUpkeep = planetUpkeep;
 	}
 
 
+	/** @return the monetary upkeep for the empire's fleets */
 	public long getFleetUpkeep( )
 	{
-		return fleetUpkeep;
+		return this.fleetUpkeep;
 	}
 
 
+	/**
+	 * Set the monetary upkeep for the empire's fleets
+	 * 
+	 * @param fleetUpkeep
+	 *            the monetary upkeep for the empire's fleets
+	 */
 	public void setFleetUpkeep( long fleetUpkeep )
 	{
 		this.fleetUpkeep = fleetUpkeep;
 	}
 
 
+	/** @return the total amount of money invested in civilian or military queues */
 	public long getInvestment( )
 	{
-		return investment;
+		return this.investment;
 	}
 
 
+	/**
+	 * Set the total amount of money invested in civilian or military queues
+	 * 
+	 * @param investment
+	 *            the total amount of money invested in civilian or military queues
+	 */
 	public void setInvestment( long investment )
 	{
 		this.investment = investment;
 	}
 
+
+	/** @return the list of economic information records */
+	public List< EmpireResourceRecord > getEconomy( )
+	{
+		return this.economy;
+	}
+
+
+	/**
+	 * Set the list of economic information records
+	 * 
+	 * @param economy
+	 *            the list of economic information records
+	 */
+	public void setEconomy( List< EmpireResourceRecord > economy )
+	{
+		this.economy = economy;
+	}
+
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOwnView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOwnView.java
index a567a8e..9dfc71f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOwnView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOwnView.java
@@ -6,128 +6,283 @@ import java.util.List;
 
 
 
+/**
+ * Planet view details available to planet owners
+ * 
+ * <p>
+ * This class carries the part of the planet view which is only available to empires owning the
+ * planet being viewed.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
 public class PlanetOwnView
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	/**
+	 * Serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M1 with ID 1
+	 * <li>Modified in B6M2, ID set to 2
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 2L;
 
+	/** Happiness of the planet's population as a percentage */
 	private int happiness;
+
+	/**
+	 * Happiness change indicator
+	 * 
+	 * <p>
+	 * This field's value is an integer between -2 and 2 (inclusive). It indicates the direction of
+	 * the happiness' change (positive meaning that the happiness is increasing, negative meaning it
+	 * is decreasing), and the strength of the change (absolute value 1 meaning a relatively small
+	 * change, 2 a bigger one)
+	 */
 	private int hChange;
+
+	/** Planet monetary income for 24h of RL time */
 	private long income;
+
+	/** Planet monetary upkeep for 24h of RL time */
 	private long upkeep;
+
+	/**
+	 * Various planet state indicators
+	 * 
+	 * <p>
+	 * This field carries a few flags about whether it is possible to rename or abandon the planet,
+	 * and similar information.
+	 */
 	private OwnPlanetStatusData status;
+
+	/** Civilian construction queue */
 	private QueueData civQueue;
+
+	/** Military construction queue */
 	private QueueData milQueue;
+
+	/** Descriptions of ships that can be built */
 	private List< BuildableShipData > bShips;
+
+	/** Descriptions of buildings that can be constructed */
 	private List< BuildableBuildingData > bBuildings;
 
+	/** List of the planet's resources */
+	private List< PlanetResourceRecord > resources;
 
+	/** Is the planet using specific mining settings? */
+	private boolean ownMiningSettings;
+
+
+	/** @return the planet's happiness */
 	public int getHappiness( )
 	{
-		return happiness;
+		return this.happiness;
 	}
 
 
+	/**
+	 * Set the planet's happiness
+	 * 
+	 * @param happiness
+	 *            the planet's happiness
+	 */
 	public void setHappiness( int happiness )
 	{
 		this.happiness = happiness;
 	}
 
 
+	/** @return the planet's happiness change indicator */
 	public int gethChange( )
 	{
-		return hChange;
+		return this.hChange;
 	}
 
 
+	/**
+	 * Set the planet's happiness change indicator
+	 * 
+	 * @param hChange
+	 *            the planet's happiness change indicator
+	 */
 	public void sethChange( int hChange )
 	{
 		this.hChange = hChange;
 	}
 
 
+	/** @return the planet's monetary income */
 	public long getIncome( )
 	{
-		return income;
+		return this.income;
 	}
 
 
+	/**
+	 * Set the planet's monetary income
+	 * 
+	 * @param income
+	 *            the planet's monetary income
+	 */
 	public void setIncome( long income )
 	{
 		this.income = income;
 	}
 
 
+	/** @return the planet's monetary upkeep */
 	public long getUpkeep( )
 	{
-		return upkeep;
+		return this.upkeep;
 	}
 
 
+	/**
+	 * Set the planet's monetary upkeep
+	 * 
+	 * @param upkeep
+	 *            the planet's monetary upkeep
+	 */
 	public void setUpkeep( long upkeep )
 	{
 		this.upkeep = upkeep;
 	}
 
 
+	/** @return the planet status object */
 	public OwnPlanetStatusData getStatus( )
 	{
-		return status;
+		return this.status;
 	}
 
 
+	/**
+	 * Set the planet status object
+	 * 
+	 * @param status
+	 *            the planet status object
+	 */
 	public void setStatus( OwnPlanetStatusData status )
 	{
 		this.status = status;
 	}
 
 
+	/** @return the civilian construction queue */
 	public QueueData getCivQueue( )
 	{
-		return civQueue;
+		return this.civQueue;
 	}
 
 
+	/**
+	 * Set the civilian construction queue
+	 * 
+	 * @param civQueue
+	 *            the civilian construction queue
+	 */
 	public void setCivQueue( QueueData civQueue )
 	{
 		this.civQueue = civQueue;
 	}
 
 
+	/** @return the military construction queue */
 	public QueueData getMilQueue( )
 	{
-		return milQueue;
+		return this.milQueue;
 	}
 
 
+	/**
+	 * Set the military construction queue
+	 * 
+	 * @param milQueue
+	 *            the military construction queue
+	 */
 	public void setMilQueue( QueueData milQueue )
 	{
 		this.milQueue = milQueue;
 	}
 
 
+	/** @return the descriptions of all ships which can be constructed */
 	public List< BuildableShipData > getbShips( )
 	{
-		return bShips;
+		return this.bShips;
 	}
 
 
+	/**
+	 * Set the descriptions of ships which can be constructed
+	 * 
+	 * @param bShips
+	 *            the descriptions of ships which can be constructed
+	 */
 	public void setbShips( List< BuildableShipData > bShips )
 	{
 		this.bShips = bShips;
 	}
 
 
+	/** @return the descriptions of all buildings which can be constructed */
 	public List< BuildableBuildingData > getbBuildings( )
 	{
-		return bBuildings;
+		return this.bBuildings;
 	}
 
 
+	/**
+	 * Set the descriptions of buildings which can be constructed
+	 * 
+	 * @param bBuildings
+	 *            the descriptions of buildings which can be constructed
+	 */
 	public void setbBuildings( List< BuildableBuildingData > bBuildings )
 	{
 		this.bBuildings = bBuildings;
 	}
 
+
+	/** @return the list of resources */
+	public List< PlanetResourceRecord > getResources( )
+	{
+		return this.resources;
+	}
+
+
+	/**
+	 * Set the list of resources
+	 * 
+	 * @param resourceProviders
+	 *            the list of resources
+	 */
+	public void setResources( List< PlanetResourceRecord > resources )
+	{
+		this.resources = resources;
+	}
+
+
+	/** @return whether the planet is using specific mining settings */
+	public boolean getOwnMiningSettings( )
+	{
+		return this.ownMiningSettings;
+	}
+
+
+	/**
+	 * Set whether the planet is using specific mining settings
+	 * 
+	 * @param ownMiningSettings
+	 *            whether the planet is using specific mining settings
+	 */
+	public void setOwnMiningSettings( boolean ownMiningSettings )
+	{
+		this.ownMiningSettings = ownMiningSettings;
+	}
+
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetResourceRecord.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetResourceRecord.java
new file mode 100644
index 0000000..704e422
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetResourceRecord.java
@@ -0,0 +1,125 @@
+package com.deepclone.lw.cmd.player.gdata.planets;
+
+
+import com.deepclone.lw.cmd.player.gdata.AbstractResourceRecord;
+
+
+
+/**
+ * Planet resources information
+ * 
+ * <p>
+ * This class carries information about a resource type, including all required strings, as well as
+ * income, upkeep and investment. It also contains information about the planet's resource provider
+ * if there is one.
+ * 
+ * <p>
+ * The class is used by {@link PlanetOwnView} to represent all of a planet's resources.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class PlanetResourceRecord
+		extends AbstractResourceRecord
+{
+
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** Income for 12h RT / 1 month GT */
+	private long income;
+
+	/** Upkeep for 12h RT / 1 month GT */
+	private long upkeep;
+
+	/** Quantity of that resource that will be used by the planet's queues */
+	private long invested;
+
+	/** Information about the planet's resource provider of that type, if there is one */
+	private ResourceProviderRecord resourceProvider;
+
+
+	/** @return the planet's income for that resource, over 12h RT / 1 month GT */
+	public long getIncome( )
+	{
+		return this.income;
+	}
+
+
+	/**
+	 * Set the planet's income for that resource
+	 * 
+	 * @param income
+	 *            the planet's income for that resource, over 12h RT / 1 month GT
+	 */
+	public void setIncome( long income )
+	{
+		this.income = income;
+	}
+
+
+	/** @return the planet's upkeep for that resource, over 12h RT / 1 month GT */
+	public long getUpkeep( )
+	{
+		return this.upkeep;
+	}
+
+
+	/**
+	 * Set the planet's upkeep for that resource
+	 * 
+	 * @param upkeep
+	 *            the planet's upkeep for that resource, over 12h RT / 1 month GT
+	 */
+	public void setUpkeep( long upkeep )
+	{
+		this.upkeep = upkeep;
+	}
+
+
+	/** @return the amount of that resource invested in the planet's build queues */
+	public long getInvested( )
+	{
+		return this.invested;
+	}
+
+
+	/**
+	 * Set the amount of that resource invested in the planet's build queues
+	 * 
+	 * @param invested
+	 *            the amount of that resource invested in the planet's build queues
+	 */
+	public void setInvested( long invested )
+	{
+		this.invested = invested;
+	}
+
+
+	/**
+	 * @return the resource provider data on that planet for the current resource type or
+	 *         <code>null</code> if there is no resource provider of that type.
+	 */
+	public ResourceProviderRecord getResourceProvider( )
+	{
+		return this.resourceProvider;
+	}
+
+
+	/**
+	 * Set the resource provider data on that planet for the current resource type
+	 * 
+	 * @param resourceProvider
+	 *            the resource provider data on that planet for the current resource type
+	 */
+	public void setResourceProvider( ResourceProviderRecord resourceProvider )
+	{
+		this.resourceProvider = resourceProvider;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/ResourceProviderRecord.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/ResourceProviderRecord.java
new file mode 100644
index 0000000..dee594d
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/ResourceProviderRecord.java
@@ -0,0 +1,117 @@
+package com.deepclone.lw.cmd.player.gdata.planets;
+
+
+import java.io.Serializable;
+
+
+
+/**
+ * Information displayed about a resource provider
+ * 
+ * <p>
+ * This class carries the information about a resource provider and associated mining priorities.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class ResourceProviderRecord
+		implements Serializable
+{
+
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** The resource provider's capacity (maximal quantity of resources) */
+	private long capacity;
+
+	/** The current quantity of resources in the provider */
+	private long quantity;
+
+	/** The extraction difficulty as a percentage */
+	private int difficulty;
+
+	/** The extraction priority */
+	private int priority;
+
+
+	/** @return the resource provider's capacity */
+	public long getCapacity( )
+	{
+		return this.capacity;
+	}
+
+
+	/**
+	 * Set the resource provider's capacity
+	 * 
+	 * @param capacity
+	 *            the resource provider's capacity
+	 */
+	public void setCapacity( long capacity )
+	{
+		this.capacity = capacity;
+	}
+
+
+	/** @return the resource provider's current quantity */
+	public long getQuantity( )
+	{
+		return this.quantity;
+	}
+
+
+	/**
+	 * Set the resource provider's current quantity
+	 * 
+	 * @param quantity
+	 *            the resource provider's current quantity
+	 */
+	public void setQuantity( long quantity )
+	{
+		this.quantity = quantity;
+	}
+
+
+	/** @return the extraction difficulty */
+	public int getDifficulty( )
+	{
+		return this.difficulty;
+	}
+
+
+	/**
+	 * Set the extraction difficulty
+	 * 
+	 * @param difficulty
+	 *            the extraction difficulty
+	 */
+	public void setDifficulty( int difficulty )
+	{
+		this.difficulty = difficulty;
+	}
+
+
+	/** @return the extraction priority */
+	public int getPriority( )
+	{
+		return this.priority;
+	}
+
+
+	/**
+	 * Set the extraction priority
+	 * 
+	 * @param priority
+	 *            the extraction priority
+	 */
+	public void setPriority( int priority )
+	{
+		this.priority = priority;
+	}
+
+}
diff --git a/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/TestAbstractResourceRecord.java b/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/TestAbstractResourceRecord.java
new file mode 100644
index 0000000..e4a0e5c
--- /dev/null
+++ b/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/TestAbstractResourceRecord.java
@@ -0,0 +1,88 @@
+package com.deepclone.lw.cmd.player.gdata;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+
+/**
+ * Tests of the {@link AbstractResourceRecord} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestAbstractResourceRecord
+{
+	/** String used in tests */
+	private static final String TEST_STRING = "Test";
+
+	/** Class used in the tests */
+	@SuppressWarnings( "serial" )
+	private static class FakeChild
+			extends AbstractResourceRecord
+	{
+		// EMPTY
+	}
+
+	/** Guinea pig instance */
+	private FakeChild resource;
+
+
+	/** Create the "guinea pig" instance */
+	@Before
+	public void setUp( )
+	{
+		this.resource = new FakeChild( );
+	}
+
+
+	/** Test default values */
+	@Test
+	public void testDefaults( )
+	{
+		assertNull( this.resource.getIdentifier( ) );
+		assertNull( this.resource.getCategory( ) );
+		assertNull( this.resource.getTitle( ) );
+		assertNull( this.resource.getDescription( ) );
+	}
+
+
+	/** Test setting and reading the identifier */
+	@Test
+	public void testIdentifier( )
+	{
+		this.resource.setIdentifier( TEST_STRING );
+		assertEquals( TEST_STRING , this.resource.getIdentifier( ) );
+	}
+
+
+	/** Test setting and reading the category */
+	@Test
+	public void testCategory( )
+	{
+		this.resource.setCategory( TEST_STRING );
+		assertEquals( TEST_STRING , this.resource.getCategory( ) );
+	}
+
+
+	/** Test setting and reading the title */
+	@Test
+	public void testTitle( )
+	{
+		this.resource.setTitle( TEST_STRING );
+		assertEquals( TEST_STRING , this.resource.getTitle( ) );
+	}
+
+
+	/** Test setting and reading the description */
+	@Test
+	public void testDescription( )
+	{
+		this.resource.setDescription( TEST_STRING );
+		assertEquals( TEST_STRING , this.resource.getDescription( ) );
+	}
+
+}
diff --git a/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/empire/TestEmpireResourceRecord.java b/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/empire/TestEmpireResourceRecord.java
new file mode 100644
index 0000000..02f3ea7
--- /dev/null
+++ b/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/empire/TestEmpireResourceRecord.java
@@ -0,0 +1,125 @@
+package com.deepclone.lw.cmd.player.gdata.empire;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+
+/**
+ * Tests for the {@link EmpireResourceRecord} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestEmpireResourceRecord
+{
+	/** String used in tests */
+	private static final String TEST_STRING = "Test";
+
+	/** Long integer used in tests */
+	private static final long TEST_LONG = 42L;
+
+	private static final int TEST_INTEGER = 42;
+
+	/** The "guinea pig" instance */
+	private EmpireResourceRecord instance;
+
+
+	/** Create the "guinea pig" instance */
+	@Before
+	public void setUp( )
+	{
+		this.instance = new EmpireResourceRecord( );
+	}
+
+
+	/** Check default values */
+	@Test
+	public void testDefaults( )
+	{
+		assertNull( this.instance.getIdentifier( ) );
+		assertNull( this.instance.getDescription( ) );
+		assertEquals( 0L , this.instance.getIncome( ) );
+		assertEquals( 0L , this.instance.getPlanetUpkeep( ) );
+		assertEquals( 0L , this.instance.getFleetUpkeep( ) );
+		assertEquals( 0L , this.instance.getUpkeep( ) );
+		assertEquals( 0L , this.instance.getStockpiled( ) );
+		assertNull( this.instance.getMiningPriority( ) );
+	}
+
+
+	/** Setting and reading the identifier */
+	@Test
+	public void testIdentifier( )
+	{
+		this.instance.setIdentifier( TEST_STRING );
+		assertEquals( TEST_STRING , this.instance.getIdentifier( ) );
+	}
+
+
+	/** Setting and reading the description */
+	@Test
+	public void testDescription( )
+	{
+		this.instance.setDescription( TEST_STRING );
+		assertEquals( TEST_STRING , this.instance.getDescription( ) );
+	}
+
+
+	/** Setting and reading the income */
+	@Test
+	public void testIncome( )
+	{
+		this.instance.setIncome( TEST_LONG );
+		assertEquals( TEST_LONG , this.instance.getIncome( ) );
+	}
+
+
+	/** Setting and reading the planet upkeep */
+	@Test
+	public void testPlanetUpkeep( )
+	{
+		this.instance.setPlanetUpkeep( TEST_LONG );
+		assertEquals( TEST_LONG , this.instance.getPlanetUpkeep( ) );
+	}
+
+
+	/** Setting and reading the fleet upkeep */
+	@Test
+	public void testFleetUpkeep( )
+	{
+		this.instance.setFleetUpkeep( TEST_LONG );
+		assertEquals( TEST_LONG , this.instance.getFleetUpkeep( ) );
+	}
+
+
+	/** Setting and reading the stockpiled quantity */
+	@Test
+	public void testStockpiled( )
+	{
+		this.instance.setStockpiled( TEST_LONG );
+		assertEquals( TEST_LONG , this.instance.getStockpiled( ) );
+	}
+
+
+	/** Setting and reading the mining priority */
+	@Test
+	public void testMiningPriority( )
+	{
+		this.instance.setMiningPriority( TEST_INTEGER );
+		assertEquals( (Integer) TEST_INTEGER , this.instance.getMiningPriority( ) );
+	}
+
+
+	/** Total upkeep = fleet upkeep + planet upkeep */
+	@Test
+	public void testTotalUpkeep( )
+	{
+		this.instance.setPlanetUpkeep( TEST_LONG );
+		assertEquals( TEST_LONG , this.instance.getUpkeep( ) );
+		this.instance.setFleetUpkeep( TEST_LONG + 1 );
+		assertEquals( TEST_LONG * 2 + 1 , this.instance.getUpkeep( ) );
+	}
+}
diff --git a/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/planets/TestPlanetResourceRecord.java b/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/planets/TestPlanetResourceRecord.java
new file mode 100644
index 0000000..30818de
--- /dev/null
+++ b/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/planets/TestPlanetResourceRecord.java
@@ -0,0 +1,82 @@
+package com.deepclone.lw.cmd.player.gdata.planets;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+
+/**
+ * Tests for the {@link PlanetResourceRecord} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestPlanetResourceRecord
+{
+	/** Long integer used in tests */
+	private static final long TEST_LONG = 42L;
+
+	/** Resource data instance to test on */
+	private PlanetResourceRecord resourceData;
+
+
+	/** Create the "guinea pig" instance */
+	@Before
+	public void setUp( )
+	{
+		this.resourceData = new PlanetResourceRecord( );
+	}
+
+
+	/** Test default values */
+	@Test
+	public void testDefaults( )
+	{
+		assertEquals( 0L , this.resourceData.getIncome( ) );
+		assertEquals( 0L , this.resourceData.getUpkeep( ) );
+		assertEquals( 0L , this.resourceData.getInvested( ) );
+		assertNull( this.resourceData.getResourceProvider( ) );
+	}
+
+
+	/** Test setting and reading the income */
+	@Test
+	public void testIncome( )
+	{
+		this.resourceData.setIncome( TEST_LONG );
+		assertEquals( TEST_LONG , this.resourceData.getIncome( ) );
+	}
+
+
+	/** Test setting and reading the upkeep */
+	@Test
+	public void testUpkeep( )
+	{
+		this.resourceData.setUpkeep( TEST_LONG );
+		assertEquals( TEST_LONG , this.resourceData.getUpkeep( ) );
+	}
+
+
+	/** Test setting and reading the invested amount */
+	@Test
+	public void testInvested( )
+	{
+		this.resourceData.setInvested( TEST_LONG );
+		assertEquals( TEST_LONG , this.resourceData.getInvested( ) );
+	}
+
+
+	/** Test setting and reading the resource provider */
+	@Test
+	public void testResourceProvider( )
+	{
+		ResourceProviderRecord rpd = new ResourceProviderRecord( );
+		rpd.setCapacity( TEST_LONG );
+		this.resourceData.setResourceProvider( rpd );
+		rpd = this.resourceData.getResourceProvider( );
+		assertNotNull( rpd );
+		assertEquals( TEST_LONG , rpd.getCapacity( ) );
+	}
+}
diff --git a/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/planets/TestResourceProviderRecord.java b/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/planets/TestResourceProviderRecord.java
new file mode 100644
index 0000000..cac3037
--- /dev/null
+++ b/legacyworlds-tests/src/test/java/com/deepclone/lw/cmd/player/gdata/planets/TestResourceProviderRecord.java
@@ -0,0 +1,83 @@
+package com.deepclone.lw.cmd.player.gdata.planets;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+
+/**
+ * Tests for the {@link ResourceProviderRecord} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class TestResourceProviderRecord
+{
+
+	/** Long integer used in tests */
+	private static final long TEST_LONG = 42L;
+
+	/** Integer used in tests */
+	private static final int TEST_INT = 42;
+
+	/** Resource provider data instance to test on */
+	private ResourceProviderRecord resProvData;
+
+
+	/** Create the "guinea pig" instance */
+	@Before
+	public void setUp( )
+	{
+		this.resProvData = new ResourceProviderRecord( );
+	}
+	
+	/** Check default values */
+	@Test
+	public void testDefaults( )
+	{
+		assertEquals( 0L , this.resProvData.getCapacity( ) );
+		assertEquals( 0L , this.resProvData.getQuantity( ) );
+		assertEquals( 0 , this.resProvData.getDifficulty( ) );
+		assertEquals( 0 , this.resProvData.getPriority( ) );
+	}
+
+
+	/** Setting and reading the capacity */
+	@Test
+	public void testSetCapacity( )
+	{
+		this.resProvData.setCapacity( TEST_LONG );
+		assertEquals( TEST_LONG , this.resProvData.getCapacity( ) );
+	}
+
+
+	/** Setting and reading the quantity */
+	@Test
+	public void testSetQuantity( )
+	{
+		this.resProvData.setQuantity( TEST_LONG );
+		assertEquals( TEST_LONG , this.resProvData.getQuantity( ) );
+	}
+
+
+	/** Setting and reading the difficulty */
+	@Test
+	public void testSetDifficulty( )
+	{
+		this.resProvData.setDifficulty( TEST_INT );
+		assertEquals( TEST_INT , this.resProvData.getDifficulty( ) );
+	}
+
+
+	/** Setting and reading the priority */
+	@Test
+	public void testSetPriority( )
+	{
+		this.resProvData.setPriority( TEST_INT );
+		assertEquals( TEST_INT , this.resProvData.getPriority( ) );
+	}
+
+}
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/game.ftl
index f9518eb..122c93a 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/game.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/game.ftl
@@ -63,4 +63,11 @@
 </#macro>
 <#macro abbr_bgc><abbr title="billion galactic credits">bgc</abbr></#macro>
 <#macro abbr_st><abbr title="Server time">ST</abbr></#macro>
-<#macro abbr_gt><abbr title="Time in the game universe">GT</abbr></#macro>
\ No newline at end of file
+<#macro abbr_gt><abbr title="Time in the game universe">GT</abbr></#macro>
+<#macro over_time title>
+	<#if data.page.useRLTime>
+		${title?xhtml} (for 12h)
+	<#else>
+		Monthly ${title?xhtml}
+	</#if>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
index af04b79..639de56 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
@@ -61,7 +61,64 @@
 		
 			</@right_column>
 		</@tab>
+
+		<@tab id="economy" title="Economy">
 		
+			<@listview>
+				<@lv_line headers=true>
+					<@lv_column width=30>&nbsp;</@lv_column>
+					<@lv_column width="x">Resource</@lv_column>
+					<@lv_column width=100 centered=true><@over_time "Income" /></@lv_column>
+					<@lv_column width=100 centered=true><@over_time "Upkeep" /></@lv_column>
+					<@lv_column width=100 centered=true>Reserves</@lv_column>
+					<@lv_column width=100 centered=true>Mining priority</@lv_column>
+				</@lv_line>
+					
+				<#list ov.economy as resource>
+
+					<#if previousCategory?has_content && !resource.category?has_content
+							|| resource.category?has_content && !previousCategory?has_content
+							|| resource.category?has_content && previousCategory?has_content
+								&& resource.category != previousCategory>
+						<@lv_line>
+							<#if resource.category?has_content>
+								<td colspan="5"><strong>${resource.category?xhtml}</strong></td>
+							<#else>
+								<td colspan="5"><hr /></td>
+							</#if>
+						</@lv_line>
+						<#local previousCategory=resource.category>
+					</#if>
+					
+					<@lv_line>
+						<@lv_column>&nbsp;</@lv_column>
+						<@lv_column>${resource.title?xhtml}
+							<div class="auto-hide">${resource.description?xhtml}</div>
+						</@lv_column>
+						<@lv_column centered=true>${resource.income?string(",##0")}</@lv_column>
+						<@lv_column centered=true>${resource.upkeep?string(",##0")}</@lv_column>
+						<@lv_column centered=true>${resource.stockpiled?string(",##0")}</@lv_column>
+						<@lv_column centered=true>
+							<#if resource.miningPriority?has_content>
+								<#switch resource.miningPriority>
+									<#case 0>lowest<#break>
+									<#case 1>low<#break>
+									<#case 2>normal<#break>
+									<#case 3>high<#break>
+									<#case 4>highest<#break>
+								</#switch>
+							<#else>
+								N/A
+							</#if>
+						</@lv_column>
+					</@lv_line>
+
+				</#list>
+
+			</@listview>
+
+		</@tab>
+
 		<@tab id="research" title="Research">
 			<#if rs?size == 0>
 				<p>Our scientists are still settling in.</p>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl
index f32d709..7f3649c 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl
@@ -108,6 +108,55 @@
 			</#if>
 
 		</@tab>
+
+		<#if data.own?has_content && data.own.resources?size gt 0>
+		
+			<@tab id="resources" title="Economy">
+			
+				<@listview>
+					<#local previousCategory="">
+
+					<@lv_line headers=true>
+						<@lv_column width=30>&nbsp;</@lv_column>
+						<@lv_column width="x">Resource</@lv_column>
+						<@lv_column width=100 centered=true><@over_time "Income" /></@lv_column>
+						<@lv_column width=100 centered=true><@over_time "Upkeep" /></@lv_column>
+						<@lv_column width=100 centered=true>Invested</@lv_column>
+					</@lv_line>
+					
+					<#list data.own.resources as resource>
+
+						<#if previousCategory?has_content && !resource.category?has_content
+								|| resource.category?has_content && !previousCategory?has_content
+								|| resource.category?has_content && previousCategory?has_content
+									&& resource.category != previousCategory>
+							<@lv_line>
+								<#if resource.category?has_content>
+									<td colspan="5"><strong>${resource.category?xhtml}</strong></td>
+								<#else>
+									<td colspan="5"><hr /></td>
+								</#if>
+							</@lv_line>
+							<#local previousCategory=resource.category>
+						</#if>
+						
+						<@lv_line>
+							<@lv_column>&nbsp;</@lv_column>
+							<@lv_column>${resource.title?xhtml}
+								<div class="auto-hide">${resource.description?xhtml}</div>
+							</@lv_column>
+							<@lv_column centered=true>${resource.income?string(",##0")}</@lv_column>
+							<@lv_column centered=true>${resource.upkeep?string(",##0")}</@lv_column>
+							<@lv_column centered=true>${resource.invested?string(",##0")}</@lv_column>
+						</@lv_line>
+
+					</#list>
+
+				</@listview>
+			
+			</@tab>
+
+		</#if>
 	
 		<#if data.orbit?has_content>
 		
@@ -143,6 +192,7 @@
 												<#case "DEF">defence<#break>
 												<#case "WORK">mil. output<#break>
 												<#case "POP">growth<#break>
+												<#case "MINE">res. extraction<#break>
 											</#switch>
 										</@lv_column>
 										<@lv_column centered=true>${building.jobs?string(",##0")}</@lv_column>
@@ -189,6 +239,7 @@
 														<#case "DEF">defence<#break>
 														<#case "WORK">mil. output<#break>
 														<#case "POP">growth<#break>
+														<#case "MINE">res. extraction<#break>
 													</#switch>
 												</@dt_entry>
 											</@dt_main>
@@ -251,6 +302,7 @@
 			</#if>
 
 			<#if data.own?has_content>
+
 				<@tab id="ships" title="Shipyards">
 					<#if data.page.special! != 'v'>
 						<#if data.own.milQueue.appendPossible>
@@ -321,9 +373,60 @@
 						</@listview>
 					</#if>
 				</@tab>
-			</#if>
-		</#if>
 
+			</#if>
+			
+			<#list data.own.resources as resource>
+				<#if resource.resourceProvider?has_content>
+					<#local showResources=true>
+					<#break>
+				</#if>
+			</#list>
+			
+			<#if showResources?has_content>
+				<@tab id="natres" title="Natural resources">
+				
+					<@listview>
+						<@lv_line headers=true>
+							<@lv_column width="x">Resource</@lv_column>
+							<@lv_column width=100 right=true>Quantity&nbsp;</@lv_column>
+							<@lv_column width=100>&nbsp;&nbsp;Capacity</@lv_column>
+							<@lv_column width=100 centered=true>Extraction<br/>difficulty</@lv_column>
+							<@lv_column width=100 centered=true>Priority</@lv_column>
+						</@lv_line>
+						
+						<#list data.own.resources as resource>
+							<#if resource.resourceProvider?has_content>
+								<#local resProv=resource.resourceProvider>
+
+								<@lv_line>
+									<@lv_column>
+										${resource.title?xhtml}
+										<div class="auto-hide">${resource.description?xhtml}</div>
+									</@lv_column>
+									<@lv_column right=true>${resProv.quantity?string(",##0")}&nbsp;</@lv_column>
+									<@lv_column>/&nbsp;${resProv.capacity?string(",##0")}</@lv_column>
+									<@lv_column centered=true>${resProv.difficulty}&nbsp;%</@lv_column>
+									<@lv_column centered=true>
+										<#switch resProv.priority>
+											<#case 0>lowest<#break>
+											<#case 1>low<#break>
+											<#case 2>normal<#break>
+											<#case 3>high<#break>
+											<#case 4>highest<#break>
+										</#switch>
+									</@lv_column>
+								</@lv_line>
+
+							</#if>
+						</#list>
+					</@listview>
+
+				</@tab>
+			</#if>
+
+		</#if>
+		
 	</@tabs>
 </@page>
 </#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/game.ftl
index 81e8aa9..b0c8a1a 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/game.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/game.ftl
@@ -63,4 +63,11 @@
 </#macro>
 <#macro abbr_bgc><abbr title="milliards de crédits galactiques">mcg</abbr></#macro>
 <#macro abbr_st><abbr title="Temps Serveur">TS </abbr></#macro>
-<#macro abbr_gt><abbr title="Temps dans l'univers du jeu">TJ </abbr></#macro>
\ No newline at end of file
+<#macro abbr_gt><abbr title="Temps dans l'univers du jeu">TJ </abbr></#macro>
+<#macro over_time title feminin=false pluriel=false>
+	<#if data.page.useRLTime>
+		${title?xhtml} (pour 12h)
+	<#else>
+		 ${title?xhtml} mensuel<#if feminin>le</#if><#if pluriel>s</#if>
+	</#if>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
index 919f8e9..5bf71e8 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
@@ -62,6 +62,63 @@
 			</@right_column>
 		</@tab>
 		
+		<@tab id="economy" title="Économie">
+		
+			<@listview>
+				<@lv_line headers=true>
+					<@lv_column width=30>&nbsp;</@lv_column>
+					<@lv_column width="x">Ressource</@lv_column>
+					<@lv_column width=100 centered=true><@over_time "Bénéfice" /></@lv_column>
+					<@lv_column width=100 centered=true><@over_time title="Charges" feminin=true pluriel=true /></@lv_column>
+					<@lv_column width=100 centered=true>Réserves</@lv_column>
+					<@lv_column width=100 centered=true>Priorité d'extraction</@lv_column>
+				</@lv_line>
+					
+				<#list ov.economy as resource>
+
+					<#if previousCategory?has_content && !resource.category?has_content
+							|| resource.category?has_content && !previousCategory?has_content
+							|| resource.category?has_content && previousCategory?has_content
+								&& resource.category != previousCategory>
+						<@lv_line>
+							<#if resource.category?has_content>
+								<td colspan="5"><strong>${resource.category?xhtml}</strong></td>
+							<#else>
+								<td colspan="5"><hr /></td>
+							</#if>
+						</@lv_line>
+						<#local previousCategory=resource.category>
+					</#if>
+					
+					<@lv_line>
+						<@lv_column>&nbsp;</@lv_column>
+						<@lv_column>${resource.title?xhtml}
+							<div class="auto-hide">${resource.description?xhtml}</div>
+						</@lv_column>
+						<@lv_column centered=true>${resource.income?string(",##0")}</@lv_column>
+						<@lv_column centered=true>${resource.upkeep?string(",##0")}</@lv_column>
+						<@lv_column centered=true>${resource.stockpiled?string(",##0")}</@lv_column>
+						<@lv_column centered=true>
+							<#if resource.miningPriority?has_content>
+								<#switch resource.miningPriority>
+									<#case 0>très basse<#break>
+									<#case 1>basse<#break>
+									<#case 2>normale<#break>
+									<#case 3>haute<#break>
+									<#case 4>très haute<#break>
+								</#switch>
+							<#else>
+								N/A
+							</#if>
+						</@lv_column>
+					</@lv_line>
+
+				</#list>
+
+			</@listview>
+
+		</@tab>
+		
 		<@tab id="research" title="Recherche">
 			<#if rs?size == 0>
 				<p>Nos scientifiques sont encore en train de s'installer.</p>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl
index 1aed9bf..5f6fadd 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl
@@ -108,6 +108,55 @@
 			</#if>
 
 		</@tab>
+
+		<#if data.own?has_content && data.own.resources?size gt 0>
+		
+			<@tab id="resources" title="Économie">
+			
+				<@listview>
+					<#local previousCategory="">
+
+					<@lv_line headers=true>
+						<@lv_column width=30>&nbsp;</@lv_column>
+						<@lv_column width="x">Ressource</@lv_column>
+						<@lv_column width=100 centered=true><@over_time "Bénéfice" /></@lv_column>
+						<@lv_column width=100 centered=true><@over_time title="Charges" feminin=true pluriel=true /></@lv_column>
+						<@lv_column width=100 centered=true>Investissement</@lv_column>
+					</@lv_line>
+					
+					<#list data.own.resources as resource>
+
+						<#if previousCategory?has_content && !resource.category?has_content
+								|| resource.category?has_content && !previousCategory?has_content
+								|| resource.category?has_content && previousCategory?has_content
+									&& resource.category != previousCategory>
+							<@lv_line>
+								<#if resource.category?has_content>
+									<td colspan="5"><strong>${resource.category?xhtml}</strong></td>
+								<#else>
+									<td colspan="5"><hr /></td>
+								</#if>
+							</@lv_line>
+							<#local previousCategory=resource.category>
+						</#if>
+						
+						<@lv_line>
+							<@lv_column>&nbsp;</@lv_column>
+							<@lv_column>${resource.title?xhtml}
+								<div class="auto-hide">${resource.description?xhtml}</div>
+							</@lv_column>
+							<@lv_column centered=true>${resource.income?string(",##0")}</@lv_column>
+							<@lv_column centered=true>${resource.upkeep?string(",##0")}</@lv_column>
+							<@lv_column centered=true>${resource.invested?string(",##0")}</@lv_column>
+						</@lv_line>
+
+					</#list>
+
+				</@listview>
+			
+			</@tab>
+
+		</#if>
 	
 		<#if data.orbit?has_content>
 		
@@ -143,6 +192,7 @@
 												<#case "DEF">défense<#break>
 												<#case "WORK">production mil.<#break>
 												<#case "POP">croissance<#break>
+												<#case "MINE">extraction<#break>
 											</#switch>
 										</@lv_column>
 										<@lv_column centered=true>${building.jobs?string(",##0")}</@lv_column>
@@ -189,6 +239,7 @@
 														<#case "DEF">défense<#break>
 														<#case "WORK">production mil.<#break>
 														<#case "POP">croissance<#break>
+														<#case "MINE">extraction<#break>
 													</#switch>
 												</@dt_entry>
 											</@dt_main>
@@ -322,6 +373,56 @@
 					</#if>
 				</@tab>
 			</#if>
+
+			<#list data.own.resources as resource>
+				<#if resource.resourceProvider?has_content>
+					<#local showResources=true>
+					<#break>
+				</#if>
+			</#list>
+			
+			<#if showResources?has_content>
+				<@tab id="natres" title="Ressources naturelles">
+				
+					<@listview>
+						<@lv_line headers=true>
+							<@lv_column width="x">Ressource</@lv_column>
+							<@lv_column width=100 right=true>Quantité&nbsp;</@lv_column>
+							<@lv_column width=100>&nbsp;&nbsp;Capacité</@lv_column>
+							<@lv_column width=100 centered=true>Difficulté<br/>d'extraction</@lv_column>
+							<@lv_column width=100 centered=true>Priorité</@lv_column>
+						</@lv_line>
+						
+						<#list data.own.resources as resource>
+							<#if resource.resourceProvider?has_content>
+								<#local resProv=resource.resourceProvider>
+
+								<@lv_line>
+									<@lv_column>
+										${resource.title?xhtml}
+										<div class="auto-hide">${resource.description?xhtml}</div>
+									</@lv_column>
+									<@lv_column right=true>${resProv.quantity?string(",##0")}&nbsp;</@lv_column>
+									<@lv_column>/&nbsp;${resProv.capacity?string(",##0")}</@lv_column>
+									<@lv_column centered=true>${resProv.difficulty}&nbsp;%</@lv_column>
+									<@lv_column centered=true>
+										<#switch resProv.priority>
+											<#case 0>très basse<#break>
+											<#case 1>basse<#break>
+											<#case 2>normale<#break>
+											<#case 3>haute<#break>
+											<#case 4>très haute<#break>
+										</#switch>
+									</@lv_column>
+								</@lv_line>
+
+							</#if>
+						</#list>
+					</@listview>
+
+				</@tab>
+			</#if>
+
 		</#if>
 
 	</@tabs>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
index 798342a..3e196b7 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
@@ -8,17 +8,17 @@
 		<#nested>
 	</tr>
 </#macro>
-<#macro lv_column width=0 centered=false right=false>
+<#macro lv_column width=0 centered=false right=false colspan=0>
 	<#if width?is_string>
-		<th style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>">
+		<th style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1>colspan="${colspan}"</#if>>
 			<#nested>
 		</th>
 	<#elseif width gt 0>
-		<th style="width: ${width}px; text-align: <#if centered>center<#elseif right>right<#else>left</#if>">
+		<th style="width: ${width}px; text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1>colspan="${colspan}"</#if>>
 			<#nested>
 		</th>
 	<#else>
-		<td style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>">
+		<td style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>" <#if colspan gt 1>colspan="${colspan}"</#if>>
 			<#nested>
 		</td>
 	</#if>

From 35d8891fe3bca322972aaa22e39a18f0246dd5b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 4 Feb 2012 12:56:58 +0100
Subject: [PATCH 40/94] Spring configuration clean-up

* Regrouped component definitions into one single file per package in
most cases, with a few exceptions

* Added documentation about how the configuration is stored and which
guidelines should be followed when adding or modifying Spring component
configurations.
---
 .../configuration/accounts-beans.xml          | 24 ------
 .../accounts/account-cleanup-bean.xml         |  8 --
 .../accounts/account-management-bean.xml      |  9 ---
 .../configuration/accounts/admin-dao-bean.xml |  9 ---
 .../accounts/administration-bean.xml          |  9 ---
 .../configuration/accounts/bans-dao-bean.xml  |  9 ---
 .../accounts/bans-processor-bean.xml          |  9 ---
 .../accounts/default-preferences-bean.xml     |  9 ---
 .../configuration/accounts/ip-ban-bean.xml    |  9 ---
 .../accounts/preference-definitions-bean.xml  |  9 ---
 .../accounts/preferences-dao-bean.xml         |  9 ---
 .../accounts/quit-processor-bean.xml          |  9 ---
 .../accounts/requests-expiration-bean.xml     |  9 ---
 .../accounts/user-session-dao-bean.xml        |  9 ---
 .../configuration/accounts/users-dao-bean.xml |  9 ---
 .../accounts/vacation-processor-bean.xml      |  9 ---
 .../resources/configuration/meta/accounts.xml | 26 +++++++
 .../main/resources/configuration/bt-beans.xml | 11 ---
 .../configuration/bt/admin-bugs-bean.xml      |  9 ---
 .../configuration/bt/bugs-dao-bean.xml        |  9 ---
 .../configuration/bt/empire-summary-bean.xml  |  8 --
 .../{bt/player-bugs-bean.xml => meta/bt.xml}  |  7 +-
 .../configuration/eventlog-beans.xml          | 12 ---
 .../eventlog/admin-error-mail-bean.xml        |  8 --
 .../eventlog/log-cleaner-bean.xml             |  9 ---
 .../eventlog/log-reader-bean.xml              |  9 ---
 .../eventlog/log-writer-bean.xml              |  9 ---
 .../configuration/eventlog/logger-bean.xml    |  9 ---
 .../resources/configuration/meta/eventlog.xml | 13 ++++
 .../resources/configuration/i18n-beans.xml    | 10 ---
 .../configuration/i18n/i18n-manager-bean.xml  |  9 ---
 .../i18n/i18n-translator-bean.xml             |  9 ---
 .../resources/configuration/meta/i18n.xml     |  3 +-
 .../configuration/mailer/mailer-bean.xml      | 10 ---
 .../{mailer-beans.xml => meta/mailer.xml}     |  4 +-
 .../naming-dao-bean.xml => meta/naming.xml}   |  3 +-
 .../resources/configuration/naming-beans.xml  | 10 ---
 .../naming/names-manager-bean.xml             |  8 --
 .../{resources-beans.xml => resources.xml}    |  0
 .../src/main/resources/configuration/game.xml |  8 --
 .../{simple-beans.xml => simple.xml}          |  0
 .../system.xml}                               | 14 +++-
 .../resources/configuration/system-beans.xml  | 12 ---
 .../system/constants-manager-bean.xml         |  9 ---
 .../system/constants-registrar-bean.xml       |  9 ---
 .../system/system-status-bean.xml             |  9 ---
 .../configuration/system/ticker-bean.xml      | 10 ---
 .../sessions.xml}                             |  6 +-
 .../resources/configuration/session-types.xml |  6 +-
 .../administration.xml}                       |  0
 .../external.xml}                             |  0
 .../player.xml}                               |  0
 .../resources/configuration/user-beans.xml    | 14 ----
 .../user/object-name-validator-bean.xml       |  9 ---
 .../user/session-subtype-wiring-bean.xml      |  9 ---
 .../configuration/transaction-bean.xml        | 14 ----
 .../configuration/context-configuration.xml   | 11 ---
 .../src/main/resources/configuration/game.xml | 13 ++++
 .../src/main/resources/configuration/meta.xml | 19 +++++
 .../resources/configuration/transactions.xml  | 15 ++++
 .../src/main/resources/lw-server.xml          | 35 +++++----
 legacyworlds/doc/spring-configuration.txt     | 77 +++++++++++++++++++
 62 files changed, 211 insertions(+), 451 deletions(-)
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts-beans.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-cleanup-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-management-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-dao-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/administration-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-dao-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-processor-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/default-preferences-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/ip-ban-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preference-definitions-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preferences-dao-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/quit-processor-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/requests-expiration-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/user-session-dao-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/users-dao-bean.xml
 delete mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/vacation-processor-bean.xml
 create mode 100644 legacyworlds-server-beans-accounts/src/main/resources/configuration/meta/accounts.xml
 delete mode 100644 legacyworlds-server-beans-bt/src/main/resources/configuration/bt-beans.xml
 delete mode 100644 legacyworlds-server-beans-bt/src/main/resources/configuration/bt/admin-bugs-bean.xml
 delete mode 100644 legacyworlds-server-beans-bt/src/main/resources/configuration/bt/bugs-dao-bean.xml
 delete mode 100644 legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
 rename legacyworlds-server-beans-bt/src/main/resources/configuration/{bt/player-bugs-bean.xml => meta/bt.xml} (52%)
 delete mode 100644 legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog-beans.xml
 delete mode 100644 legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml
 delete mode 100644 legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-cleaner-bean.xml
 delete mode 100644 legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-reader-bean.xml
 delete mode 100644 legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-writer-bean.xml
 delete mode 100644 legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/logger-bean.xml
 create mode 100644 legacyworlds-server-beans-eventlog/src/main/resources/configuration/meta/eventlog.xml
 delete mode 100644 legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n-beans.xml
 delete mode 100644 legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-manager-bean.xml
 delete mode 100644 legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-translator-bean.xml
 rename legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml => legacyworlds-server-beans-i18n/src/main/resources/configuration/meta/i18n.xml (65%)
 delete mode 100644 legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer/mailer-bean.xml
 rename legacyworlds-server-beans-mailer/src/main/resources/configuration/{mailer-beans.xml => meta/mailer.xml} (74%)
 rename legacyworlds-server-beans-naming/src/main/resources/configuration/{naming/naming-dao-bean.xml => meta/naming.xml} (69%)
 delete mode 100644 legacyworlds-server-beans-naming/src/main/resources/configuration/naming-beans.xml
 delete mode 100644 legacyworlds-server-beans-naming/src/main/resources/configuration/naming/names-manager-bean.xml
 rename legacyworlds-server-beans-resources/src/main/resources/configuration/game/{resources-beans.xml => resources.xml} (100%)
 delete mode 100644 legacyworlds-server-beans-simple/src/main/resources/configuration/game.xml
 rename legacyworlds-server-beans-simple/src/main/resources/configuration/{simple-beans.xml => simple.xml} (100%)
 rename legacyworlds-server-beans-system/src/main/resources/configuration/{system/session-manager-bean.xml => meta/system.xml} (54%)
 delete mode 100644 legacyworlds-server-beans-system/src/main/resources/configuration/system-beans.xml
 delete mode 100644 legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-manager-bean.xml
 delete mode 100644 legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-registrar-bean.xml
 delete mode 100644 legacyworlds-server-beans-system/src/main/resources/configuration/system/system-status-bean.xml
 delete mode 100644 legacyworlds-server-beans-system/src/main/resources/configuration/system/ticker-bean.xml
 rename legacyworlds-server-beans-user/src/main/resources/configuration/{user/session-command-wiring-bean.xml => meta/sessions.xml} (56%)
 rename legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-recap-bean.xml => legacyworlds-server-beans-user/src/main/resources/configuration/session-types.xml (65%)
 rename legacyworlds-server-beans-user/src/main/resources/configuration/{user/admin-session-definer-bean.xml => session-types/administration.xml} (100%)
 rename legacyworlds-server-beans-user/src/main/resources/configuration/{user/external-session-definer-bean.xml => session-types/external.xml} (100%)
 rename legacyworlds-server-beans-user/src/main/resources/configuration/{user/player-session-definer-bean.xml => session-types/player.xml} (100%)
 delete mode 100644 legacyworlds-server-beans-user/src/main/resources/configuration/user-beans.xml
 delete mode 100644 legacyworlds-server-beans-user/src/main/resources/configuration/user/object-name-validator-bean.xml
 delete mode 100644 legacyworlds-server-beans-user/src/main/resources/configuration/user/session-subtype-wiring-bean.xml
 delete mode 100644 legacyworlds-server-data/src/main/resources/configuration/transaction-bean.xml
 delete mode 100644 legacyworlds-server-main/src/main/resources/configuration/context-configuration.xml
 create mode 100644 legacyworlds-server-main/src/main/resources/configuration/game.xml
 create mode 100644 legacyworlds-server-main/src/main/resources/configuration/meta.xml
 create mode 100644 legacyworlds-server-main/src/main/resources/configuration/transactions.xml
 create mode 100644 legacyworlds/doc/spring-configuration.txt

diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts-beans.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts-beans.xml
deleted file mode 100644
index bd7ec9b..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts-beans.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<import resource="accounts/account-cleanup-bean.xml" />
-	<import resource="accounts/account-management-bean.xml" />
-	<import resource="accounts/admin-dao-bean.xml" />
-	<import resource="accounts/admin-recap-bean.xml" />
-	<import resource="accounts/administration-bean.xml" />
-	<import resource="accounts/bans-dao-bean.xml" />
-	<import resource="accounts/bans-processor-bean.xml" />
-	<import resource="accounts/default-preferences-bean.xml" />
-	<import resource="accounts/ip-ban-bean.xml" />
-	<import resource="accounts/preference-definitions-bean.xml" />
-	<import resource="accounts/preferences-dao-bean.xml" />
-	<import resource="accounts/quit-processor-bean.xml" />
-	<import resource="accounts/requests-expiration-bean.xml" />
-	<import resource="accounts/user-session-dao-bean.xml" />
-	<import resource="accounts/users-dao-bean.xml" />
-	<import resource="accounts/vacation-processor-bean.xml" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-cleanup-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-cleanup-bean.xml
deleted file mode 100644
index e1c6a61..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-cleanup-bean.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="accountCleanup" class="com.deepclone.lw.beans.acm.AccountCleanupBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-management-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-management-bean.xml
deleted file mode 100644
index 6062c6a..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/account-management-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="accountManagement" class="com.deepclone.lw.beans.acm.AccountManagementBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-dao-bean.xml
deleted file mode 100644
index 8640f69..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-dao-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="adminDao" class="com.deepclone.lw.beans.admin.AdminDAOBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/administration-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/administration-bean.xml
deleted file mode 100644
index 1039d63..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/administration-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="administration" class="com.deepclone.lw.beans.admin.AdministrationBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-dao-bean.xml
deleted file mode 100644
index f654c86..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-dao-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="bansDao" class="com.deepclone.lw.beans.admin.BansDAOBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-processor-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-processor-bean.xml
deleted file mode 100644
index 4167e63..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/bans-processor-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="bansProcessor" class="com.deepclone.lw.beans.admin.BansProcessorBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/default-preferences-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/default-preferences-bean.xml
deleted file mode 100644
index e7bca93..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/default-preferences-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="defaultPreferences" class="com.deepclone.lw.beans.prefs.DefaultPreferencesBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/ip-ban-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/ip-ban-bean.xml
deleted file mode 100644
index 2c70cf8..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/ip-ban-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="ipBan" class="com.deepclone.lw.beans.admin.IpBanBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preference-definitions-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preference-definitions-bean.xml
deleted file mode 100644
index 73dfc57..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preference-definitions-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="preferenceDefinitions" class="com.deepclone.lw.beans.prefs.PreferenceDefinitionsBean" />
-	<bean id="preferenceTypesRegistry" class="com.deepclone.lw.beans.prefs.PreferenceTypesRegistryBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preferences-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preferences-dao-bean.xml
deleted file mode 100644
index 42926c3..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/preferences-dao-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="preferencesDAO" class="com.deepclone.lw.beans.prefs.PreferencesDAOBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/quit-processor-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/quit-processor-bean.xml
deleted file mode 100644
index a571d09..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/quit-processor-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="quitProcessor" class="com.deepclone.lw.beans.acm.QuitProcessorBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/requests-expiration-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/requests-expiration-bean.xml
deleted file mode 100644
index f4525e4..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/requests-expiration-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="requestsExpiration" class="com.deepclone.lw.beans.acm.RequestsExpirationBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/user-session-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/user-session-dao-bean.xml
deleted file mode 100644
index 0c7e785..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/user-session-dao-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="userSessionDao" class="com.deepclone.lw.beans.acm.UserSessionDAOBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/users-dao-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/users-dao-bean.xml
deleted file mode 100644
index 817940f..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/users-dao-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="usersDAO" class="com.deepclone.lw.beans.acm.UsersDAOBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/vacation-processor-bean.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/vacation-processor-bean.xml
deleted file mode 100644
index 2c67fea..0000000
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/vacation-processor-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="vacationProcessor" class="com.deepclone.lw.beans.acm.VacationProcessorBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/meta/accounts.xml b/legacyworlds-server-beans-accounts/src/main/resources/configuration/meta/accounts.xml
new file mode 100644
index 0000000..98207c4
--- /dev/null
+++ b/legacyworlds-server-beans-accounts/src/main/resources/configuration/meta/accounts.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+	<bean id="accountCleanup" class="com.deepclone.lw.beans.acm.AccountCleanupBean" />
+	<bean id="accountManagement" class="com.deepclone.lw.beans.acm.AccountManagementBean" />
+	<bean id="adminDao" class="com.deepclone.lw.beans.admin.AdminDAOBean" />
+	<bean id="administration" class="com.deepclone.lw.beans.admin.AdministrationBean" />
+	<bean id="adminRecap" class="com.deepclone.lw.beans.admin.AdminRecapBean" />
+	<bean id="bansDao" class="com.deepclone.lw.beans.admin.BansDAOBean" />
+	<bean id="bansProcessor" class="com.deepclone.lw.beans.admin.BansProcessorBean" />
+	<bean id="defaultPreferences" class="com.deepclone.lw.beans.prefs.DefaultPreferencesBean" />
+	<bean id="ipBan" class="com.deepclone.lw.beans.admin.IpBanBean" />
+	<bean id="preferenceDefinitions" class="com.deepclone.lw.beans.prefs.PreferenceDefinitionsBean" />
+	<bean id="preferencesDAO" class="com.deepclone.lw.beans.prefs.PreferencesDAOBean" />
+	<bean id="preferenceTypesRegistry"
+		class="com.deepclone.lw.beans.prefs.PreferenceTypesRegistryBean" />
+	<bean id="quitProcessor" class="com.deepclone.lw.beans.acm.QuitProcessorBean" />
+	<bean id="requestsExpiration" class="com.deepclone.lw.beans.acm.RequestsExpirationBean" />
+	<bean id="usersDAO" class="com.deepclone.lw.beans.acm.UsersDAOBean" />
+	<bean id="userSessionDao" class="com.deepclone.lw.beans.acm.UserSessionDAOBean" />
+	<bean id="vacationProcessor" class="com.deepclone.lw.beans.acm.VacationProcessorBean" />
+
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt-beans.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt-beans.xml
deleted file mode 100644
index 2673721..0000000
--- a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt-beans.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<import resource="bt/admin-bugs-bean.xml" />
-	<import resource="bt/bugs-dao-bean.xml" />
-	<import resource="bt/empire-summary-bean.xml" />
-	<import resource="bt/player-bugs-bean.xml" />
-
-</beans>
diff --git a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/admin-bugs-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/admin-bugs-bean.xml
deleted file mode 100644
index 5ddaaca..0000000
--- a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/admin-bugs-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="adminBugs" class="com.deepclone.lw.beans.bt.AdminBugsBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/bugs-dao-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/bugs-dao-bean.xml
deleted file mode 100644
index 3850ac1..0000000
--- a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/bugs-dao-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="bugsDAO" class="com.deepclone.lw.beans.bt.BugsDAOBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
deleted file mode 100644
index a268802..0000000
--- a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/empire-summary-bean.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="empireSummary" class="com.deepclone.lw.beans.bt.es.EmpireSummaryBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/player-bugs-bean.xml b/legacyworlds-server-beans-bt/src/main/resources/configuration/meta/bt.xml
similarity index 52%
rename from legacyworlds-server-beans-bt/src/main/resources/configuration/bt/player-bugs-bean.xml
rename to legacyworlds-server-beans-bt/src/main/resources/configuration/meta/bt.xml
index 47c377f..d96389c 100644
--- a/legacyworlds-server-beans-bt/src/main/resources/configuration/bt/player-bugs-bean.xml
+++ b/legacyworlds-server-beans-bt/src/main/resources/configuration/meta/bt.xml
@@ -2,8 +2,11 @@
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
+	<bean id="adminBugs" class="com.deepclone.lw.beans.bt.AdminBugsBean" />
+	<bean id="bugsDAO" class="com.deepclone.lw.beans.bt.BugsDAOBean" />
+	<bean id="empireSummary" class="com.deepclone.lw.beans.bt.es.EmpireSummaryBean" />
 	<bean id="playerBugs" class="com.deepclone.lw.beans.bt.PlayerBugsBean" />
 
-</beans>
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog-beans.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog-beans.xml
deleted file mode 100644
index 934a2d8..0000000
--- a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog-beans.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<import resource="eventlog/admin-error-mail-bean.xml" />
-	<import resource="eventlog/log-cleaner-bean.xml" />
-	<import resource="eventlog/log-reader-bean.xml" />
-	<import resource="eventlog/log-writer-bean.xml" />
-	<import resource="eventlog/logger-bean.xml" />
-
-</beans>
diff --git a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml
deleted file mode 100644
index f0e6a6e..0000000
--- a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/admin-error-mail-bean.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="adminErrorMail" class="com.deepclone.lw.beans.eventlog.AdminErrorMailBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-cleaner-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-cleaner-bean.xml
deleted file mode 100644
index 7773c57..0000000
--- a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-cleaner-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="logCleaner" class="com.deepclone.lw.beans.eventlog.LogCleanerBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-reader-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-reader-bean.xml
deleted file mode 100644
index 1e56da0..0000000
--- a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-reader-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="logReader" class="com.deepclone.lw.beans.eventlog.LogReaderBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-writer-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-writer-bean.xml
deleted file mode 100644
index 21fc777..0000000
--- a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/log-writer-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="logWriter" class="com.deepclone.lw.beans.eventlog.LogWriterBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/logger-bean.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/logger-bean.xml
deleted file mode 100644
index 22b1347..0000000
--- a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/eventlog/logger-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="logger" class="com.deepclone.lw.beans.eventlog.LoggerBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-eventlog/src/main/resources/configuration/meta/eventlog.xml b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/meta/eventlog.xml
new file mode 100644
index 0000000..7841904
--- /dev/null
+++ b/legacyworlds-server-beans-eventlog/src/main/resources/configuration/meta/eventlog.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+	<bean id="adminErrorMail" class="com.deepclone.lw.beans.eventlog.AdminErrorMailBean" />
+	<bean id="logCleaner" class="com.deepclone.lw.beans.eventlog.LogCleanerBean" />
+	<bean id="logger" class="com.deepclone.lw.beans.eventlog.LoggerBean" />
+	<bean id="logReader" class="com.deepclone.lw.beans.eventlog.LogReaderBean" />
+	<bean id="logWriter" class="com.deepclone.lw.beans.eventlog.LogWriterBean" />
+
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n-beans.xml b/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n-beans.xml
deleted file mode 100644
index bfaaebb..0000000
--- a/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n-beans.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<import resource="i18n/i18n-manager-bean.xml" />
-	<import resource="i18n/i18n-translator-bean.xml" />
-
-</beans>
diff --git a/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-manager-bean.xml b/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-manager-bean.xml
deleted file mode 100644
index a683f48..0000000
--- a/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-manager-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="i18nManager" class="com.deepclone.lw.beans.i18n.I18NManagerBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-translator-bean.xml b/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-translator-bean.xml
deleted file mode 100644
index 9118332..0000000
--- a/legacyworlds-server-beans-i18n/src/main/resources/configuration/i18n/i18n-translator-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-                        
-	<bean id="i18nTranslator" class="com.deepclone.lw.beans.i18n.TranslatorBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml b/legacyworlds-server-beans-i18n/src/main/resources/configuration/meta/i18n.xml
similarity index 65%
rename from legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml
rename to legacyworlds-server-beans-i18n/src/main/resources/configuration/meta/i18n.xml
index 687e57a..dc21c5b 100644
--- a/legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml
+++ b/legacyworlds-server-beans-i18n/src/main/resources/configuration/meta/i18n.xml
@@ -4,6 +4,7 @@
 	xsi:schemaLocation="
                         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
-	<import resource="game/updates.xml" />
+	<bean id="i18nManager" class="com.deepclone.lw.beans.i18n.I18NManagerBean" />
+	<bean id="i18nTranslator" class="com.deepclone.lw.beans.i18n.TranslatorBean" />
 
 </beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer/mailer-bean.xml b/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer/mailer-bean.xml
deleted file mode 100644
index a430dd0..0000000
--- a/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer/mailer-bean.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<!-- Mailer bean - all properties autowired or defaulted -->
-	<bean id="mailer" class="com.deepclone.lw.beans.mailer.MailerBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer-beans.xml b/legacyworlds-server-beans-mailer/src/main/resources/configuration/meta/mailer.xml
similarity index 74%
rename from legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer-beans.xml
rename to legacyworlds-server-beans-mailer/src/main/resources/configuration/meta/mailer.xml
index e5e284f..cceb640 100644
--- a/legacyworlds-server-beans-mailer/src/main/resources/configuration/mailer-beans.xml
+++ b/legacyworlds-server-beans-mailer/src/main/resources/configuration/meta/mailer.xml
@@ -2,9 +2,9 @@
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
-	<import resource="mailer/mailer-bean.xml" />
+	<bean id="mailer" class="com.deepclone.lw.beans.mailer.MailerBean" />
 	<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
 		<property name="host" value="127.0.0.1" />
 	</bean>
diff --git a/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/naming-dao-bean.xml b/legacyworlds-server-beans-naming/src/main/resources/configuration/meta/naming.xml
similarity index 69%
rename from legacyworlds-server-beans-naming/src/main/resources/configuration/naming/naming-dao-bean.xml
rename to legacyworlds-server-beans-naming/src/main/resources/configuration/meta/naming.xml
index 0a3dfe5..f63e0df 100644
--- a/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/naming-dao-bean.xml
+++ b/legacyworlds-server-beans-naming/src/main/resources/configuration/meta/naming.xml
@@ -2,8 +2,9 @@
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
+	<bean id="namesManager" class="com.deepclone.lw.beans.naming.NamesManagerBean" />
 	<bean id="namingDAO" class="com.deepclone.lw.beans.naming.NamingDAOBean" />
 
 </beans>
diff --git a/legacyworlds-server-beans-naming/src/main/resources/configuration/naming-beans.xml b/legacyworlds-server-beans-naming/src/main/resources/configuration/naming-beans.xml
deleted file mode 100644
index f4ab8cc..0000000
--- a/legacyworlds-server-beans-naming/src/main/resources/configuration/naming-beans.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<import resource="naming/names-manager-bean.xml" />
-	<import resource="naming/naming-dao-bean.xml" />
-
-</beans>
diff --git a/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/names-manager-bean.xml b/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/names-manager-bean.xml
deleted file mode 100644
index 060f506..0000000
--- a/legacyworlds-server-beans-naming/src/main/resources/configuration/naming/names-manager-bean.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="namesManager" class="com.deepclone.lw.beans.naming.NamesManagerBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources-beans.xml b/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources.xml
similarity index 100%
rename from legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources-beans.xml
rename to legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources.xml
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/game.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/game.xml
deleted file mode 100644
index 952f3f6..0000000
--- a/legacyworlds-server-beans-simple/src/main/resources/configuration/game.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
-
-	<import resource="game/resources-beans.xml" />
-
-</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple.xml
similarity index 100%
rename from legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
rename to legacyworlds-server-beans-simple/src/main/resources/configuration/simple.xml
diff --git a/legacyworlds-server-beans-system/src/main/resources/configuration/system/session-manager-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/meta/system.xml
similarity index 54%
rename from legacyworlds-server-beans-system/src/main/resources/configuration/system/session-manager-bean.xml
rename to legacyworlds-server-beans-system/src/main/resources/configuration/meta/system.xml
index dcddd03..7728e8d 100644
--- a/legacyworlds-server-beans-system/src/main/resources/configuration/system/session-manager-bean.xml
+++ b/legacyworlds-server-beans-system/src/main/resources/configuration/meta/system.xml
@@ -7,15 +7,23 @@
 	<bean id="authChallengeGenerator" class="com.deepclone.lw.utils.RandomStringGenerator">
 		<qualifier value="authChallenges" />
 		<property name="length" value="100" />
-		<property name="characterSet" value="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" />
+		<property name="characterSet"
+			value="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" />
 	</bean>
 
+	<bean id="constantsManager" class="com.deepclone.lw.beans.sys.ConstantsManagerBean" />
+	<bean id="constantsRegistrar" class="com.deepclone.lw.beans.sys.ConstantsRegistrarBean" />
+
 	<bean id="sessionIDGenerator" class="com.deepclone.lw.utils.RandomStringGenerator">
 		<qualifier value="sessionIdentifiers" />
 		<property name="length" value="50" />
-		<property name="characterSet" value="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" />
+		<property name="characterSet"
+			value="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" />
 	</bean>
 
 	<bean id="sessionManager" class="com.deepclone.lw.beans.sys.SessionManagerBean" />
+	<bean id="systemStatus" class="com.deepclone.lw.beans.sys.SystemStatusBean" />
+	<bean id="ticker" class="com.deepclone.lw.beans.sys.TickerBean" />
+	<bean id="tickerManager" class="com.deepclone.lw.beans.sys.TickerManagerBean" />
 
-</beans>
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-system/src/main/resources/configuration/system-beans.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system-beans.xml
deleted file mode 100644
index 7d0a16e..0000000
--- a/legacyworlds-server-beans-system/src/main/resources/configuration/system-beans.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<import resource="system/constants-manager-bean.xml" />
-	<import resource="system/constants-registrar-bean.xml" />
-	<import resource="system/ticker-bean.xml" />
-	<import resource="system/session-manager-bean.xml" />
-	<import resource="system/system-status-bean.xml" />
-
-</beans>
diff --git a/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-manager-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-manager-bean.xml
deleted file mode 100644
index 3603ae6..0000000
--- a/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-manager-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="constantsManager" class="com.deepclone.lw.beans.sys.ConstantsManagerBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-registrar-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-registrar-bean.xml
deleted file mode 100644
index 3fa577d..0000000
--- a/legacyworlds-server-beans-system/src/main/resources/configuration/system/constants-registrar-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="constantsRegistrar" class="com.deepclone.lw.beans.sys.ConstantsRegistrarBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-system/src/main/resources/configuration/system/system-status-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/system-status-bean.xml
deleted file mode 100644
index f8b97f4..0000000
--- a/legacyworlds-server-beans-system/src/main/resources/configuration/system/system-status-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="systemStatus" class="com.deepclone.lw.beans.sys.SystemStatusBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-system/src/main/resources/configuration/system/ticker-bean.xml b/legacyworlds-server-beans-system/src/main/resources/configuration/system/ticker-bean.xml
deleted file mode 100644
index 894c846..0000000
--- a/legacyworlds-server-beans-system/src/main/resources/configuration/system/ticker-bean.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="tickerManager" class="com.deepclone.lw.beans.sys.TickerManagerBean" />
-	<bean id="ticker" class="com.deepclone.lw.beans.sys.TickerBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-command-wiring-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/meta/sessions.xml
similarity index 56%
rename from legacyworlds-server-beans-user/src/main/resources/configuration/user/session-command-wiring-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/meta/sessions.xml
index da9ba6d..611aa59 100644
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-command-wiring-bean.xml
+++ b/legacyworlds-server-beans-user/src/main/resources/configuration/meta/sessions.xml
@@ -2,8 +2,10 @@
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
+	<bean id="objectNameValidator" class="com.deepclone.lw.beans.user.ObjectNameValidatorBean" />
 	<bean id="sessionCommandWiring" class="com.deepclone.lw.beans.user.abst.SessionCommandWiringBean" />
+	<bean id="sessionSubTypeWiring" class="com.deepclone.lw.beans.user.abst.SessionSubTypeWiringBean" />
 
-</beans>
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-recap-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types.xml
similarity index 65%
rename from legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-recap-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/session-types.xml
index 4d609ab..e52899d 100644
--- a/legacyworlds-server-beans-accounts/src/main/resources/configuration/accounts/admin-recap-bean.xml
+++ b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types.xml
@@ -4,6 +4,8 @@
 	xsi:schemaLocation="
                         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 
-	<bean id="adminRecap" class="com.deepclone.lw.beans.admin.AdminRecapBean" />
+	<import resource="session-types/administration.xml" />
+	<import resource="session-types/external.xml" />
+	<import resource="session-types/player.xml" />
 
-</beans>
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/administration.xml
similarity index 100%
rename from legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/session-types/administration.xml
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/user/external-session-definer-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/external.xml
similarity index 100%
rename from legacyworlds-server-beans-user/src/main/resources/configuration/user/external-session-definer-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/session-types/external.xml
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
similarity index 100%
rename from legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml
rename to legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/user-beans.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user-beans.xml
deleted file mode 100644
index 3c1c824..0000000
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/user-beans.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<import resource="user/admin-session-definer-bean.xml" />
-	<import resource="user/external-session-definer-bean.xml" />
-	<import resource="user/object-name-validator-bean.xml" />
-	<import resource="user/player-session-definer-bean.xml" />
-	<import resource="user/session-command-wiring-bean.xml" />
-	<import resource="user/session-subtype-wiring-bean.xml" />
-
-</beans>
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/user/object-name-validator-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user/object-name-validator-bean.xml
deleted file mode 100644
index 7d06f66..0000000
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/user/object-name-validator-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="objectNameValidator" class="com.deepclone.lw.beans.user.ObjectNameValidatorBean" />
-
-</beans>
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-subtype-wiring-bean.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-subtype-wiring-bean.xml
deleted file mode 100644
index 5713773..0000000
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/user/session-subtype-wiring-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="sessionSubTypeWiring" class="com.deepclone.lw.beans.user.abst.SessionSubTypeWiringBean" />
-
-</beans>
diff --git a/legacyworlds-server-data/src/main/resources/configuration/transaction-bean.xml b/legacyworlds-server-data/src/main/resources/configuration/transaction-bean.xml
deleted file mode 100644
index 6cf7b29..0000000
--- a/legacyworlds-server-data/src/main/resources/configuration/transaction-bean.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xmlns:tx="http://www.springframework.org/schema/tx"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
-                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
-
-	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
-		<property name="dataSource" ref="dataSource" />
-	</bean>
-
-	<tx:annotation-driven transaction-manager="transactionManager"/>
-
-</beans>
diff --git a/legacyworlds-server-main/src/main/resources/configuration/context-configuration.xml b/legacyworlds-server-main/src/main/resources/configuration/context-configuration.xml
deleted file mode 100644
index 11e1a81..0000000
--- a/legacyworlds-server-main/src/main/resources/configuration/context-configuration.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
-	xsi:schemaLocation="http://www.springframework.org/schema/beans 
-           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
-           http://www.springframework.org/schema/context
-           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
-
-	<context:annotation-config />
-
-</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-main/src/main/resources/configuration/game.xml b/legacyworlds-server-main/src/main/resources/configuration/game.xml
new file mode 100644
index 0000000..2702f02
--- /dev/null
+++ b/legacyworlds-server-main/src/main/resources/configuration/game.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+	<!-- ========================================================== -->
+	<!-- Spring configuration loader for all "real" game components -->
+	<!-- ========================================================== -->
+	<import resource="game/resources.xml" />
+	<import resource="game/updates.xml" />
+
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-main/src/main/resources/configuration/meta.xml b/legacyworlds-server-main/src/main/resources/configuration/meta.xml
new file mode 100644
index 0000000..69924f6
--- /dev/null
+++ b/legacyworlds-server-main/src/main/resources/configuration/meta.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+	<!-- ================================================= -->
+	<!-- Spring configuration loader for "meta" components -->
+	<!-- ================================================= -->
+	<import resource="meta/accounts.xml" />
+	<import resource="meta/bt.xml" />
+	<import resource="meta/eventlog.xml" />
+	<import resource="meta/i18n.xml" />
+	<import resource="meta/mailer.xml" />
+	<import resource="meta/naming.xml" />
+	<import resource="meta/sessions.xml" />
+	<import resource="meta/system.xml" />
+
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-main/src/main/resources/configuration/transactions.xml b/legacyworlds-server-main/src/main/resources/configuration/transactions.xml
new file mode 100644
index 0000000..3e20eb9
--- /dev/null
+++ b/legacyworlds-server-main/src/main/resources/configuration/transactions.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
+                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
+
+	<bean id="transactionManager"
+		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
+		<property name="dataSource" ref="dataSource" />
+	</bean>
+
+	<tx:annotation-driven transaction-manager="transactionManager" />
+
+</beans>
diff --git a/legacyworlds-server-main/src/main/resources/lw-server.xml b/legacyworlds-server-main/src/main/resources/lw-server.xml
index 081b882..07a0a74 100644
--- a/legacyworlds-server-main/src/main/resources/lw-server.xml
+++ b/legacyworlds-server-main/src/main/resources/lw-server.xml
@@ -2,30 +2,31 @@
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
-                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
+                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
 
+	<!-- ================================================== -->
+	<!-- Legacy Worlds server - Spring configuration loader -->
+	<!-- ================================================== -->
+
+	<!-- Use annotations to determine component wiring -->
 	<context:annotation-config />
 
-	<!-- Server terminator bean -->
+	<!-- Transaction manager -->
+	<import resource="configuration/transactions.xml" />
+
+	<!-- Server terminator -->
 	<bean id="terminator" class="com.deepclone.lw.srv.ServerTerminatorBean" />
 
-	<!-- Load transaction manager -->
-	<import resource="configuration/transaction-bean.xml" />
+	<!-- "Meta" components -->
+	<import resource="configuration/meta.xml" />
 
-	<!-- Load all system and low-level beans -->
-	<import resource="configuration/accounts-beans.xml" />
-	<import resource="configuration/bt-beans.xml" />
-	<import resource="configuration/eventlog-beans.xml" />
-	<import resource="configuration/i18n-beans.xml" />
-	<import resource="configuration/mailer-beans.xml" />
-	<import resource="configuration/naming-beans.xml" />
-	<import resource="configuration/system-beans.xml" />
-	<import resource="configuration/user-beans.xml" />
+	<!-- Session types and command handlers -->
+	<import resource="configuration/session-types.xml" />
 
-	<!-- Load both the "final" component configuration file and the "simple" 
-		implementation from B6M1. -->
+	<!-- Load both the "final" game file and the "simple" implementation from 
+		B6M1. -->
 	<import resource="configuration/game.xml" />
-	<import resource="configuration/simple-beans.xml" />
+	<import resource="configuration/simple.xml" />
 
 </beans>
diff --git a/legacyworlds/doc/spring-configuration.txt b/legacyworlds/doc/spring-configuration.txt
new file mode 100644
index 0000000..2eda413
--- /dev/null
+++ b/legacyworlds/doc/spring-configuration.txt
@@ -0,0 +1,77 @@
+Spring configuration files
+===========================
+
+A lot of the game's code (whether on the server side or on the web clients)
+relies on the Spring framework. For most of the components, it is easier to
+use XML configuration files rather than in-code configuration. This file
+describes the common elements and guidelines to follow for the configuration
+files.
+
+
+Files in the main server package:
+
+	lw-server.xml					Main configuration file
+	
+		This file contains the server's top-level configuration. It enables
+		annotation-based component processing, adds the server termination
+		component, and loads the configuration files for all elements of the
+		server: transaction management, meta components, game components for
+		both the "real" game and the "simple" implementation written for
+		Milestone 1, session type definitions and commands.
+
+	configuration/game.xml			"Real" game components
+
+		This file acts as a loader for all configuration files the load
+		elements of the "real" game - that is, parts of the game which have
+		taken (or are taking) the form they should have in the final version.
+
+	configuration/meta.xml			Meta components
+
+		This file loads the configuration files for all "meta" components.
+		A "meta" component is any component which is not directly related to
+		the game itself, but it still necessary: for example account
+		management or the bug tracking system fall into this category.
+
+	configuration/transactions.xml	Transaction manager configuration
+
+		This file contains the configuration for Spring's transaction
+		management system. It defines the transaction manager and enables
+		annotation-based transaction processing.
+
+
+Files in general component definition packages:
+
+	Most of the component definition packages will include one single
+	configuration file which loads the components the package defines. This
+	file will be found in either configuration/meta/ or configuration/game/
+	depending on the purpose of the components. The file in question will
+	be loaded from the corresponding file in the main server package.
+
+
+Files in the legacyworlds-server-beans-simple package:
+
+	This package includes its own top-level configuration file, unsurprisingly
+	named configuration/simple.xml, which is included in the server's top-level
+	configuration and loads all files in the configuration/simple/
+	sub-directory. Since it corresponds to Milestone 1's "simple" game
+	implementation, it should only be modified to remove components as they
+	are moved or replaced.
+
+
+Files in the legacyworlds-server-beans-user package:
+
+	This package includes a "meta" components configuration file, which loads
+	low-level session management components. In addition, it features the
+	following configuration files:
+	
+		configuration/session-types.xml		Session types loader
+
+			This file loads the configuration files for each specific type of
+			session.
+
+		configuration/session-types/		Session types definitions
+
+			This directory contains configuration files which loads all
+			components for some type of session. Each file should therefore
+			load at least one session definer component, and a bunch of
+			command handling components.
\ No newline at end of file

From b4903d78e4b79f04f56e1e68474bce7c306d138d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 4 Feb 2012 15:26:53 +0100
Subject: [PATCH 41/94] Object name validator component

* Moved the component from the -user package to the -naming package

* Added a separate interface to the component
---
 .../beans/naming/ObjectNameValidatorBean.java | 136 ++++++++++
 .../resources/configuration/meta/naming.xml   |   1 +
 .../beans/user/ObjectNameValidatorBean.java   |  77 ------
 .../AddAdministratorCommandDelegateBean.java  |   8 +-
 .../player/ValidationCommandDelegateBean.java |   6 +-
 .../game/GetNewPlanetCommandDelegateBean.java |   6 +-
 .../CreateAllianceCommandDelegateBean.java    |  10 +-
 .../RenameFleetsCommandDelegateBean.java      |   8 +-
 .../fleets/SplitFleetCommandDelegateBean.java |   8 +-
 .../RenamePlanetCommandDelegateBean.java      |   6 +-
 .../resources/configuration/meta/sessions.xml |   1 -
 .../naming/ObjectNameValidator.java           |  55 ++++
 .../naming/TestObjectNameValidatorBean.java   | 249 ++++++++++++++++++
 .../com/deepclone/lw/cmd/ObjectNameError.java |  16 ++
 14 files changed, 483 insertions(+), 104 deletions(-)
 create mode 100644 legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/ObjectNameValidatorBean.java
 delete mode 100644 legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/ObjectNameValidator.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/naming/TestObjectNameValidatorBean.java

diff --git a/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/ObjectNameValidatorBean.java b/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/ObjectNameValidatorBean.java
new file mode 100644
index 0000000..4f3b83f
--- /dev/null
+++ b/legacyworlds-server-beans-naming/src/main/java/com/deepclone/lw/beans/naming/ObjectNameValidatorBean.java
@@ -0,0 +1,136 @@
+package com.deepclone.lw.beans.naming;
+
+
+import java.util.regex.Pattern;
+
+import com.deepclone.lw.cmd.ObjectNameError;
+import com.deepclone.lw.interfaces.naming.ObjectNameValidator;
+
+
+
+/**
+ * Object name validation component
+ * 
+ * <p>
+ * This component implements the simple name "pre-validation" component, used before an empire or
+ * map name is sent to the database.
+ * 
+ * <p>
+ * By default, it will accept names that are between 2 and 20 characters. That may be modified in
+ * the component's initialisation.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class ObjectNameValidatorBean
+		implements ObjectNameValidator
+{
+	/** Default minimal length */
+	public static final int MIN_LENGTH = 2;
+
+	/** Default maximal length */
+	public static final int MAX_LENGTH = 20;
+
+	/**
+	 * Invalid patterns
+	 * 
+	 * <p>
+	 * This constant lists patterns which must be rejected systematically.
+	 */
+	private static final Pattern fail[] = {
+			Pattern.compile( "\\s\\s+" ) , Pattern.compile( "[^A-Za-z0-9 _\\'\\!\\:\\,\\-\\.\\*@\\[\\]\\{\\}]" )
+	};
+
+	/**
+	 * Required patterns
+	 * 
+	 * <p>
+	 * This constant lists patters which must be present for a name to be considered valid.
+	 */
+	private static final Pattern needed[] = {
+		Pattern.compile( "[A-Za-z]" )
+	};
+
+	/** Minimal length of a name */
+	private int minLength = MIN_LENGTH;
+
+	/** Maximal length of a name */
+	private int maxLength = MAX_LENGTH;
+
+
+	/**
+	 * Set the minimal length of a name
+	 * 
+	 * @param length
+	 *            the new minimal length
+	 */
+	public void setMinLength( Integer length )
+	{
+		this.minLength = length;
+	}
+
+
+	/**
+	 * Set the maximal length of a name
+	 * 
+	 * @param length
+	 *            the new maximal length
+	 */
+	public void setMaxLength( Integer length )
+	{
+		this.maxLength = length;
+	}
+
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.deepclone.lw.beans.user.ObjectNameValidator#validate(java.lang.String)
+	 */
+	@Override
+	public ObjectNameError validate( String name )
+	{
+		return this.validate( name , this.minLength , this.maxLength );
+	}
+
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.deepclone.lw.beans.user.ObjectNameValidator#customValidate(java.lang.String, int,
+	 * int)
+	 */
+	@Override
+	public ObjectNameError validate( String name , int minLength , int maxLength )
+	{
+		if ( "".equals( name.trim( ) ) ) {
+			return ObjectNameError.EMPTY;
+		}
+
+		// No leading or trailing spaces
+		if ( !name.equals( name.trim( ) ) ) {
+			return ObjectNameError.INVALID;
+		}
+
+		// Check length
+		int length = name.length( );
+		if ( length < minLength || length > maxLength ) {
+			return ObjectNameError.INVALID;
+		}
+
+		// Check bad patterns
+		for ( Pattern p : ObjectNameValidatorBean.fail ) {
+			if ( p.matcher( name ).find( ) ) {
+				return ObjectNameError.INVALID;
+			}
+		}
+
+		// Check good patterns
+		for ( Pattern p : ObjectNameValidatorBean.needed ) {
+			if ( !p.matcher( name ).find( ) ) {
+				return ObjectNameError.INVALID;
+			}
+		}
+
+		return null;
+	}
+}
diff --git a/legacyworlds-server-beans-naming/src/main/resources/configuration/meta/naming.xml b/legacyworlds-server-beans-naming/src/main/resources/configuration/meta/naming.xml
index f63e0df..4750147 100644
--- a/legacyworlds-server-beans-naming/src/main/resources/configuration/meta/naming.xml
+++ b/legacyworlds-server-beans-naming/src/main/resources/configuration/meta/naming.xml
@@ -4,6 +4,7 @@
 	xsi:schemaLocation="
                         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
+	<bean id="objectNameValidator" class="com.deepclone.lw.beans.naming.ObjectNameValidatorBean" />
 	<bean id="namesManager" class="com.deepclone.lw.beans.naming.NamesManagerBean" />
 	<bean id="namingDAO" class="com.deepclone.lw.beans.naming.NamingDAOBean" />
 
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java
deleted file mode 100644
index cd3bcff..0000000
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/ObjectNameValidatorBean.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.deepclone.lw.beans.user;
-
-
-import java.util.regex.Pattern;
-
-import com.deepclone.lw.cmd.ObjectNameError;
-
-
-
-public class ObjectNameValidatorBean
-{
-
-	private int minLength = 2;
-	private int maxLength = 20;
-
-	private static Pattern fail[] = {
-			Pattern.compile( "\\s\\s+" ) ,
-			Pattern.compile( "[^A-Za-z0-9 _\\'\\!\\:\\,\\-\\.\\*@\\[\\]\\{\\}]" )
-	};
-
-	private static Pattern needed[] = {
-		Pattern.compile( "[A-Za-z]" )
-	};
-
-
-	public void setMinLength( Integer v )
-	{
-		this.minLength = v;
-	}
-
-
-	public void setMaxLength( Integer v )
-	{
-		this.maxLength = v;
-	}
-
-
-	public ObjectNameError validate( String name )
-	{
-		return this.customValidate( name , this.minLength , this.maxLength );
-	}
-
-
-	public ObjectNameError customValidate( String name , int minLength , int maxLength )
-	{
-		if ( "".equals( name.trim( ) ) ) {
-			return ObjectNameError.EMPTY;
-		}
-
-		// No leading or trailing spaces
-		if ( !name.equals( name.trim( ) ) ) {
-			return ObjectNameError.INVALID;
-		}
-
-		// Check length
-		int length = name.length( );
-		if ( length < minLength || length > maxLength ) {
-			return ObjectNameError.INVALID;
-		}
-
-		// Check bad patterns
-		for ( Pattern p : ObjectNameValidatorBean.fail ) {
-			if ( p.matcher( name ).find( ) ) {
-				return ObjectNameError.INVALID;
-			}
-		}
-
-		// Check good patterns
-		for ( Pattern p : ObjectNameValidatorBean.needed ) {
-			if ( !p.matcher( name ).find( ) ) {
-				return ObjectNameError.INVALID;
-			}
-		}
-
-		return null;
-	}
-}
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java
index 8be8246..ee5aac1 100644
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/su/AddAdministratorCommandDelegateBean.java
@@ -3,7 +3,6 @@ package com.deepclone.lw.beans.user.admin.main.su;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
-import com.deepclone.lw.beans.user.ObjectNameValidatorBean;
 import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.admin.adata.Privileges;
@@ -11,6 +10,7 @@ import com.deepclone.lw.cmd.admin.su.AddAdministratorCommand;
 import com.deepclone.lw.cmd.admin.su.AddAdministratorResponse;
 import com.deepclone.lw.cmd.admin.su.AddAdministratorResponse.AddressError;
 import com.deepclone.lw.interfaces.admin.Administration;
+import com.deepclone.lw.interfaces.naming.ObjectNameValidator;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.CommandResponse;
 import com.deepclone.lw.utils.EmailAddress;
@@ -22,7 +22,7 @@ public class AddAdministratorCommandDelegateBean
 {
 
 	private Administration administration;
-	private ObjectNameValidatorBean validator;
+	private ObjectNameValidator validator;
 
 
 	@Autowired( required = true )
@@ -33,7 +33,7 @@ public class AddAdministratorCommandDelegateBean
 
 
 	@Autowired( required = true )
-	public void setValidator( ObjectNameValidatorBean validator )
+	public void setValidator( ObjectNameValidator validator )
 	{
 		this.validator = validator;
 	}
@@ -57,7 +57,7 @@ public class AddAdministratorCommandDelegateBean
 		if ( "".equals( name ) ) {
 			nameError = ObjectNameError.EMPTY;
 		} else {
-			nameError = this.validator.customValidate( name , 2 , 64 );
+			nameError = this.validator.validate( name , 2 , 64 );
 		}
 
 		// Check address
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java
index 6633bb8..7235287 100644
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/ValidationCommandDelegateBean.java
@@ -3,13 +3,13 @@ package com.deepclone.lw.beans.user.player;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
-import com.deepclone.lw.beans.user.ObjectNameValidatorBean;
 import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
 import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
 import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.player.account.AccountValidationCommand;
 import com.deepclone.lw.cmd.player.account.AccountValidationResponse;
 import com.deepclone.lw.interfaces.acm.AccountManagement;
+import com.deepclone.lw.interfaces.naming.ObjectNameValidator;
 import com.deepclone.lw.interfaces.session.ServerSession;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.CommandResponse;
@@ -23,7 +23,7 @@ public class ValidationCommandDelegateBean
 {
 
 	private AccountManagement manager;
-	private ObjectNameValidatorBean validator;
+	private ObjectNameValidator validator;
 
 
 	@Autowired( required = true )
@@ -34,7 +34,7 @@ public class ValidationCommandDelegateBean
 
 
 	@Autowired( required = true )
-	public void setObjectNameValidator( ObjectNameValidatorBean validator )
+	public void setObjectNameValidator( ObjectNameValidator validator )
 	{
 		this.validator = validator;
 	}
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java
index e59bb96..9b0add8 100644
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/GetNewPlanetCommandDelegateBean.java
@@ -3,7 +3,6 @@ package com.deepclone.lw.beans.user.player.game;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
-import com.deepclone.lw.beans.user.ObjectNameValidatorBean;
 import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
 import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
 import com.deepclone.lw.beans.user.player.GameSubTypeBean;
@@ -11,6 +10,7 @@ import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.player.GetNewPlanetCommand;
 import com.deepclone.lw.cmd.player.GetNewPlanetResponse;
 import com.deepclone.lw.interfaces.game.EmpireManagement;
+import com.deepclone.lw.interfaces.naming.ObjectNameValidator;
 import com.deepclone.lw.interfaces.session.ServerSession;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.CommandResponse;
@@ -23,7 +23,7 @@ public class GetNewPlanetCommandDelegateBean
 {
 
 	private EmpireManagement empireManagement;
-	private ObjectNameValidatorBean validator;
+	private ObjectNameValidator validator;
 
 
 	@Autowired( required = true )
@@ -34,7 +34,7 @@ public class GetNewPlanetCommandDelegateBean
 
 
 	@Autowired( required = true )
-	public void setObjectNameValidator( ObjectNameValidatorBean validator )
+	public void setObjectNameValidator( ObjectNameValidator validator )
 	{
 		this.validator = validator;
 	}
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java
index 607054a..fbaa298 100644
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/alliances/CreateAllianceCommandDelegateBean.java
@@ -3,7 +3,6 @@ package com.deepclone.lw.beans.user.player.game.alliances;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
-import com.deepclone.lw.beans.user.ObjectNameValidatorBean;
 import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
 import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
 import com.deepclone.lw.beans.user.player.GameSubTypeBean;
@@ -13,6 +12,7 @@ import com.deepclone.lw.cmd.player.alliances.CreateAllianceCommand;
 import com.deepclone.lw.cmd.player.alliances.CreateAllianceResponse;
 import com.deepclone.lw.cmd.player.gdata.alliance.AllianceCreationStatus;
 import com.deepclone.lw.interfaces.game.AllianceManagement;
+import com.deepclone.lw.interfaces.naming.ObjectNameValidator;
 import com.deepclone.lw.interfaces.session.ServerSession;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.CommandResponse;
@@ -24,7 +24,7 @@ public class CreateAllianceCommandDelegateBean
 {
 
 	private AllianceManagement allianceManagement;
-	private ObjectNameValidatorBean validator;
+	private ObjectNameValidator validator;
 
 
 	@Autowired( required = true )
@@ -35,7 +35,7 @@ public class CreateAllianceCommandDelegateBean
 
 
 	@Autowired( required = true )
-	public void setObjectNameValidator( ObjectNameValidatorBean validator )
+	public void setObjectNameValidator( ObjectNameValidator validator )
 	{
 		this.validator = validator;
 	}
@@ -65,10 +65,10 @@ public class CreateAllianceCommandDelegateBean
 		}
 
 		String tag = command.getTag( );
-		ObjectNameError tagError = this.validator.customValidate( tag , 2 , 5 );
+		ObjectNameError tagError = this.validator.validate( tag , 2 , 5 );
 
 		String name = command.getName( );
-		ObjectNameError nameError = this.validator.customValidate( name , 5 , 64 );
+		ObjectNameError nameError = this.validator.validate( name , 5 , 64 );
 
 		if ( tagError != null || nameError != null ) {
 			AllianceStatusResponse r = this.allianceManagement.getView( empireId );
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java
index 240c345..3f57dc8 100644
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/RenameFleetsCommandDelegateBean.java
@@ -3,13 +3,13 @@ package com.deepclone.lw.beans.user.player.game.fleets;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
-import com.deepclone.lw.beans.user.ObjectNameValidatorBean;
 import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
 import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
 import com.deepclone.lw.beans.user.player.GameSubTypeBean;
 import com.deepclone.lw.cmd.player.fleets.RenameFleetsCommand;
 import com.deepclone.lw.cmd.player.fleets.RenameFleetsResponse;
 import com.deepclone.lw.interfaces.game.FleetManagement;
+import com.deepclone.lw.interfaces.naming.ObjectNameValidator;
 import com.deepclone.lw.interfaces.session.ServerSession;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.CommandResponse;
@@ -20,7 +20,7 @@ public class RenameFleetsCommandDelegateBean
 		implements AutowiredCommandDelegate
 {
 	private FleetManagement fleetManager;
-	private ObjectNameValidatorBean validator;
+	private ObjectNameValidator validator;
 
 
 	@Autowired( required = true )
@@ -31,7 +31,7 @@ public class RenameFleetsCommandDelegateBean
 
 
 	@Autowired( required = true )
-	public void setObjectNameValidator( ObjectNameValidatorBean validator )
+	public void setObjectNameValidator( ObjectNameValidator validator )
 	{
 		this.validator = validator;
 	}
@@ -64,7 +64,7 @@ public class RenameFleetsCommandDelegateBean
 		boolean error;
 		if ( command.isRename( ) ) {
 			name = command.getName( ).trim( );
-			if ( !"".equals( name ) && this.validator.customValidate( name , 1 , 40 ) != null ) {
+			if ( !"".equals( name ) && this.validator.validate( name , 1 , 40 ) != null ) {
 				error = true;
 				name = null;
 			} else {
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java
index 3d77684..47e1a17 100644
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/fleets/SplitFleetCommandDelegateBean.java
@@ -3,13 +3,13 @@ package com.deepclone.lw.beans.user.player.game.fleets;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
-import com.deepclone.lw.beans.user.ObjectNameValidatorBean;
 import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
 import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
 import com.deepclone.lw.beans.user.player.GameSubTypeBean;
 import com.deepclone.lw.cmd.player.fleets.SplitFleetCommand;
 import com.deepclone.lw.cmd.player.fleets.SplitFleetResponse;
 import com.deepclone.lw.interfaces.game.FleetManagement;
+import com.deepclone.lw.interfaces.naming.ObjectNameValidator;
 import com.deepclone.lw.interfaces.session.ServerSession;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.CommandResponse;
@@ -20,7 +20,7 @@ public class SplitFleetCommandDelegateBean
 		implements AutowiredCommandDelegate
 {
 	private FleetManagement fleetManager;
-	private ObjectNameValidatorBean validator;
+	private ObjectNameValidator validator;
 
 
 	@Autowired( required = true )
@@ -31,7 +31,7 @@ public class SplitFleetCommandDelegateBean
 
 
 	@Autowired( required = true )
-	public void setObjectNameValidator( ObjectNameValidatorBean validator )
+	public void setObjectNameValidator( ObjectNameValidator validator )
 	{
 		this.validator = validator;
 	}
@@ -64,7 +64,7 @@ public class SplitFleetCommandDelegateBean
 		boolean error;
 		if ( name != null ) {
 			name = command.getName( ).trim( );
-			if ( !"".equals( name ) && this.validator.customValidate( name , 1 , 40 ) != null ) {
+			if ( !"".equals( name ) && this.validator.validate( name , 1 , 40 ) != null ) {
 				error = true;
 				name = "";
 			} else {
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java
index f006bee..73b07c3 100644
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/RenamePlanetCommandDelegateBean.java
@@ -3,7 +3,6 @@ package com.deepclone.lw.beans.user.player.game.planets;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
-import com.deepclone.lw.beans.user.ObjectNameValidatorBean;
 import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
 import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
 import com.deepclone.lw.beans.user.player.GameSubTypeBean;
@@ -12,6 +11,7 @@ import com.deepclone.lw.cmd.player.planets.RenamePlanetCommand;
 import com.deepclone.lw.cmd.player.planets.RenamePlanetResponse;
 import com.deepclone.lw.cmd.player.planets.ViewPlanetResponse;
 import com.deepclone.lw.interfaces.game.PlanetsManagement;
+import com.deepclone.lw.interfaces.naming.ObjectNameValidator;
 import com.deepclone.lw.interfaces.session.ServerSession;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.CommandResponse;
@@ -24,7 +24,7 @@ public class RenamePlanetCommandDelegateBean
 {
 
 	private PlanetsManagement planetsManagement;
-	private ObjectNameValidatorBean validator;
+	private ObjectNameValidator validator;
 
 
 	@Autowired( required = true )
@@ -35,7 +35,7 @@ public class RenamePlanetCommandDelegateBean
 
 
 	@Autowired( required = true )
-	public void setObjectNameValidator( ObjectNameValidatorBean validator )
+	public void setObjectNameValidator( ObjectNameValidator validator )
 	{
 		this.validator = validator;
 	}
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/meta/sessions.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/meta/sessions.xml
index 611aa59..b676198 100644
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/meta/sessions.xml
+++ b/legacyworlds-server-beans-user/src/main/resources/configuration/meta/sessions.xml
@@ -4,7 +4,6 @@
 	xsi:schemaLocation="
                         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
-	<bean id="objectNameValidator" class="com.deepclone.lw.beans.user.ObjectNameValidatorBean" />
 	<bean id="sessionCommandWiring" class="com.deepclone.lw.beans.user.abst.SessionCommandWiringBean" />
 	<bean id="sessionSubTypeWiring" class="com.deepclone.lw.beans.user.abst.SessionSubTypeWiringBean" />
 
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/ObjectNameValidator.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/ObjectNameValidator.java
new file mode 100644
index 0000000..12b88de
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/naming/ObjectNameValidator.java
@@ -0,0 +1,55 @@
+package com.deepclone.lw.interfaces.naming;
+
+
+import com.deepclone.lw.cmd.ObjectNameError;
+
+
+
+/**
+ * Object name validator
+ * 
+ * <p>
+ * This interface corresponds to a component which can be used to "pre-validate" the names of
+ * objects (empires, map objects, fleets, etc...). It does not check for banned or duplicate names.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public interface ObjectNameValidator
+{
+
+	/**
+	 * Validate a name using the component's defaults
+	 * 
+	 * <p>
+	 * Check the name's validity. The name must match the usual naming constraints (valid
+	 * characters, no sequences of white space, name starts with an alphabetic character) and its
+	 * length is checked against the component's default constraints.
+	 * 
+	 * @param name
+	 *            the name to validate
+	 * 
+	 * @return the validation error if one occurred, or <code>null</code> if the name is valid
+	 */
+	public ObjectNameError validate( String name );
+
+
+	/**
+	 * Validate a name using specific length constraints
+	 * 
+	 * <p>
+	 * Check the name's validity. The name must match the usual naming constraints (valid
+	 * characters, no sequences of white space, name starts with an alphabetic character) and its
+	 * length is checked against the specific constraints given as arguments.
+	 * 
+	 * @param name
+	 *            the name to validate
+	 * @param minLength
+	 *            the minimal accepted length
+	 * @param maxLength
+	 *            the maximal accepted length
+	 * 
+	 * @return the validation error if one occurred, or <code>null</code> if the name is valid
+	 */
+	public ObjectNameError validate( String name , int minLength , int maxLength );
+
+}
\ No newline at end of file
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/naming/TestObjectNameValidatorBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/naming/TestObjectNameValidatorBean.java
new file mode 100644
index 0000000..1e9d197
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/naming/TestObjectNameValidatorBean.java
@@ -0,0 +1,249 @@
+package com.deepclone.lw.beans.naming;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cmd.ObjectNameError;
+
+
+
+/**
+ * Tests for {@link ObjectNameValidatorBean}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class TestObjectNameValidatorBean
+{
+
+	/** Object name validator being tested */
+	private ObjectNameValidatorBean validator;
+
+
+	/**
+	 * Create the validator to run tests on
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.validator = new ObjectNameValidatorBean( );
+	}
+
+
+	/**
+	 * A string shorter than the default minimal length will cause validation to fail with the
+	 * {@link ObjectNameError#INVALID} error on an unmodified validator.
+	 */
+	@Test
+	public void testDefaultMinimalLength( )
+	{
+		String test = "";
+		for ( int i = 0 ; i < ObjectNameValidatorBean.MIN_LENGTH - 1 ; i++ ) {
+			test += "x";
+		}
+
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * A string longer than the default maximal length will cause validation to fail with the
+	 * {@link ObjectNameError#INVALID} error on an unmodified validator.
+	 */
+	@Test
+	public void testDefaultMaximalLength( )
+	{
+		String test = "";
+		for ( int i = 0 ; i < ObjectNameValidatorBean.MAX_LENGTH + 1 ; i++ ) {
+			test += "x";
+		}
+
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * A string shorter than a modified minimal length will cause validation to fail with the
+	 * {@link ObjectNameError#INVALID} error.
+	 */
+	@Test
+	public void testMinimalLength( )
+	{
+		String test = "";
+		for ( int i = 0 ; i < 3 ; i++ ) {
+			test += "x";
+		}
+
+		this.validator.setMinLength( 4 );
+		this.validator.setMaxLength( 10 );
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * A string longer than a modified maximal length will cause validation to fail with the
+	 * {@link ObjectNameError#INVALID} error on an unmodified validator.
+	 */
+	@Test
+	public void testMaximalLength( )
+	{
+		String test = "";
+		for ( int i = 0 ; i < 11 ; i++ ) {
+			test += "x";
+		}
+
+		this.validator.setMinLength( 4 );
+		this.validator.setMaxLength( 10 );
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * A string that's empty will cause an {@link ObjectNameError#EMPTY} error.
+	 */
+	@Test
+	public void testEmptyString( )
+	{
+		String test = "";
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.EMPTY , error );
+	}
+
+
+	/**
+	 * A string that's full of spaces will cause an {@link ObjectNameError#EMPTY} error.
+	 */
+	@Test
+	public void testSpacesOnlyString( )
+	{
+		String test = "       ";
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.EMPTY , error );
+	}
+
+
+	/**
+	 * An otherwise valid string that starts with spaces will cause an
+	 * {@link ObjectNameError#INVALID} error.
+	 */
+	@Test
+	public void testStringStartsWithSpace( )
+	{
+		String test = " abcde";
+		this.validator.setMinLength( 1 );
+		this.validator.setMaxLength( 10 );
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * An otherwise valid string that ends with spaces will cause an {@link ObjectNameError#INVALID}
+	 * error.
+	 */
+	@Test
+	public void testStringEndsWithSpace( )
+	{
+		String test = "abcde ";
+		this.validator.setMinLength( 1 );
+		this.validator.setMaxLength( 10 );
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * An otherwise valid string that contains a sequence of two spaces will cause an
+	 * {@link ObjectNameError#INVALID} error.
+	 */
+	@Test
+	public void testStringSpaceSequence( )
+	{
+		String test = "ab  cde";
+		this.validator.setMinLength( 1 );
+		this.validator.setMaxLength( 10 );
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * A string that contains an unsupported character will cause an {@link ObjectNameError#INVALID}
+	 * error.
+	 */
+	@Test
+	public void testStringInvalidCharacter( )
+	{
+		String test = "abcdœ";
+		this.validator.setMinLength( 1 );
+		this.validator.setMaxLength( 10 );
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * A string that does not contain any letter (but is otherwise valid) will cause an
+	 * {@link ObjectNameError#INVALID} error.
+	 */
+	@Test
+	public void testStringNoLetters( )
+	{
+		String test = "@_@12";
+		this.validator.setMinLength( 1 );
+		this.validator.setMaxLength( 10 );
+		ObjectNameError error = this.validator.validate( test );
+		assertNotNull( error );
+		assertEquals( ObjectNameError.INVALID , error );
+	}
+
+
+	/**
+	 * Test validation on a "good" string
+	 */
+	@Test
+	public void testValidString( )
+	{
+		this.validator.setMinLength( 1 );
+		this.validator.setMaxLength( 5 );
+
+		ObjectNameError error = this.validator.validate( "ab12@" );
+		assertNull( error );
+	}
+
+
+
+	/**
+	 * Test validation with call-specific length constraints
+	 */
+	@Test
+	public void testLengthConstraints( )
+	{
+		this.validator.setMinLength( 5 );
+		this.validator.setMaxLength( 6 );
+
+		ObjectNameError error = this.validator.validate( "ab" , 2 , 10 );
+		assertNull( error );
+
+		error = this.validator.validate( "a012345678" , 2 , 10 );
+		assertNull( error );
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ObjectNameError.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ObjectNameError.java
index 650e8fb..493c435 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ObjectNameError.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ObjectNameError.java
@@ -1,11 +1,27 @@
 package com.deepclone.lw.cmd;
 
 
+/**
+ * The results of a name validation operation
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
 public enum ObjectNameError {
 
+	/** The name is empty */
 	EMPTY ,
+
+	/** The name is used by another object (e.g. duplicate empire name) */
 	UNAVAILABLE ,
+
+	/** The name has been banned from being used again */
 	BANNED ,
+
+	/**
+	 * The name is invalid (too short, too long, sequences of white space, invalid characters, does
+	 * not start with a letter)
+	 */
 	INVALID
 
 }

From 92dd01ffce8cbdf9b1539b5b9e8f9a5ec7363f32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 4 Feb 2012 15:32:20 +0100
Subject: [PATCH 42/94] Fixed project directories

* Because some resources were moved in a previous commit, Git decided to
automatically remove a directory. Re-added that directory with a hidden
file to prevent Git from being such a moron.
---
 legacyworlds-server-data/src/main/resources/.empty | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 legacyworlds-server-data/src/main/resources/.empty

diff --git a/legacyworlds-server-data/src/main/resources/.empty b/legacyworlds-server-data/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29

From d38576a5cf781cf3165035fa6620668243ed5aa7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sun, 5 Feb 2012 10:10:43 +0100
Subject: [PATCH 43/94] Empire mining settings

* Modified mining settings stored procedures to use text identifiers
instead of numeric identifiers

* Added DAO for mining settings and controller for resource operations

* Added UpdateEmpireMiningSettingsCommand and associated command
delegate. The command always returns NullResponse.

* Overview page templates split into multiple files for clarity, added
priority update form to the empire economy view and associated web
server handler
---
 .../game/resources/MiningSettingsDAOBean.java |  83 ++++++++++++
 .../resources/ResourcesControllerBean.java    |  68 ++++++++++
 .../configuration/game/resources.xml          |   2 +
 ...pireMiningSettingsCommandDelegateBean.java |  74 +++++++++++
 .../configuration/session-types/player.xml    |   1 +
 .../parts/040-functions/045-empire-mining.sql |  34 +++--
 .../010-mset-update-start.sql                 |   2 +-
 .../015-mset-update-start-planet.sql          |  10 +-
 .../045-empire-mining/020-mset-update-set.sql |  22 ++--
 .../030-mset-update-apply.sql                 |  61 ++++++---
 .../045-empire-mining/020-mset-update-set.sql |   2 +-
 .../game/resources/MiningSettingsDAO.java     |  60 +++++++++
 .../game/resources/ResourcesController.java   |  30 +++++
 .../game/resources/MockMiningSettingsDAO.java | 121 +++++++++++++++++
 .../TestResourcesControllerBean.java          | 121 +++++++++++++++++
 .../UpdateEmpireMiningSettingsCommand.java    |  50 +++++++
 .../deepclone/lw/web/csess/PlayerSession.java |  13 ++
 .../Raw/WEB-INF/fm/en/types/overview.ftl      |  56 +-------
 .../fm/en/types/overview/resources.ftl        |  64 +++++++++
 .../Raw/WEB-INF/fm/fr/types/overview.ftl      |  56 +-------
 .../fm/fr/types/overview/resources.ftl        |  64 +++++++++
 .../Content/Raw/WEB-INF/fm/layout/lists.ftl   |   6 +-
 .../lw/web/main/game/OverviewPage.java        | 123 ++++++++++++++++++
 legacyworlds/dev-tools/run-database-tests.sh  |  61 +++++++++
 24 files changed, 1024 insertions(+), 160 deletions(-)
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/MiningSettingsDAOBean.java
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesControllerBean.java
 create mode 100644 legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/UpdateEmpireMiningSettingsCommandDelegateBean.java
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/MiningSettingsDAO.java
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesController.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/MockMiningSettingsDAO.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestResourcesControllerBean.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/UpdateEmpireMiningSettingsCommand.java
 create mode 100644 legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview/resources.ftl
 create mode 100644 legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview/resources.ftl
 create mode 100755 legacyworlds/dev-tools/run-database-tests.sh

diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/MiningSettingsDAOBean.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/MiningSettingsDAOBean.java
new file mode 100644
index 0000000..57047b9
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/MiningSettingsDAOBean.java
@@ -0,0 +1,83 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import java.sql.Types;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.interfaces.game.resources.MiningSettingsDAO;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Data access component for mining settings
+ * 
+ * <p>
+ * This component implements the methods which allow mining settings to be updated by calling the
+ * appropriate stored procedures.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class MiningSettingsDAOBean
+		implements MiningSettingsDAO
+{
+	/** <code>emp.mset_update_start(INT)</code> stored procedure */
+	private StoredProc pStartEmpireUpdate;
+
+	/** <code>emp.mset_update_set(TEXT,INT)</code> stored procedure */
+	private StoredProc pSetMiningPriority;
+
+	/** <code>emp.mset_update_apply()</code> stored procedure */
+	private StoredProc pApplyUpdate;
+
+
+	/**
+	 * Dependency injector that sets the data source
+	 * 
+	 * @param dataSource
+	 *            the data source
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.pStartEmpireUpdate = new StoredProc( dataSource , "emp" , "mset_update_start" );
+		this.pStartEmpireUpdate.addParameter( "_empire" , Types.INTEGER );
+		this.pStartEmpireUpdate.addOutput( "_success" , Types.BOOLEAN );
+
+		this.pSetMiningPriority = new StoredProc( dataSource , "emp" , "mset_update_set" );
+		this.pSetMiningPriority.addParameter( "_resource" , Types.VARCHAR );
+		this.pSetMiningPriority.addParameter( "_priority" , Types.INTEGER );
+		this.pSetMiningPriority.addOutput( "_success" , Types.BOOLEAN );
+
+		this.pApplyUpdate = new StoredProc( dataSource , "emp" , "mset_update_apply" );
+		this.pApplyUpdate.addOutput( "_success" , Types.BOOLEAN );
+	}
+
+
+	/* Documented in interface */
+	@Override
+	public boolean startUpdate( int empireId )
+	{
+		return (Boolean) this.pStartEmpireUpdate.execute( empireId ).get( "_success" );
+	}
+
+
+	/* Documented in interface */
+	@Override
+	public boolean setNewPriority( String resource , int priority )
+	{
+		return (Boolean) this.pSetMiningPriority.execute( resource , priority ).get( "_success" );
+	}
+
+
+	/* Documented in interface */
+	@Override
+	public boolean applyUpdate( )
+	{
+		return (Boolean) this.pApplyUpdate.execute( ).get( "_success" );
+	}
+
+}
diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesControllerBean.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesControllerBean.java
new file mode 100644
index 0000000..a2b8040
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesControllerBean.java
@@ -0,0 +1,68 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.deepclone.lw.interfaces.game.resources.MiningSettingsDAO;
+import com.deepclone.lw.interfaces.game.resources.ResourcesController;
+
+
+
+/**
+ * Resources controller component
+ * 
+ * <p>
+ * This component implements all game actions that affect resources or resource-related information,
+ * such as mining priorities.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@Transactional
+class ResourcesControllerBean
+		implements ResourcesController
+{
+
+	/** The mining settings access interface */
+	private MiningSettingsDAO settings;
+
+
+	/**
+	 * Dependency injector that sets the mining settings access interface
+	 * 
+	 * @param settings
+	 *            the mining settings access interface
+	 */
+	@Autowired( required = true )
+	public void setMiningSettingsDAO( MiningSettingsDAO settings )
+	{
+		this.settings = settings;
+	}
+
+
+	/**
+	 * Update an empire's mining settings
+	 * 
+	 * <p>
+	 * Start a mining settings update for the specified empire, then set each resource-specific
+	 * priority, then apply the update. Exit whenever something goes wrong.
+	 */
+	@Override
+	public void updateEmpireSettings( int empireId , Map< String , Integer > settings )
+	{
+		if ( !this.settings.startUpdate( empireId ) ) {
+			return;
+		}
+
+		for ( Map.Entry< String , Integer > entry : settings.entrySet( ) ) {
+			if ( ! this.settings.setNewPriority( entry.getKey( ) , entry.getValue( ) ) ) {
+				return;
+			}
+		}
+
+		this.settings.applyUpdate( );
+	}
+
+}
diff --git a/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources.xml b/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources.xml
index 2c36ed9..12debf7 100644
--- a/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources.xml
+++ b/legacyworlds-server-beans-resources/src/main/resources/configuration/game/resources.xml
@@ -3,6 +3,8 @@
 	xsi:schemaLocation="
                         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
+	<bean id="miningSettingsDAO" class="com.deepclone.lw.beans.game.resources.MiningSettingsDAOBean" />
+	<bean id="resourcesController" class="com.deepclone.lw.beans.game.resources.ResourcesControllerBean" />
 	<bean id="resourcesInformationDAO" class="com.deepclone.lw.beans.game.resources.ResourcesInformationDAOBean" />
 
 </beans>
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/UpdateEmpireMiningSettingsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/UpdateEmpireMiningSettingsCommandDelegateBean.java
new file mode 100644
index 0000000..ea0b021
--- /dev/null
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/UpdateEmpireMiningSettingsCommandDelegateBean.java
@@ -0,0 +1,74 @@
+package com.deepclone.lw.beans.user.player.game;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.player.GameSubTypeBean;
+import com.deepclone.lw.cmd.player.UpdateEmpireMiningSettingsCommand;
+import com.deepclone.lw.interfaces.game.resources.ResourcesController;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+import com.deepclone.lw.session.NullResponse;
+
+
+
+/**
+ * Command handler for empire mining settings updates
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class UpdateEmpireMiningSettingsCommandDelegateBean
+		implements AutowiredCommandDelegate
+{
+
+	/** The resources controller */
+	private ResourcesController resourcesController;
+
+
+	/**
+	 * Dependency injector that sets the resources controller
+	 * 
+	 * @param resourcesController
+	 *            the resources controller
+	 */
+	@Autowired( required = true )
+	public void setResourcesController( ResourcesController resourcesController )
+	{
+		this.resourcesController = resourcesController;
+	}
+
+
+	/** This class handles {@link UpdateEmpireMiningSettingsCommand} instances */
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return UpdateEmpireMiningSettingsCommand.class;
+	}
+
+
+	/** This class is enabled for the {@link GameSubTypeBean} session type */
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return GameSubTypeBean.class;
+	}
+
+
+	/**
+	 * If the account is not in vacation mode, update mining settings. Always return a
+	 * {@link NullResponse}.
+	 */
+	@Override
+	public CommandResponse execute( ServerSession session , Command command )
+	{
+		if ( !session.get( "vacation" , Boolean.class ) ) {
+			int empireId = session.get( "empireId" , Integer.class );
+			this.resourcesController.updateEmpireSettings( empireId ,
+					( (UpdateEmpireMiningSettingsCommand) command ).getSettings( ) );
+		}
+		return new NullResponse( );
+	}
+}
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
index 37a91ea..203a345 100644
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
+++ b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
@@ -45,6 +45,7 @@
 	<!-- Game: empire -->
 	<bean class="com.deepclone.lw.beans.user.player.game.OverviewCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.player.game.ImplementTechCommandDelegateBean" />
+	<bean class="com.deepclone.lw.beans.user.player.game.UpdateEmpireMiningSettingsCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.player.game.GetNewPlanetCommandDelegateBean" />
 
 	<!-- Game: planet list -->
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
index 9c4545f..d8b4f04 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
@@ -29,15 +29,17 @@ BEGIN
 
 	CREATE TEMPORARY TABLE mset_update(
 		empire_id			INT ,
-		resource_name_id	INT ,
+		resource_name		TEXT ,
 		empmset_weight		INT
 	) ON COMMIT DROP;
 
 	INSERT INTO mset_update
-		SELECT _mset.empire_id , _mset.resource_name_id , 2
+		SELECT _mset.empire_id , _str.name , 2
 			FROM emp.empires _empire
 				INNER JOIN emp.mining_settings _mset
 					ON _empire.name_id = _mset.empire_id
+				INNER JOIN defs.strings _str
+					ON _str.id = _mset.resource_name_id
 			WHERE _empire.name_id = _empire_id
 			FOR SHARE OF _empire
 			FOR UPDATE OF _mset;
@@ -81,7 +83,7 @@ BEGIN
 	CREATE TEMPORARY TABLE mset_update(
 		empire_id			INT ,
 		planet_id			INT ,
-		resource_name_id	INT ,
+		resource_name		TEXT ,
 		empmset_weight		INT
 	) ON COMMIT DROP;
 	
@@ -106,8 +108,10 @@ BEGIN
 		FOR UPDATE;
 
 	INSERT INTO mset_update
-		SELECT _empire_id , _planet_id , resource_name_id , 2
+		SELECT _empire_id , _planet_id , _str.name , 2
 			FROM verse.resource_providers
+				INNER JOIN defs.strings _str
+					ON _str.id = resource_name_id
 			WHERE planet_id = _planet_id;
 
 	RETURN TRUE;
@@ -131,14 +135,14 @@ GRANT EXECUTE
  * must be called after emp.mset_update_start() has initialised the table.
  *
  * Parameters:
- *		_resource_id		The resource's identifier
+ *		_resource			The resource's text identifier
  *		_weight				The setting's new value
  *
  * Returns:
  *		?					True if the resource exists, false otherwise.
  */
-DROP FUNCTION IF EXISTS emp.mset_update_set( INT , INT );
-CREATE FUNCTION emp.mset_update_set( _resource_id INT , _weight INT )
+DROP FUNCTION IF EXISTS emp.mset_update_set( TEXT , INT );
+CREATE FUNCTION emp.mset_update_set( _resource TEXT , _weight INT )
 		RETURNS BOOLEAN
 		STRICT VOLATILE
 		SECURITY DEFINER
@@ -147,7 +151,7 @@ BEGIN
 
 	UPDATE mset_update
 		SET empmset_weight = _weight
-		WHERE resource_name_id = _resource_id;
+		WHERE resource_name = _resource;
 	RETURN FOUND;
 
 END;
@@ -155,10 +159,10 @@ $mset_update_set$ LANGUAGE PLPGSQL;
 
 
 REVOKE EXECUTE
-	ON FUNCTION emp.mset_update_set( INT , INT )
+	ON FUNCTION emp.mset_update_set( TEXT , INT )
 	FROM PUBLIC;
 GRANT EXECUTE
-	ON FUNCTION emp.mset_update_set( INT , INT )
+	ON FUNCTION emp.mset_update_set( TEXT , INT )
 	TO :dbuser;
 
 
@@ -201,8 +205,10 @@ BEGIN
 					empire_id , planet_id , resource_name_id ,
 					emppmset_weight
 			) SELECT empire_id , planet_id ,
-					resource_name_id ,	empmset_weight
-				FROM mset_update;
+					_str.id , empmset_weight
+				FROM mset_update
+					INNER JOIN defs.strings _str
+						ON _str.name = resource_name;
 
 	EXCEPTION
 
@@ -211,8 +217,10 @@ BEGIN
 			UPDATE emp.mining_settings _settings
 				SET empmset_weight = _update.empmset_weight
 				FROM mset_update _update
+					INNER JOIN defs.strings _str
+						ON _str.name = _update.resource_name
 				WHERE _update.empire_id = _settings.empire_id
-					AND _update.resource_name_id = _settings.resource_name_id;
+					AND _str.id = _settings.resource_name_id;
 	END;
 	RETURN TRUE;
 
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql
index b73c7bc..8584f82 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql
@@ -23,7 +23,7 @@ BEGIN;
 	SELECT ok( NOT emp.mset_update_start( _get_bad_emp_name( ) ) );
 	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Temporary table exists despite bad empire identifier' );
 	SELECT has_table( 'mset_update' );
-	DROP TABLE mset_update;
+	DROP TABLE IF EXISTS mset_update;
 
 	
 	SELECT diag_test_name( 'emp.mset_update_start( INT ) - Return value on valid empire identifier' );
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
index 816508a..31536c8 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
@@ -28,31 +28,31 @@ BEGIN;
 	SELECT ok( NOT emp.mset_update_start( _get_bad_emp_name( ) , _get_map_name( 'testPlanet1' ) ) );
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite bad empire identifier' );
 	SELECT has_table( 'mset_update' );
-	DROP TABLE mset_update;
+	DROP TABLE IF EXISTS mset_update;
 
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on bad planet identifier' );
 	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_bad_map_name( ) ) );
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite bad planet identifier' );
 	SELECT has_table( 'mset_update' );
-	DROP TABLE mset_update;
+	DROP TABLE IF EXISTS mset_update;
 
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on unowned planet identifier' );
 	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet2' ) ) );
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite unowned planet identifier' );
 	SELECT has_table( 'mset_update' );
-	DROP TABLE mset_update;
+	DROP TABLE IF EXISTS mset_update;
 
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on unowned planet identifier' );
 	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet2' ) ) );
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite unowned planet identifier' );
 	SELECT has_table( 'mset_update' );
-	DROP TABLE mset_update;
+	DROP TABLE IF EXISTS mset_update;
 
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on planet with no resource providers' );
 	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet3' ) ) );
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite planet with no resource providers' );
 	SELECT has_table( 'mset_update' );
-	DROP TABLE mset_update;
+	DROP TABLE IF EXISTS mset_update;
 
 	
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on valid identifiers' );
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql
index 7515bfb..5009bb6 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql
@@ -4,34 +4,34 @@
 BEGIN;
 	CREATE TEMPORARY TABLE mset_update(
 		empire_id			INT ,
-		resource_name_id	INT ,
+		resource_name		TEXT ,
 		empmset_weight		INT
 	) ON COMMIT DROP;
-	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 0 );
+	INSERT INTO mset_update VALUES ( 1 , 'a' , 0 ) , ( 1 , 'b' , 0 );
 	
 	/***** TESTS BEGIN HERE *****/
 	SELECT plan( 7 );
 	
 	SELECT diag_test_name( 'emp.mset_update_set( ) - Valid update' );
-	SELECT ok( emp.mset_update_set( 1 , 1 ) );
+	SELECT ok( emp.mset_update_set( 'a' , 1 ) );
 	SELECT diag_test_name( 'emp.mset_update_set( ) - Valid update results (1/2)' );
-	SELECT is( empmset_weight , 1 ) FROM mset_update WHERE resource_name_id = 1;
+	SELECT is( empmset_weight , 1 ) FROM mset_update WHERE resource_name = 'a';
 	SELECT diag_test_name( 'emp.mset_update_set( ) - Valid update results (2/2)' );
-	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name_id = 2;
+	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name = 'b';
 	DELETE FROM mset_update;
 
-	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 0 );
+	INSERT INTO mset_update VALUES ( 1 , 'a' , 0 ) , ( 1 , 'b' , 0 );
 	SELECT diag_test_name( 'emp.mset_update_set( ) - Update on unknown resource' );
-	SELECT ok( NOT emp.mset_update_set( 12 , 1 ) );
+	SELECT ok( NOT emp.mset_update_set( 'c' , 1 ) );
 	SELECT diag_test_name( 'emp.mset_update_set( ) - Unknown resource update results (1/2)' );
-	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name_id = 1;
+	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name = 'a';
 	SELECT diag_test_name( 'emp.mset_update_set( ) - Unknown resource update results (2/2)' );
-	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name_id = 2;
+	SELECT is( empmset_weight , 0 ) FROM mset_update WHERE resource_name = 'b';
 	DELETE FROM mset_update;
 
-	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 0 );
+	INSERT INTO mset_update VALUES ( 1 , 'a' , 0 ) , ( 1 , 'b' , 0 );
 	SELECT diag_test_name( 'emp.mset_update_set( ) - Update with invalid weight' );
-	SELECT ok( emp.mset_update_set( 1 , -1 ) );
+	SELECT ok( emp.mset_update_set( 'a' , -1 ) );
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
index 14c8d6d..d9088fa 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
@@ -2,6 +2,11 @@
  * Test the emp.mset_update_apply() function
  */
 BEGIN;
+	/*
+	 * Create a pair of strings
+	 */
+	INSERT INTO defs.strings (name) VALUES ( 'a' ) , ( 'b' );
+
 	/*
 	 * Remove foreign keys from the empire mining settings table, insert some
 	 * data into it.
@@ -10,15 +15,17 @@ BEGIN;
 		DROP CONSTRAINT fk_empmset_empire ,
 		DROP CONSTRAINT fk_empmset_resource;
 	INSERT INTO emp.mining_settings ( empire_id , resource_name_id )
-		VALUES ( 1 , 1 ) , ( 1 , 2 );
+		SELECT 1 , id
+			FROM defs.strings
+			WHERE name IN ( 'a' , 'b' );
 
 	/* Create the temporary table */
 	CREATE TEMPORARY TABLE mset_update(
 		empire_id			INT ,
-		resource_name_id	INT ,
+		resource_name		TEXT ,
 		empmset_weight		INT
 	) ON COMMIT DROP;
-	INSERT INTO mset_update VALUES ( 1 , 1 , 0 ) , ( 1 , 2 , 4 );
+	INSERT INTO mset_update VALUES ( 1 , 'a' , 0 ) , ( 1 , 'b' , 4 );
 
 	/***** TESTS BEGIN HERE *****/
 	SELECT plan( 8 );
@@ -27,23 +34,31 @@ BEGIN;
 	SELECT ok( emp.mset_update_apply( ) );
 	SELECT diag_test_name( 'emp.mset_update_apply() - Empire update results' );
 	SELECT set_eq(
-		$$ SELECT resource_name_id , empmset_weight FROM emp.mining_settings $$ ,
-		$$ VALUES ( 1 , 0 ) , ( 2 , 4 ) $$
+		$$ SELECT name , empmset_weight
+			FROM emp.mining_settings
+				INNER JOIN defs.strings
+					ON resource_name_id = id $$ ,
+		$$ VALUES ( 'a' , 0 ) , ( 'b' , 4 ) $$
 	);
 
 	/* Reset temporary table and settings */
 	DELETE FROM mset_update;
 	DELETE FROM emp.mining_settings;
 	INSERT INTO emp.mining_settings ( empire_id , resource_name_id )
-		VALUES ( 1 , 1 ) , ( 1 , 2 );
+		SELECT 1 , id
+			FROM defs.strings
+			WHERE name IN ( 'a' , 'b' );
 
-	INSERT INTO mset_update VALUES ( 1 , 1 , -1 ) , ( 1 , 2 , 4 );
+	INSERT INTO mset_update VALUES ( 1 , 'a' , -1 ) , ( 1 , 'b' , 4 );
 	SELECT diag_test_name( 'emp.mset_update_apply() - Applying invalid empire update' );
 	SELECT ok( NOT emp.mset_update_apply( ) );
 	SELECT diag_test_name( 'emp.mset_update_apply() - Invalid empire update results' );
 	SELECT set_eq(
-		$$ SELECT resource_name_id , empmset_weight FROM emp.mining_settings $$ ,
-		$$ VALUES ( 1 , 2 ) , ( 2 , 2 ) $$
+		$$ SELECT name , empmset_weight
+			FROM emp.mining_settings
+				INNER JOIN defs.strings
+					ON resource_name_id = id $$ ,
+		$$ VALUES ( 'a' , 2 ) , ( 'b' , 2 ) $$
 	);
 
 
@@ -55,38 +70,48 @@ BEGIN;
 		DROP CONSTRAINT fk_emppmset_empire ,
 		DROP CONSTRAINT fk_emppmset_resource;
 	INSERT INTO emp.planet_mining_settings ( empire_id , planet_id , resource_name_id )
-		VALUES ( 1 , 1 , 1 ) , ( 1 , 1 , 2 );
+		SELECT 1 , 1 , id
+			FROM defs.strings
+			WHERE name IN ( 'a' , 'b' );
 
 	DROP TABLE mset_update;
 	CREATE TEMPORARY TABLE mset_update(
 		empire_id			INT ,
 		planet_id			INT ,
-		resource_name_id	INT ,
+		resource_name		TEXT ,
 		empmset_weight		INT
 	) ON COMMIT DROP;
-	INSERT INTO mset_update VALUES ( 1 , 1 , 1 , 0 ) , ( 1 , 1 , 2 , 4 );
+	INSERT INTO mset_update VALUES ( 1 , 1 , 'a' , 0 ) , ( 1 , 1 , 'b' , 4 );
 
 	SELECT diag_test_name( 'emp.mset_update_apply() - Applying valid planet update' );
 	SELECT ok( emp.mset_update_apply( ) );
 	SELECT diag_test_name( 'emp.mset_update_apply() - Planet update results' );
 	SELECT set_eq(
-		$$ SELECT resource_name_id , emppmset_weight FROM emp.planet_mining_settings $$ ,
-		$$ VALUES ( 1 , 0 ) , ( 2 , 4 ) $$
+		$$ SELECT name , emppmset_weight
+			FROM emp.planet_mining_settings
+				INNER JOIN defs.strings
+					ON resource_name_id = id $$ ,
+		$$ VALUES ( 'a' , 0 ) , ( 'b' , 4 ) $$
 	);
 
 	/* Reset temporary table and settings */
 	DELETE FROM mset_update;
 	DELETE FROM emp.planet_mining_settings;
 	INSERT INTO emp.planet_mining_settings ( empire_id , planet_id , resource_name_id )
-		VALUES ( 1 , 1 , 1 ) , ( 1 , 1 , 2 );
+		SELECT 1 , 1 , id
+			FROM defs.strings
+			WHERE name IN ( 'a' , 'b' );
 
-	INSERT INTO mset_update VALUES ( 1 , 1 , 1 , -1 ) , ( 1 , 1 , 2 , 4 );
+	INSERT INTO mset_update VALUES ( 1 , 1 , 'a' , -1 ) , ( 1 , 1 , 'b' , 4 );
 	SELECT diag_test_name( 'emp.mset_update_apply() - Applying invalid planet update' );
 	SELECT ok( NOT emp.mset_update_apply( ) );
 	SELECT diag_test_name( 'emp.mset_update_apply() - Invalid planet update results' );
 	SELECT set_eq(
-		$$ SELECT resource_name_id , emppmset_weight FROM emp.planet_mining_settings $$ ,
-		$$ VALUES ( 1 , 2 ) , ( 2 , 2 ) $$
+		$$ SELECT name , emppmset_weight
+			FROM emp.planet_mining_settings
+				INNER JOIN defs.strings
+					ON resource_name_id = id $$ ,
+		$$ VALUES ( 'a' , 2 ) , ( 'b' , 2 ) $$
 	);
 
 	SELECT * FROM finish( );
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql
index cc37514..633ccae 100644
--- a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql
@@ -7,7 +7,7 @@ BEGIN;
 	
 	SELECT diag_test_name( 'emp.mset_update_set() - Privileges' );
 	SELECT lives_ok( $$
-			SELECT emp.mset_update_set( 1 , -1 )
+			SELECT emp.mset_update_set( 'a' , -1 )
 		$$ );
 	
 	SELECT * FROM finish( );
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/MiningSettingsDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/MiningSettingsDAO.java
new file mode 100644
index 0000000..c19656e
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/MiningSettingsDAO.java
@@ -0,0 +1,60 @@
+package com.deepclone.lw.interfaces.game.resources;
+
+
+/**
+ * Data access object for mining settings
+ * 
+ * <p>
+ * This interface provides methods which call stored procedures that control mining settings for
+ * both empires and planets.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public interface MiningSettingsDAO
+{
+
+	/**
+	 * Start an empire settings update
+	 * 
+	 * <p>
+	 * Start an empire settings update by calling the <code>emp.mset_update_start(INT)</code> stored
+	 * procedure.
+	 * 
+	 * @param empireId
+	 *            the identifier of the empire whose settings will be updated
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> on failure
+	 */
+	public boolean startUpdate( int empireId );
+
+
+	/**
+	 * Set the new extraction priority of a resource
+	 * 
+	 * <p>
+	 * Call the <code>emp.mset_update_set(TEXT,INT)</code> stored procedure to set the new
+	 * extraction priority for some type of resources. An mining settings update (empire- or
+	 * planet-wide) must have been initiated before this method is called.
+	 * 
+	 * @param resource
+	 *            the identifier of the resource
+	 * @param priority
+	 *            the new extraction priority for the resource
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> if the resource did not exist.
+	 */
+	public boolean setNewPriority( String resource , int priority );
+
+
+	/**
+	 * Apply the current settings update
+	 * 
+	 * <p>
+	 * This method applies a previously initiated mining settings update by calling the
+	 * <code>emp.mset_update_apply()</code> stored procedure.
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> if one of the priorities was
+	 *         invalid.
+	 */
+	public boolean applyUpdate( );
+}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesController.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesController.java
new file mode 100644
index 0000000..2444da7
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesController.java
@@ -0,0 +1,30 @@
+package com.deepclone.lw.interfaces.game.resources;
+
+
+import java.util.Map;
+
+
+
+/**
+ * Resources control interface
+ * 
+ * <p>
+ * This interface corresponds to the component which controls resources from the game's point of
+ * view. It contains methods that update mining settings.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public interface ResourcesController
+{
+
+	/**
+	 * Update an empire's mining settings
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param settings
+	 *            the new settings
+	 */
+	public void updateEmpireSettings( int empireId , Map< String , Integer > settings );
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/MockMiningSettingsDAO.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/MockMiningSettingsDAO.java
new file mode 100644
index 0000000..0b46dce
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/MockMiningSettingsDAO.java
@@ -0,0 +1,121 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import com.deepclone.lw.interfaces.game.resources.MiningSettingsDAO;
+
+
+
+/**
+ * Mock mining settings DAO which can be used to simulate failures and trace which methods were
+ * called.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class MockMiningSettingsDAO
+		implements MiningSettingsDAO
+{
+	/** The empire identifier with which {@link #startUpdate(int)} was called */
+	private Integer updateEmpire = null;
+
+	/** The amount of calls to {@link #setNewPriority(String, int)} */
+	private int callsToSet = 0;
+
+	/** Whether {@link #applyUpdate()} was called */
+	private boolean applyCalled = false;
+
+	/** Whether {@link #startUpdate(int)} will succeed or fail */
+	private boolean startUpdateSucceeds = true;
+
+	/** Whether applyUpdate will succeed */
+	private boolean applyUpdateSucceeds = true;
+
+	/**
+	 * Amount of calls to {@link #setNewPriority(String, int)} that will succeed, or
+	 * <code>null</code> if they will all succeed.
+	 */
+	private Integer maxSetCalls = null;
+
+
+	/** @return the empire identifier to update */
+	public Integer getUpdateEmpire( )
+	{
+		return this.updateEmpire;
+	}
+
+
+	/** @return the amount of calls to {@link #setNewPriority(String, int)} */
+	public int getCallsToSet( )
+	{
+		return this.callsToSet;
+	}
+
+
+	/** @return <code>true</code> if {@link #applyUpdate()} was called */
+	public boolean wasApplyCalled( )
+	{
+		return this.applyCalled;
+	}
+
+
+	/**
+	 * Determine whether calls to {@link #startUpdate(int)} will succeed
+	 * 
+	 * @param startUpdateSucceeds
+	 *            <code>true</code> if the call is to succeed, <code>false</code> if it is to fail
+	 */
+	public void setStartUpdateSucceeds( boolean startUpdateSucceeds )
+	{
+		this.startUpdateSucceeds = startUpdateSucceeds;
+	}
+
+
+	/**
+	 * Determine whether calls to {@link #applyUpdate()} will succeed
+	 * 
+	 * @param applyUpdateSucceeds
+	 *            <code>true</code> if the call is to succeed, <code>false</code> if it is to fail
+	 */
+	public void setApplyUpdateSucceeds( boolean applyUpdateSucceeds )
+	{
+		this.applyUpdateSucceeds = applyUpdateSucceeds;
+	}
+
+
+	/**
+	 * Set the amount of calls to {@link #setNewPriority(String, int)} that will succeed
+	 * 
+	 * @param maxSetCalls
+	 *            the amount of calls that will succeed, or <code>null</code> if the method must
+	 *            always succeed
+	 */
+	public void setMaxSetCalls( Integer maxSetCalls )
+	{
+		this.maxSetCalls = maxSetCalls;
+	}
+
+
+	@Override
+	public boolean startUpdate( int empireId )
+	{
+		this.updateEmpire = empireId;
+		return this.startUpdateSucceeds;
+	}
+
+
+	@Override
+	public boolean setNewPriority( String resource , int priority )
+	{
+		this.callsToSet++;
+		return ( this.maxSetCalls == null || this.maxSetCalls > this.callsToSet - 1 );
+	}
+
+
+	@Override
+	public boolean applyUpdate( )
+	{
+		this.applyCalled = true;
+		return this.applyUpdateSucceeds;
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestResourcesControllerBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestResourcesControllerBean.java
new file mode 100644
index 0000000..b836ab9
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestResourcesControllerBean.java
@@ -0,0 +1,121 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.interfaces.game.resources.MiningSettingsDAO;
+
+
+
+/**
+ * Tests for {@link ResourcesControllerBean}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+public class TestResourcesControllerBean
+{
+
+	/** Empire identifier used in the tests */
+	private static final Integer EMPIRE_ID = 42;
+
+	/** Mining settings used in the tests */
+	private static final Map< String , Integer > MINING_SETTINGS;
+
+	/**
+	 * Initialise the mining settings map
+	 */
+	static {
+		HashMap< String , Integer > tempMap = new HashMap< String , Integer >( );
+		tempMap.put( "test 1" , 1 );
+		tempMap.put( "test 2" , 2 );
+		tempMap.put( "test 3" , 3 );
+		tempMap.put( "test 4" , 4 );
+		MINING_SETTINGS = Collections.unmodifiableMap( tempMap );
+	}
+
+	/** The mock database access object */
+	private MockMiningSettingsDAO miningSettingsDAO;
+
+	/** The instance to test on */
+	private ResourcesControllerBean ctrl;
+
+
+	/**
+	 * Create the resource controller that will be used in the tests and its various (usually fake)
+	 * dependencies
+	 */
+	@Before
+	public void setUp( )
+	{
+		this.miningSettingsDAO = new MockMiningSettingsDAO( );
+		this.ctrl = new ResourcesControllerBean( );
+		this.ctrl.setMiningSettingsDAO( this.miningSettingsDAO );
+	}
+
+
+	/**
+	 * When calling {@link MiningSettingsDAO#startUpdate(int)} fails, the empire settings update is
+	 * interrupted.
+	 */
+	@Test
+	public void testEmpireStartUpdateFails( )
+	{
+		this.miningSettingsDAO.setStartUpdateSucceeds( false );
+		this.ctrl.updateEmpireSettings( EMPIRE_ID , MINING_SETTINGS );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getUpdateEmpire( ) );
+		assertEquals( 0 , this.miningSettingsDAO.getCallsToSet( ) );
+		assertFalse( this.miningSettingsDAO.wasApplyCalled( ) );
+	}
+
+
+	/**
+	 * When calling {@link MiningSettingsDAO#startUpdate(int)} succeeds but one of the calls to
+	 * {@link MiningSettingsDAO#setNewPriority(String, int)} fails, the update is interrupted.
+	 */
+	@Test
+	public void testSetMiningPriorityFails( )
+	{
+		this.miningSettingsDAO.setMaxSetCalls( 2 );
+		this.ctrl.updateEmpireSettings( EMPIRE_ID , MINING_SETTINGS );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getUpdateEmpire( ) );
+		assertEquals( 3 , this.miningSettingsDAO.getCallsToSet( ) );
+		assertFalse( this.miningSettingsDAO.wasApplyCalled( ) );
+	}
+
+
+	/**
+	 * If both {@link MiningSettingsDAO#startUpdate(int)} and
+	 * {@link MiningSettingsDAO#setNewPriority(String, int)} succeed,
+	 * {@link MiningSettingsDAO#applyUpdate()} is called.
+	 */
+	@Test
+	public void testSettingsSuccess( )
+	{
+		this.ctrl.updateEmpireSettings( EMPIRE_ID , MINING_SETTINGS );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getUpdateEmpire( ) );
+		assertEquals( 4 , this.miningSettingsDAO.getCallsToSet( ) );
+		assertTrue( this.miningSettingsDAO.wasApplyCalled( ) );
+	}
+
+
+	/**
+	 * A failure of {@link MiningSettingsDAO#applyUpdate()} has no influence on the call.
+	 */
+	@Test
+	public void testSettingsApplyFail( )
+	{
+		this.miningSettingsDAO.setApplyUpdateSucceeds( false );
+		this.ctrl.updateEmpireSettings( EMPIRE_ID , MINING_SETTINGS );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getUpdateEmpire( ) );
+		assertEquals( 4 , this.miningSettingsDAO.getCallsToSet( ) );
+		assertTrue( this.miningSettingsDAO.wasApplyCalled( ) );
+	}
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/UpdateEmpireMiningSettingsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/UpdateEmpireMiningSettingsCommand.java
new file mode 100644
index 0000000..757417d
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/UpdateEmpireMiningSettingsCommand.java
@@ -0,0 +1,50 @@
+package com.deepclone.lw.cmd.player;
+
+
+import java.util.Map;
+
+import com.deepclone.lw.session.Command;
+
+
+
+/**
+ * Command that updates empire mining settings
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class UpdateEmpireMiningSettingsCommand
+		extends Command
+{
+
+	/**
+	 * Serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** The new empire mining settings */
+	private final Map< String , Integer > settings;
+
+
+	/**
+	 * Initialise the command using mining settings
+	 * 
+	 * @param settings
+	 *            a map that associates resource identifiers to priorities
+	 */
+	public UpdateEmpireMiningSettingsCommand( Map< String , Integer > settings )
+	{
+		this.settings = settings;
+	}
+
+
+	/** @return the mining settings to apply */
+	public Map< String , Integer > getSettings( )
+	{
+		return this.settings;
+	}
+
+}
diff --git a/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
index 1cc1607..0f15268 100644
--- a/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
+++ b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
@@ -85,6 +85,19 @@ public class PlayerSession
 	}
 
 
+	/**
+	 * Send an {@link UpdateEmpireMiningSettingsCommand} to the server
+	 * 
+	 * @param miningSettings
+	 *            the mining settings
+	 */
+	public void updateEmpireMiningSettings( Map< String , Integer > miningSettings )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		this.execute( new UpdateEmpireMiningSettingsCommand( miningSettings ) );
+	}
+
+
 	/* Planet list */
 
 	public ListPlanetsResponse listPlanets( )
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
index 639de56..28d1686 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
@@ -63,60 +63,8 @@
 		</@tab>
 
 		<@tab id="economy" title="Economy">
-		
-			<@listview>
-				<@lv_line headers=true>
-					<@lv_column width=30>&nbsp;</@lv_column>
-					<@lv_column width="x">Resource</@lv_column>
-					<@lv_column width=100 centered=true><@over_time "Income" /></@lv_column>
-					<@lv_column width=100 centered=true><@over_time "Upkeep" /></@lv_column>
-					<@lv_column width=100 centered=true>Reserves</@lv_column>
-					<@lv_column width=100 centered=true>Mining priority</@lv_column>
-				</@lv_line>
-					
-				<#list ov.economy as resource>
-
-					<#if previousCategory?has_content && !resource.category?has_content
-							|| resource.category?has_content && !previousCategory?has_content
-							|| resource.category?has_content && previousCategory?has_content
-								&& resource.category != previousCategory>
-						<@lv_line>
-							<#if resource.category?has_content>
-								<td colspan="5"><strong>${resource.category?xhtml}</strong></td>
-							<#else>
-								<td colspan="5"><hr /></td>
-							</#if>
-						</@lv_line>
-						<#local previousCategory=resource.category>
-					</#if>
-					
-					<@lv_line>
-						<@lv_column>&nbsp;</@lv_column>
-						<@lv_column>${resource.title?xhtml}
-							<div class="auto-hide">${resource.description?xhtml}</div>
-						</@lv_column>
-						<@lv_column centered=true>${resource.income?string(",##0")}</@lv_column>
-						<@lv_column centered=true>${resource.upkeep?string(",##0")}</@lv_column>
-						<@lv_column centered=true>${resource.stockpiled?string(",##0")}</@lv_column>
-						<@lv_column centered=true>
-							<#if resource.miningPriority?has_content>
-								<#switch resource.miningPriority>
-									<#case 0>lowest<#break>
-									<#case 1>low<#break>
-									<#case 2>normal<#break>
-									<#case 3>high<#break>
-									<#case 4>highest<#break>
-								</#switch>
-							<#else>
-								N/A
-							</#if>
-						</@lv_column>
-					</@lv_line>
-
-				</#list>
-
-			</@listview>
-
+			<#include "overview/resources.ftl" />
+			<@overviewResources />
 		</@tab>
 
 		<@tab id="research" title="Research">
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview/resources.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview/resources.ftl
new file mode 100644
index 0000000..7206f92
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview/resources.ftl
@@ -0,0 +1,64 @@
+<#macro overviewResources>
+<form action="update-mining-settings.action" method="POST"><@listview>
+	<@lv_line headers=true>
+		<@lv_column width=30>&nbsp;</@lv_column>
+		<@lv_column width="x">Resource</@lv_column>
+		<@lv_column width=100 centered=true><@over_time "Income" /></@lv_column>
+		<@lv_column width=100 centered=true><@over_time "Upkeep" /></@lv_column>
+		<@lv_column width=100 centered=true>Reserves</@lv_column>
+		<@lv_column width=100 centered=true>Mining priority</@lv_column>
+	</@lv_line>
+		
+	<#list ov.economy as resource>
+
+		<#if previousCategory?has_content && !resource.category?has_content
+				|| resource.category?has_content && !previousCategory?has_content
+				|| resource.category?has_content && previousCategory?has_content
+					&& resource.category != previousCategory>
+			<@lv_line>
+				<#if resource.category?has_content>
+					<td colspan="5"><strong>${resource.category?xhtml}</strong></td>
+				<#else>
+					<td colspan="5"><hr /></td>
+				</#if>
+			</@lv_line>
+			<#local previousCategory=resource.category>
+		</#if>
+		
+		<@lv_line>
+			<@lv_column>&nbsp;</@lv_column>
+			<@lv_column>${resource.title?xhtml}
+				<div class="auto-hide">${resource.description?xhtml}</div>
+			</@lv_column>
+			<@lv_column centered=true>${resource.income?string(",##0")}</@lv_column>
+			<@lv_column centered=true>${resource.upkeep?string(",##0")}</@lv_column>
+			<@lv_column centered=true>${resource.stockpiled?string(",##0")}</@lv_column>
+			<@lv_column centered=true>
+				<#if resource.miningPriority?has_content>
+					<select name="ems-${resource.identifier?xhtml}" class="input">
+						<option style="padding: 0 5px" value="0" <#if resource.miningPriority = 0>selected="selected"</#if>>lowest</option>
+						<option style="padding: 0 5px" value="1" <#if resource.miningPriority = 1>selected="selected"</#if>>low</option>
+						<option style="padding: 0 5px" value="2" <#if resource.miningPriority = 2>selected="selected"</#if>>normal</option>
+						<option style="padding: 0 5px" value="3" <#if resource.miningPriority = 3>selected="selected"</#if>>high</option>
+						<option style="padding: 0 5px" value="4" <#if resource.miningPriority = 4>selected="selected"</#if>>highest</option>
+					</select>
+				<#else>
+					N/A
+				</#if>
+			</@lv_column>
+		</@lv_line>
+
+	</#list>
+	
+	<@lv_line headers=true>
+		<@lv_column width="x" colspan=6>&nbsp;</@lv_column>
+	</@lv_line>
+	<@lv_line>
+		<@lv_column colspan=4>&nbsp;</@lv_column>
+		<@lv_column right=true colspan=2>
+			<input type="submit" value="Update mining priorities" class="input" style="margin: 15px 0 0 0" />
+		</@lv_column>
+	</@lv_line>
+
+</@listview></form>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
index 5bf71e8..01aa309 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
@@ -63,60 +63,8 @@
 		</@tab>
 		
 		<@tab id="economy" title="Économie">
-		
-			<@listview>
-				<@lv_line headers=true>
-					<@lv_column width=30>&nbsp;</@lv_column>
-					<@lv_column width="x">Ressource</@lv_column>
-					<@lv_column width=100 centered=true><@over_time "Bénéfice" /></@lv_column>
-					<@lv_column width=100 centered=true><@over_time title="Charges" feminin=true pluriel=true /></@lv_column>
-					<@lv_column width=100 centered=true>Réserves</@lv_column>
-					<@lv_column width=100 centered=true>Priorité d'extraction</@lv_column>
-				</@lv_line>
-					
-				<#list ov.economy as resource>
-
-					<#if previousCategory?has_content && !resource.category?has_content
-							|| resource.category?has_content && !previousCategory?has_content
-							|| resource.category?has_content && previousCategory?has_content
-								&& resource.category != previousCategory>
-						<@lv_line>
-							<#if resource.category?has_content>
-								<td colspan="5"><strong>${resource.category?xhtml}</strong></td>
-							<#else>
-								<td colspan="5"><hr /></td>
-							</#if>
-						</@lv_line>
-						<#local previousCategory=resource.category>
-					</#if>
-					
-					<@lv_line>
-						<@lv_column>&nbsp;</@lv_column>
-						<@lv_column>${resource.title?xhtml}
-							<div class="auto-hide">${resource.description?xhtml}</div>
-						</@lv_column>
-						<@lv_column centered=true>${resource.income?string(",##0")}</@lv_column>
-						<@lv_column centered=true>${resource.upkeep?string(",##0")}</@lv_column>
-						<@lv_column centered=true>${resource.stockpiled?string(",##0")}</@lv_column>
-						<@lv_column centered=true>
-							<#if resource.miningPriority?has_content>
-								<#switch resource.miningPriority>
-									<#case 0>très basse<#break>
-									<#case 1>basse<#break>
-									<#case 2>normale<#break>
-									<#case 3>haute<#break>
-									<#case 4>très haute<#break>
-								</#switch>
-							<#else>
-								N/A
-							</#if>
-						</@lv_column>
-					</@lv_line>
-
-				</#list>
-
-			</@listview>
-
+			<#include "overview/resources.ftl" />
+			<@overviewResources />
 		</@tab>
 		
 		<@tab id="research" title="Recherche">
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview/resources.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview/resources.ftl
new file mode 100644
index 0000000..fd0a77f
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview/resources.ftl
@@ -0,0 +1,64 @@
+<#macro overviewResources>
+<form action="update-mining-settings.action" method="POST"><@listview>
+	<@lv_line headers=true>
+		<@lv_column width=30>&nbsp;</@lv_column>
+		<@lv_column width="x">Ressource</@lv_column>
+		<@lv_column width=100 centered=true><@over_time "Bénéfice" /></@lv_column>
+		<@lv_column width=100 centered=true><@over_time title="Charges" feminin=true pluriel=true /></@lv_column>
+		<@lv_column width=100 centered=true>Réserves</@lv_column>
+		<@lv_column width=100 centered=true>Priorité d'extraction</@lv_column>
+	</@lv_line>
+		
+	<#list ov.economy as resource>
+
+		<#if previousCategory?has_content && !resource.category?has_content
+				|| resource.category?has_content && !previousCategory?has_content
+				|| resource.category?has_content && previousCategory?has_content
+					&& resource.category != previousCategory>
+			<@lv_line>
+				<#if resource.category?has_content>
+					<td colspan="5"><strong>${resource.category?xhtml}</strong></td>
+				<#else>
+					<td colspan="5"><hr /></td>
+				</#if>
+			</@lv_line>
+			<#local previousCategory=resource.category>
+		</#if>
+		
+		<@lv_line>
+			<@lv_column>&nbsp;</@lv_column>
+			<@lv_column>${resource.title?xhtml}
+				<div class="auto-hide">${resource.description?xhtml}</div>
+			</@lv_column>
+			<@lv_column centered=true>${resource.income?string(",##0")}</@lv_column>
+			<@lv_column centered=true>${resource.upkeep?string(",##0")}</@lv_column>
+			<@lv_column centered=true>${resource.stockpiled?string(",##0")}</@lv_column>
+			<@lv_column centered=true>
+				<#if resource.miningPriority?has_content>
+					<select name="ems-${resource.identifier?xhtml}" class="input">
+						<option style="padding: 0 5px" value="0" <#if resource.miningPriority = 0>selected="selected"</#if>>très basse</option>
+						<option style="padding: 0 5px" value="1" <#if resource.miningPriority = 1>selected="selected"</#if>>basse</option>
+						<option style="padding: 0 5px" value="2" <#if resource.miningPriority = 2>selected="selected"</#if>>normale</option>
+						<option style="padding: 0 5px" value="3" <#if resource.miningPriority = 3>selected="selected"</#if>>haute</option>
+						<option style="padding: 0 5px" value="4" <#if resource.miningPriority = 4>selected="selected"</#if>>très haute</option>
+					</select>
+				<#else>
+					N/A
+				</#if>
+			</@lv_column>
+		</@lv_line>
+
+	</#list>
+	
+	<@lv_line headers=true>
+		<@lv_column width="x" colspan=6>&nbsp;</@lv_column>
+	</@lv_line>
+	<@lv_line>
+		<@lv_column colspan=4>&nbsp;</@lv_column>
+		<@lv_column right=true colspan=2>
+			<input type="submit" value="Modifier les priorités" class="input" style="margin: 15px 0 0 0" />
+		</@lv_column>
+	</@lv_line>
+
+</@listview></form>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
index 3e196b7..10b04f9 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
@@ -10,15 +10,15 @@
 </#macro>
 <#macro lv_column width=0 centered=false right=false colspan=0>
 	<#if width?is_string>
-		<th style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1>colspan="${colspan}"</#if>>
+		<th style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>>
 			<#nested>
 		</th>
 	<#elseif width gt 0>
-		<th style="width: ${width}px; text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1>colspan="${colspan}"</#if>>
+		<th style="width: ${width}px; text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>>
 			<#nested>
 		</th>
 	<#else>
-		<td style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>" <#if colspan gt 1>colspan="${colspan}"</#if>>
+		<td style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>>
 			<#nested>
 		</td>
 	</#if>
diff --git a/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
index ffbbf46..ecc8c1f 100644
--- a/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
+++ b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
@@ -1,6 +1,10 @@
 package com.deepclone.lw.web.main.game;
 
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
 import javax.servlet.http.HttpServletRequest;
 
 import org.springframework.stereotype.Controller;
@@ -20,6 +24,15 @@ import com.deepclone.lw.web.csess.PlayerSession;
 
 
 
+/**
+ * Overview page controller
+ * 
+ * <p>
+ * This page controller implements the "Overview" page, as well as the commands it supports
+ * (implement technology, update empire mining settings).
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
 @Controller
 @SessionRequirement( value = true , redirectTo = "player-session" , subType = "game" )
 @SessionAttributes( "language" )
@@ -27,6 +40,29 @@ public class OverviewPage
 		extends PageControllerBase
 {
 
+	/**
+	 * Main overview display
+	 * 
+	 * <p>
+	 * This method is mapped to the overview page's URL. It will fetch the empire overview from the
+	 * server and display the appropriate page.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * @param language
+	 *            the language from the session
+	 * @param model
+	 *            the model
+	 * 
+	 * @return the overview page rendering order
+	 * 
+	 * @throws SessionException
+	 *             if some error occurs on the server
+	 * @throws SessionServerException
+	 *             if the server is unreachable
+	 * @throws SessionMaintenanceException
+	 *             if the game is under maintenance
+	 */
 	@RequestMapping( "/overview" )
 	public String overview( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model )
 			throws SessionException , SessionServerException , SessionMaintenanceException
@@ -36,6 +72,30 @@ public class OverviewPage
 	}
 
 
+	/**
+	 * "Implement technology" command
+	 * 
+	 * <p>
+	 * This method is mapped to the technology implementation command URL.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * @param language
+	 *            the language from the session
+	 * @param model
+	 *            the model
+	 * @param tech
+	 *            the technology identifier
+	 * 
+	 * @return the overview page rendering order
+	 * 
+	 * @throws SessionException
+	 *             if some error occurs on the server
+	 * @throws SessionServerException
+	 *             if the server is unreachable
+	 * @throws SessionMaintenanceException
+	 *             if the game is under maintenance
+	 */
 	@RequestMapping( value = "/implement-{tech}.action" , method = RequestMethod.POST )
 	public String implement( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model ,
 			@PathVariable String tech )
@@ -52,4 +112,67 @@ public class OverviewPage
 		return this.render( model , "game" , language , "overview" , pSession.implementTechnology( techId ) );
 	}
 
+
+	@RequestMapping( value = "/update-mining-settings.action" , method = RequestMethod.POST )
+	public String updateMiningSettings( HttpServletRequest request , @ModelAttribute( "language" ) String language ,
+			Model model )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		Map< String , Integer > miningSettings = this.getMiningSettings( request );
+		if ( miningSettings != null ) {
+			this.getSession( PlayerSession.class , request ).updateEmpireMiningSettings( miningSettings );
+		}
+		return this.redirect( "overview#economy" );
+	}
+
+
+	/**
+	 * Extract mining priorities from the HTTP request
+	 * 
+	 * <p>
+	 * Look for all submitted fields that begin with "ems-" then try to extract their values into a
+	 * map that associates resource identifiers to priorities.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * 
+	 * @return the map containing the submitted mining settings, or <code>null</code> if one of the
+	 *         values was incorrect.
+	 */
+	private Map< String , Integer > getMiningSettings( HttpServletRequest request )
+	{
+		Map< String , Object > input = this.getInput( request );
+		Map< String , Integer > miningSettings = new HashMap< String , Integer >( );
+		for ( Entry< String , Object > entry : input.entrySet( ) ) {
+			// Ignore items which are not mining settings
+			String name = entry.getKey( );
+			if ( !name.startsWith( "ems-" ) ) {
+				continue;
+			}
+			name = name.substring( 4 );
+
+			// Get values
+			if ( ! ( entry.getValue( ) instanceof String[] ) ) {
+				continue;
+			}
+			String[] values = (String[]) entry.getValue( );
+			if ( values.length < 1 ) {
+				continue;
+			}
+
+			// Pre-validate them
+			int value;
+			try {
+				value = Integer.parseInt( values[ 0 ] );
+			} catch ( NumberFormatException e ) {
+				value = -1;
+			}
+			if ( value < 0 || value > 4 ) {
+				return null;
+			}
+
+			miningSettings.put( name , value );
+		}
+		return miningSettings;
+	}
 }
diff --git a/legacyworlds/dev-tools/run-database-tests.sh b/legacyworlds/dev-tools/run-database-tests.sh
new file mode 100755
index 0000000..cfaf812
--- /dev/null
+++ b/legacyworlds/dev-tools/run-database-tests.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+[ -z "$3" ] && {
+	echo "Syntax: $0 test-database admin-user test-user [misc]" >&2
+	echo
+	echo "Where misc is:"
+	echo "       --no-create	don't create the DB"
+	echo "       --run-name name	run tests matching '*name*'"
+	echo
+	exit 1
+}
+
+test_db="$1"
+admin_user="$2"
+test_user="$3"
+
+create_db=1
+run_name=""
+while ! [ -z "$4" ]; do
+	if [ "x$4" = "x--no-create" ]; then
+		create_db=0
+	elif [ "x$4" = "x--run-name" ]; then
+		shift
+		run_name="$4"
+	fi
+
+	shift
+done
+
+scriptdir="`dirname $0`"
+tempdir="`mktemp -d`"
+cp -Rap $scriptdir/../../legacyworlds-server-data/db-structure/* $tempdir/
+cat > $tempdir/db-config.txt <<EOF
+admin=$admin_user
+db=$test_db
+user=$test_user
+password=$test_user
+EOF
+
+(
+	cd $tempdir
+
+	if [ $create_db -eq 1 ]; then
+		echo "Creating DB..."
+		psql -vQUIET=1 -vON_ERROR_STOP=1 --file database.sql || exit 1
+		psql -vQUIET=1 -f tests/pgtap.sql $test_db || exit 1
+	fi
+
+	cd tests
+	if [ "x$run_name" = "x" ]; then
+		run_name='*.sql'
+	else
+		run_name='*'"$run_name"'*'
+	fi
+	pg_prove -d $test_db `find admin/ -type f -name "$run_name" | sort` || exit 1
+	pg_prove -U $test_user -d $test_db `find user/ -type f -name "$run_name" | sort` || exit 1
+)
+result=$?
+
+rm -rf $tempdir
+exit $result

From 51b529a09fe221ac97fe023d00e034c9bbf43652 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 7 Feb 2012 09:42:03 +0100
Subject: [PATCH 44/94] Removed TODO file

* Since we're now using tasks.legacyworlds.com, the TODO file is no
longer useful.
---
 legacyworlds/doc/TODO.txt | 45 ---------------------------------------
 1 file changed, 45 deletions(-)
 delete mode 100644 legacyworlds/doc/TODO.txt

diff --git a/legacyworlds/doc/TODO.txt b/legacyworlds/doc/TODO.txt
deleted file mode 100644
index 6c64d19..0000000
--- a/legacyworlds/doc/TODO.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-NOTES:
- -> This is *NOT* a complete list. Some of the tasks here will be decomposed
-    later and new tasks will be added as we go.
- -> If you feel like trying to take on some task, talk to me about it.
- -> Tasks that start with '!' are urgent, tasks that start with '?' are
-    low-priority.
-
-
-PROJECT:
-
-  * Update all dependencies to the latest versions
-
-  
-SERVER & DATABASE:
-
-  ! Add some form of database version control to allow easier updates
-		-> existing options were investigated, they are unsatisfactory
-
-  ! SQL code clean-up:
-	  * Replace all single-precision reals with double precision reals
-	  * Make sure internal functions cannot be called by the main user
-	  * Make sure functions that are supposed to be executed by the main
-	    user are not public
-	  * Rename all views to v_*
-	  * Rename all table fields to use a prefix
-
-  * Add a tool to initialise the database
-
-  * Replace current authentication information (pair of hashes) with a
-	salted SHA512 hash.
-		-> Make sure it is still possible to import old passwords using the
-			new implementation.
-
-  ? Mailer configuration shouldn't be hardcoded
-
-				
-GENERAL:
-
-  ! Add comments wherever necessary
-	-> that would be "everywhere"
-	
-  * Write unit tests
-	  * Write unit tests for all new Java code
-	  * Write unit tests for all new SQL code
-	  ? add more tests if possible	

From e64f847ec3c24c52c8ff946982bab19ba64c7bb7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 7 Feb 2012 17:03:55 +0100
Subject: [PATCH 45/94] Planet mining settings interface

* Modified owned planet view to include a field which indicates whether
mining settings are specific to the planet or come from the empire;
modified "simple" game components accordingly

* Modified stored procedures to only allow planet-specific mining
settings updates when the planet actually uses planet-specific settings,
and added a stored procedure which toggles the source of a planet's
settings

* Added corresponding parts to mining settings DAO and resources
controller

* Added session commands and server command handlers that toggle the
source of the settings and that upload the settings

* Split planet page template into multiple files for clarity and added
the mining priorities form to the natural resources tab
---
 .../game/resources/MiningSettingsDAOBean.java |  31 +++++
 .../resources/ResourcesControllerBean.java    |  45 ++++++-
 .../deepclone/lw/beans/map/PlanetDAOBean.java |   1 +
 .../lw/beans/map/PlanetsManagementBean.java   |   1 +
 ...ggleMiningSettingsCommandDelegateBean.java |  73 ++++++++++++
 ...anetMiningSettingsCommandDelegateBean.java |  72 +++++++++++
 .../configuration/session-types/player.xml    |   2 +
 .../parts/040-functions/045-empire-mining.sql |  79 +++++++++++-
 .../parts/040-functions/140-planets.sql       |  40 +++++--
 .../015-mset-update-start-planet.sql          |  14 ++-
 .../040-mset-toggle-source.sql                |  61 ++++++++++
 .../040-mset-toggle-source.sql                |  15 +++
 .../deepclone/lw/sqld/game/PlanetData.java    |  53 +++++----
 .../game/resources/MiningSettingsDAO.java     |  37 +++++-
 .../game/resources/ResourcesController.java   |  32 +++++
 .../game/resources/MockMiningSettingsDAO.java |  65 +++++++++-
 .../TestResourcesControllerBean.java          |  44 ++++++-
 .../planets/ToggleMiningSettingsCommand.java  |  38 ++++++
 .../UpdatePlanetMiningSettingsCommand.java    |  56 +++++++++
 .../deepclone/lw/web/csess/PlayerSession.java |  30 +++++
 .../Raw/WEB-INF/fm/en/types/planet.ftl        |  51 +-------
 .../Raw/WEB-INF/fm/en/types/planet/natres.ftl |  79 ++++++++++++
 .../Raw/WEB-INF/fm/fr/types/planet.ftl        |  50 +-------
 .../Raw/WEB-INF/fm/fr/types/planet/natres.ftl |  80 +++++++++++++
 .../lw/web/main/game/PlanetPage.java          | 112 +++++++++++++++++-
 25 files changed, 1009 insertions(+), 152 deletions(-)
 create mode 100644 legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ToggleMiningSettingsCommandDelegateBean.java
 create mode 100644 legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/UpdatePlanetMiningSettingsCommandDelegateBean.java
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/040-mset-toggle-source.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/040-mset-toggle-source.sql
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ToggleMiningSettingsCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/UpdatePlanetMiningSettingsCommand.java
 create mode 100644 legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl
 create mode 100644 legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet/natres.ftl

diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/MiningSettingsDAOBean.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/MiningSettingsDAOBean.java
index 57047b9..e2585f3 100644
--- a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/MiningSettingsDAOBean.java
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/MiningSettingsDAOBean.java
@@ -27,12 +27,18 @@ class MiningSettingsDAOBean
 	/** <code>emp.mset_update_start(INT)</code> stored procedure */
 	private StoredProc pStartEmpireUpdate;
 
+	/** <code>emp.mset_update_start(INT,INT)</code> stored procedure */
+	private StoredProc pStartPlanetUpdate;
+
 	/** <code>emp.mset_update_set(TEXT,INT)</code> stored procedure */
 	private StoredProc pSetMiningPriority;
 
 	/** <code>emp.mset_update_apply()</code> stored procedure */
 	private StoredProc pApplyUpdate;
 
+	/** <code>emp.mset_toggle_source(INT,INT)</code> stored procedure */
+	private StoredProc pToggleSource;
+
 
 	/**
 	 * Dependency injector that sets the data source
@@ -47,6 +53,11 @@ class MiningSettingsDAOBean
 		this.pStartEmpireUpdate.addParameter( "_empire" , Types.INTEGER );
 		this.pStartEmpireUpdate.addOutput( "_success" , Types.BOOLEAN );
 
+		this.pStartPlanetUpdate = new StoredProc( dataSource , "emp" , "mset_update_start" );
+		this.pStartPlanetUpdate.addParameter( "_empire" , Types.INTEGER );
+		this.pStartPlanetUpdate.addParameter( "_planet" , Types.INTEGER );
+		this.pStartPlanetUpdate.addOutput( "_success" , Types.BOOLEAN );
+
 		this.pSetMiningPriority = new StoredProc( dataSource , "emp" , "mset_update_set" );
 		this.pSetMiningPriority.addParameter( "_resource" , Types.VARCHAR );
 		this.pSetMiningPriority.addParameter( "_priority" , Types.INTEGER );
@@ -54,6 +65,10 @@ class MiningSettingsDAOBean
 
 		this.pApplyUpdate = new StoredProc( dataSource , "emp" , "mset_update_apply" );
 		this.pApplyUpdate.addOutput( "_success" , Types.BOOLEAN );
+
+		this.pToggleSource = new StoredProc( dataSource , "emp" , "mset_toggle_source" );
+		this.pToggleSource.addParameter( "_empire" , Types.INTEGER );
+		this.pToggleSource.addParameter( "_planet" , Types.INTEGER );
 	}
 
 
@@ -65,6 +80,14 @@ class MiningSettingsDAOBean
 	}
 
 
+	/* Documented in interface */
+	@Override
+	public boolean startUpdate( int empire , int planet )
+	{
+		return (Boolean) this.pStartPlanetUpdate.execute( empire , planet ).get( "_success" );
+	}
+
+
 	/* Documented in interface */
 	@Override
 	public boolean setNewPriority( String resource , int priority )
@@ -80,4 +103,12 @@ class MiningSettingsDAOBean
 		return (Boolean) this.pApplyUpdate.execute( ).get( "_success" );
 	}
 
+
+	/* Documented in interface */
+	@Override
+	public void togglePlanet( int empire , int planet )
+	{
+		this.pToggleSource.execute( empire , planet );
+	}
+
 }
diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesControllerBean.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesControllerBean.java
index a2b8040..1829713 100644
--- a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesControllerBean.java
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesControllerBean.java
@@ -52,12 +52,51 @@ class ResourcesControllerBean
 	@Override
 	public void updateEmpireSettings( int empireId , Map< String , Integer > settings )
 	{
-		if ( !this.settings.startUpdate( empireId ) ) {
-			return;
+		if ( this.settings.startUpdate( empireId ) ) {
+			this.sendMiningSettings( settings );
 		}
+	}
 
+
+	/* Documented in interface */
+	@Override
+	public void togglePlanet( int empire , int planet )
+	{
+		this.settings.togglePlanet( empire , planet );
+	}
+
+
+	/**
+	 * Update a planet's mining settings
+	 * 
+	 * <p>
+	 * Start a mining settings update for the specified planet / empire combination, then set each
+	 * resource-specific priority, then apply the update. Exit whenever something goes wrong.
+	 */
+	@Override
+	public void updatePlanetSettings( int empire , int planet , Map< String , Integer > settings )
+	{
+		if ( this.settings.startUpdate( empire , planet ) ) {
+			this.sendMiningSettings( settings );
+		}
+	}
+
+
+	/**
+	 * Send mining settings to the database
+	 * 
+	 * <p>
+	 * This method is called once a mining settings update has been started (whether it's a
+	 * planet-specific or empire-wide update). It sends all settings to the database server, and
+	 * aborts if anything goes wrong.
+	 * 
+	 * @param settings
+	 *            the settings to upload
+	 */
+	private void sendMiningSettings( Map< String , Integer > settings )
+	{
 		for ( Map.Entry< String , Integer > entry : settings.entrySet( ) ) {
-			if ( ! this.settings.setNewPriority( entry.getKey( ) , entry.getValue( ) ) ) {
+			if ( !this.settings.setNewPriority( entry.getKey( ) , entry.getValue( ) ) ) {
 				return;
 			}
 		}
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
index 8deb049..e9a1cf9 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetDAOBean.java
@@ -101,6 +101,7 @@ public class PlanetDAOBean
 				info.setRenamePossible( rs.getBoolean( "can_rename" ) );
 				info.setAbandonPossible( rs.getBoolean( "can_abandon" ) );
 				info.setAbandonTime( rs.getInt( "abandon_time" ) );
+				info.setOwnSettings( rs.getBoolean( "specific_mining_settings" ) );
 				return info;
 			}
 		};
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
index 0e364ca..8d5bf13 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/map/PlanetsManagementBean.java
@@ -109,6 +109,7 @@ public class PlanetsManagementBean
 		view.sethChange( data.gethChange( ) );
 		view.setIncome( data.getIncome( ) );
 		view.setUpkeep( data.getUpkeep( ) );
+		view.setOwnMiningSettings( data.isOwnSettings( ) );
 
 		view.setCivQueue( this.planetDao.getConstructionQueue( planetId ) );
 		view.setMilQueue( this.planetDao.getMilitaryQueue( planetId ) );
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ToggleMiningSettingsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ToggleMiningSettingsCommandDelegateBean.java
new file mode 100644
index 0000000..f4a951a
--- /dev/null
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/ToggleMiningSettingsCommandDelegateBean.java
@@ -0,0 +1,73 @@
+package com.deepclone.lw.beans.user.player.game.planets;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.player.GameSubTypeBean;
+import com.deepclone.lw.cmd.player.planets.ToggleMiningSettingsCommand;
+import com.deepclone.lw.cmd.player.planets.ViewPlanetCommand;
+import com.deepclone.lw.interfaces.game.resources.ResourcesController;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+import com.deepclone.lw.session.NullResponse;
+
+
+
+/**
+ * Command handler for {@link ToggleMiningSettingsCommand}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class ToggleMiningSettingsCommandDelegateBean
+		implements AutowiredCommandDelegate
+{
+
+	/** The resources controller */
+	private ResourcesController resourcesController;
+
+
+	/**
+	 * Dependency injector that sets the resources controller
+	 * 
+	 * @param resourcesController
+	 *            the resources controller
+	 */
+	@Autowired( required = true )
+	public void setResourcesController( ResourcesController resourcesController )
+	{
+		this.resourcesController = resourcesController;
+	}
+
+
+	/** This class handles {@link ToggleMiningSettingsCommand} instances */
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return ToggleMiningSettingsCommand.class;
+	}
+
+
+	/** This class is enabled for the {@link GameSubTypeBean} session type */
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return GameSubTypeBean.class;
+	}
+
+
+	/** If the account is not in vacation mode, try to toggle the planet's settings */
+	@Override
+	public CommandResponse execute( ServerSession session , Command command )
+	{
+		if ( !session.get( "vacation" , Boolean.class ) ) {
+			int empireId = session.get( "empireId" , Integer.class );
+			ViewPlanetCommand planet = (ViewPlanetCommand) command;
+			this.resourcesController.togglePlanet( empireId , planet.getId( ) );
+		}
+		return new NullResponse( );
+	}
+
+}
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/UpdatePlanetMiningSettingsCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/UpdatePlanetMiningSettingsCommandDelegateBean.java
new file mode 100644
index 0000000..1a7d954
--- /dev/null
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/planets/UpdatePlanetMiningSettingsCommandDelegateBean.java
@@ -0,0 +1,72 @@
+package com.deepclone.lw.beans.user.player.game.planets;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.player.GameSubTypeBean;
+import com.deepclone.lw.cmd.player.planets.UpdatePlanetMiningSettingsCommand;
+import com.deepclone.lw.interfaces.game.resources.ResourcesController;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+import com.deepclone.lw.session.NullResponse;
+
+
+
+/**
+ * Command handler for {@link UpdatePlanetMiningSettingsCommand}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class UpdatePlanetMiningSettingsCommandDelegateBean
+		implements AutowiredCommandDelegate
+{
+
+	/** The resources controller */
+	private ResourcesController resourcesController;
+
+
+	/**
+	 * Dependency injector that sets the resources controller
+	 * 
+	 * @param resourcesController
+	 *            the resources controller
+	 */
+	@Autowired( required = true )
+	public void setResourcesController( ResourcesController resourcesController )
+	{
+		this.resourcesController = resourcesController;
+	}
+
+
+	/** This class handles {@link UpdatePlanetMiningSettingsCommand} instances */
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return UpdatePlanetMiningSettingsCommand.class;
+	}
+
+
+	/** This class is enabled for the {@link GameSubTypeBean} session type */
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return GameSubTypeBean.class;
+	}
+
+
+	/** If the empire is not in vacation mode, try updating the planet's mining priorities */
+	@Override
+	public CommandResponse execute( ServerSession session , Command command )
+	{
+		if ( !session.get( "vacation" , Boolean.class ) ) {
+			UpdatePlanetMiningSettingsCommand settings = (UpdatePlanetMiningSettingsCommand) command;
+			int empireId = session.get( "empireId" , Integer.class );
+			this.resourcesController.updatePlanetSettings( empireId , settings.getId( ) , settings.getSettings( ) );
+		}
+		return new NullResponse( );
+	}
+
+}
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
index 203a345..ff9a382 100644
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
+++ b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
@@ -61,6 +61,8 @@
 	<bean class="com.deepclone.lw.beans.user.player.game.planets.FlushQueueCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.player.game.planets.RenamePlanetCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.player.game.planets.AbandonPlanetCommandDelegateBean" />
+	<bean class="com.deepclone.lw.beans.user.player.game.planets.ToggleMiningSettingsCommandDelegateBean" />
+	<bean class="com.deepclone.lw.beans.user.player.game.planets.UpdatePlanetMiningSettingsCommandDelegateBean" />
 
 	<!-- Game: enemy list -->
 	<bean class="com.deepclone.lw.beans.user.player.game.elist.EnemyListCommandDelegateBean" />
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
index d8b4f04..8704d68 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
@@ -69,8 +69,9 @@ GRANT EXECUTE
  *		_planet_id		The planet's identifier
  *
  * Returns:
- *		?				True if the empire exists and owns the planet, false
- *							otherwise
+ *		?				True if the empire exists and owns the planet and if
+ *							the planet is using planet-specific settings,
+ *							false otherwise
  */
 DROP FUNCTION IF EXISTS emp.mset_update_start( INT , INT );
 CREATE FUNCTION emp.mset_update_start( _empire_id INT , _planet_id INT )
@@ -95,9 +96,14 @@ BEGIN
 				ON _planet.name_id = _emp_planet.planet_id
 			INNER JOIN verse.resource_providers _resprov
 				ON _resprov.planet_id = _planet.name_id
+			INNER JOIN emp.planet_mining_settings _mset
+				ON _mset.planet_id = _planet.name_id
+					AND _mset.empire_id = _empire.name_id
+					AND _mset.resource_name_id = _resprov.resource_name_id
 		WHERE _empire.name_id = _empire_id
 				AND _planet.name_id = _planet_id
-		FOR SHARE OF _empire, _emp_planet , _planet , _resprov;
+		FOR SHARE OF _empire, _emp_planet , _planet , _resprov
+		FOR UPDATE OF _mset;
 	IF NOT FOUND THEN
 		RETURN FALSE;
 	END IF;
@@ -239,4 +245,71 @@ REVOKE EXECUTE
 	FROM PUBLIC;
 GRANT EXECUTE
 	ON FUNCTION emp.mset_update_apply( )
+	TO :dbuser;
+
+
+/*
+ * Toggle the source of a planet's mining settings
+ * ------------------------------------------------
+ * 
+ * This function causes a planet to be switched between empire-wide and
+ * planet-specific mining settings.
+ * 
+ * Parameters:
+ *		_empire		The empire's identifier
+ *		_planet		The planet's identifier
+ *
+ * Returns:
+ *		?			TRUE if the operation succeeded, FALSE if the planet
+ *						didn't exist or wasn't owned by the specified empire.
+ */
+DROP FUNCTION IF EXISTS emp.mset_toggle_source( INT , INT );
+CREATE FUNCTION emp.mset_toggle_source( _empire INT , _planet INT )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $mset_toggle_source$
+BEGIN
+	PERFORM 1
+		FROM emp.planets _ep
+			INNER JOIN verse.resource_providers _rp
+				USING ( planet_id )
+			LEFT OUTER JOIN (
+				SELECT * FROM emp.planet_mining_settings _pms
+					WHERE planet_id = _planet
+						AND empire_id = _empire
+					FOR UPDATE
+				) _pms USING ( planet_id , empire_id , resource_name_id )
+		WHERE _ep.empire_id = _empire
+			AND _ep.planet_id = _planet
+		FOR UPDATE OF _ep;
+	IF NOT FOUND THEN
+		RETURN FALSE;
+	END IF;
+
+	PERFORM 1 FROM emp.planet_mining_settings _pms
+		WHERE planet_id = _planet AND empire_id = _empire;
+	IF FOUND THEN
+		DELETE FROM emp.planet_mining_settings
+			WHERE planet_id = _planet AND empire_id = _empire;
+	ELSE
+		INSERT INTO emp.planet_mining_settings(
+				empire_id , planet_id , resource_name_id , emppmset_weight
+			) SELECT empire_id  , planet_id , resource_name_id , empmset_weight
+				FROM verse.resource_providers _rp
+					INNER JOIN emp.mining_settings
+						USING ( resource_name_id )
+				WHERE planet_id = _planet AND empire_id = _empire;
+	END IF;
+	
+	RETURN TRUE;
+END;
+$mset_toggle_source$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.mset_toggle_source( INT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION emp.mset_toggle_source( INT , INT )
 	TO :dbuser;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql b/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
index 53bca46..778b57d 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
@@ -36,14 +36,16 @@ CREATE TYPE planet_orbital_data AS (
 
 
 -- Planet owner view
-CREATE TYPE planet_owner_data AS (
-	happiness		INT ,
-	h_change		INT ,
-	income			BIGINT ,
-	upkeep			BIGINT ,
-	can_rename		BOOLEAN ,
-	can_abandon		BOOLEAN ,
-	abandon_time	INT
+DROP TYPE IF EXISTS emp.planet_owner_data;
+CREATE TYPE emp.planet_owner_data AS (
+	happiness					INT ,
+	h_change					INT ,
+	income						BIGINT ,
+	upkeep						BIGINT ,
+	specific_mining_settings	BOOLEAN ,
+	can_rename					BOOLEAN ,
+	can_abandon					BOOLEAN ,
+	abandon_time				INT
 );
 
 
@@ -243,26 +245,33 @@ GRANT EXECUTE ON FUNCTION verse.get_orbital_view( INT , INT ) TO :dbuser;
 --	an owner planet view entry
 --
 
+DROP FUNCTION IF EXISTS verse.get_owner_view( INT , INT ) CASCADE;
 CREATE OR REPLACE FUNCTION verse.get_owner_view( e_id INT , p_id INT )
-		RETURNS planet_owner_data
+		RETURNS emp.planet_owner_data
 		STRICT STABLE
 		SECURITY DEFINER
 	AS $$
 DECLARE
-	rv		planet_owner_data;
+	rv		emp.planet_owner_data;
 	t_happ	INT;
 	h_chg	INT;
 	mdelay	BIGINT;
 	r_time	INTERVAL;
 BEGIN
 	-- Get income, upkeep, current and target happiness
-	SELECT INTO rv.income , rv.upkeep , rv.happiness , t_happ
+	SELECT INTO rv.income , rv.upkeep , rv.happiness , t_happ , rv.specific_mining_settings
 			floor( pm.income )::INT , floor( pm.upkeep )::INT ,
 			floor( 100 * ph.current / p.population )::INT ,
-			floor( 100 * ph.target )::INT
+			floor( 100 * ph.target )::INT ,
+			_count.settings_exist
 		FROM verse.planets p
 			INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
 			INNER JOIN verse.planet_money pm ON pm.planet_id = p.name_id
+			CROSS JOIN (
+				SELECT ( COUNT( * ) > 0 ) AS settings_exist
+					FROM emp.planet_mining_settings
+					WHERE planet_id = p_id AND empire_id = e_id
+			) _count
 		WHERE p.name_id = p_id;
 
 	-- Compute happiness change indicator
@@ -306,7 +315,12 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION verse.get_owner_view( INT , INT ) TO :dbuser;
+REVOKE EXECUTE
+	ON FUNCTION verse.get_owner_view( INT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION verse.get_owner_view( INT , INT )
+	TO :dbuser;
 
 
 
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
index 31536c8..fa25e41 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
@@ -22,7 +22,7 @@ BEGIN;
 		VALUES ( _get_emp_name( 'testEmp1' ) , 	_get_map_name( 'testPlanet3' ) );
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 14 );
+	SELECT plan( 16 );
 
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on bad empire identifier' );
 	SELECT ok( NOT emp.mset_update_start( _get_bad_emp_name( ) , _get_map_name( 'testPlanet1' ) ) );
@@ -55,7 +55,17 @@ BEGIN;
 	DROP TABLE IF EXISTS mset_update;
 
 	
-	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on valid identifiers' );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on valid identifiers but no existing settings' );
+	SELECT ok( NOT emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet1' ) ) );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists despite lack of settings' );
+	SELECT has_table( 'mset_update' );
+	DROP TABLE IF EXISTS mset_update;
+
+	INSERT INTO emp.planet_mining_settings( empire_id , planet_id , resource_name_id )
+		SELECT _get_emp_name( 'testEmp1' ) , planet_id , resource_name_id
+			FROM verse.resource_providers
+			WHERE planet_id = _get_map_name( 'testPlanet1' );
+	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Return value on valid identifiers and existing settings' );
 	SELECT ok( emp.mset_update_start( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet1' ) ) );
 	SELECT diag_test_name( 'emp.mset_update_start( INT , INT ) - Temporary table exists' );
 	SELECT has_table( 'mset_update' );
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/040-mset-toggle-source.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/040-mset-toggle-source.sql
new file mode 100644
index 0000000..1765070
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/040-mset-toggle-source.sql
@@ -0,0 +1,61 @@
+/*
+ * Test the emp.mset_toggle_source( INT , INT ) function
+ */
+BEGIN;
+	/*
+	 * Create a pair of natural resources, three planets, an empire owning two
+	 * of the planets, and resource providers for one of the resources on the
+	 * neutral planet and on one of the owned planets.
+	 */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'resource' );
+	SELECT _create_raw_planets( 3 , 'planet' );
+	SELECT _create_emp_names( 1 , 'empire' );
+	SELECT emp.create_empire( _get_emp_name( 'empire1' ) ,
+				_get_map_name( 'planet1' ) ,
+				200.0 );
+	SELECT _create_resource_provider( 'planet1' , 'resource1' );
+	SELECT _create_resource_provider( 'planet2' , 'resource1' );
+	INSERT INTO emp.planets ( empire_id , planet_id )
+		VALUES ( _get_emp_name( 'empire1' ) , 	_get_map_name( 'planet3' ) );
+
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 8 );
+	
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Return value on bad empire identifier' );
+	SELECT ok( NOT emp.mset_toggle_source( _get_bad_emp_name( ) , _get_map_name( 'planet1' ) ) );
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Return value on bad planbet identifier' );
+	SELECT ok( NOT emp.mset_toggle_source( _get_emp_name( 'empire1' ) , _get_bad_map_name( ) ) );
+
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Return value when the empire does not own the planet' );
+	SELECT ok( NOT emp.mset_toggle_source( _get_emp_name( 'empire1' ) , _get_map_name( 'planet2' ) ) );
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Return value when the planet has no resource providers' );
+	SELECT ok( NOT emp.mset_toggle_source( _get_emp_name( 'empire1' ) , _get_map_name( 'planet3' ) ) );
+
+	DELETE FROM emp.planet_mining_settings;
+	UPDATE emp.mining_settings
+		SET empmset_weight = 0;
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Return value when activating' );
+	SELECT ok( emp.mset_toggle_source( _get_emp_name( 'empire1' ) , _get_map_name( 'planet1' ) ) );
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Mining settings inserted after activation' );
+	SELECT set_eq(
+		$$ SELECT * FROM emp.planet_mining_settings $$ ,
+		$$ VALUES ( _get_emp_name( 'empire1' ) , _get_map_name( 'planet1' ) , _get_string( 'resource1' ) , 0 ) $$
+	);
+
+	DELETE FROM emp.planet_mining_settings;
+	INSERT INTO emp.planet_mining_settings VALUES (
+		_get_emp_name( 'empire1' ) , _get_map_name( 'planet1' ) , _get_string( 'resource1' ) , 2
+	);
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Return value when de-activating' );
+	SELECT ok( emp.mset_toggle_source( _get_emp_name( 'empire1' ) , _get_map_name( 'planet1' ) ) );
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Mining settings deleted after de-activation' );
+	SELECT is_empty( $$ SELECT * FROM emp.planet_mining_settings $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/040-mset-toggle-source.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/040-mset-toggle-source.sql
new file mode 100644
index 0000000..fcb9e61
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/040-mset-toggle-source.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on emp.mset_toggle_source( INT , INT )
+ */
+BEGIN;
+	SELECT emp.mset_update_start( 1 );
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.mset_toggle_source() - Privileges' );
+	SELECT lives_ok( $$
+			SELECT emp.mset_toggle_source( 1 , 1 )
+		$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java
index 1b47231..4200a91 100644
--- a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java
+++ b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/PlanetData.java
@@ -22,7 +22,7 @@ public final class PlanetData
 
 		public AccessType getAccess( )
 		{
-			return access;
+			return this.access;
 		}
 
 
@@ -34,7 +34,7 @@ public final class PlanetData
 
 		public int getX( )
 		{
-			return x;
+			return this.x;
 		}
 
 
@@ -46,7 +46,7 @@ public final class PlanetData
 
 		public int getY( )
 		{
-			return y;
+			return this.y;
 		}
 
 
@@ -58,7 +58,7 @@ public final class PlanetData
 
 		public int getOrbit( )
 		{
-			return orbit;
+			return this.orbit;
 		}
 
 
@@ -70,7 +70,7 @@ public final class PlanetData
 
 		public int getPicture( )
 		{
-			return picture;
+			return this.picture;
 		}
 
 
@@ -82,7 +82,7 @@ public final class PlanetData
 
 		public String getName( )
 		{
-			return name;
+			return this.name;
 		}
 
 
@@ -94,7 +94,7 @@ public final class PlanetData
 
 		public String getTag( )
 		{
-			return tag;
+			return this.tag;
 		}
 
 
@@ -117,7 +117,7 @@ public final class PlanetData
 
 		public long getPopulation( )
 		{
-			return population;
+			return this.population;
 		}
 
 
@@ -129,7 +129,7 @@ public final class PlanetData
 
 		public long getDefence( )
 		{
-			return defence;
+			return this.defence;
 		}
 
 
@@ -141,7 +141,7 @@ public final class PlanetData
 
 		public long getOwnPower( )
 		{
-			return ownPower;
+			return this.ownPower;
 		}
 
 
@@ -153,7 +153,7 @@ public final class PlanetData
 
 		public long getFriendlyPower( )
 		{
-			return friendlyPower;
+			return this.friendlyPower;
 		}
 
 
@@ -165,7 +165,7 @@ public final class PlanetData
 
 		public long getHostilePower( )
 		{
-			return hostilePower;
+			return this.hostilePower;
 		}
 
 
@@ -177,7 +177,7 @@ public final class PlanetData
 
 		public Long getBattle( )
 		{
-			return battle;
+			return this.battle;
 		}
 
 
@@ -197,11 +197,12 @@ public final class PlanetData
 		private boolean renamePossible;
 		private boolean abandonPossible;
 		private Integer abandonTime;
+		private boolean ownSettings;
 
 
 		public int getHappiness( )
 		{
-			return happiness;
+			return this.happiness;
 		}
 
 
@@ -213,7 +214,7 @@ public final class PlanetData
 
 		public int gethChange( )
 		{
-			return hChange;
+			return this.hChange;
 		}
 
 
@@ -225,7 +226,7 @@ public final class PlanetData
 
 		public long getIncome( )
 		{
-			return income;
+			return this.income;
 		}
 
 
@@ -237,7 +238,7 @@ public final class PlanetData
 
 		public long getUpkeep( )
 		{
-			return upkeep;
+			return this.upkeep;
 		}
 
 
@@ -249,7 +250,7 @@ public final class PlanetData
 
 		public boolean isRenamePossible( )
 		{
-			return renamePossible;
+			return this.renamePossible;
 		}
 
 
@@ -261,7 +262,7 @@ public final class PlanetData
 
 		public boolean isAbandonPossible( )
 		{
-			return abandonPossible;
+			return this.abandonPossible;
 		}
 
 
@@ -273,7 +274,7 @@ public final class PlanetData
 
 		public Integer getAbandonTime( )
 		{
-			return abandonTime;
+			return this.abandonTime;
 		}
 
 
@@ -282,6 +283,18 @@ public final class PlanetData
 			this.abandonTime = abandonTime;
 		}
 
+
+		public boolean isOwnSettings( )
+		{
+			return this.ownSettings;
+		}
+
+
+		public void setOwnSettings( boolean ownSettings )
+		{
+			this.ownSettings = ownSettings;
+		}
+
 	}
 
 
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/MiningSettingsDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/MiningSettingsDAO.java
index c19656e..4281198 100644
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/MiningSettingsDAO.java
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/MiningSettingsDAO.java
@@ -20,12 +20,30 @@ public interface MiningSettingsDAO
 	 * Start an empire settings update by calling the <code>emp.mset_update_start(INT)</code> stored
 	 * procedure.
 	 * 
-	 * @param empireId
+	 * @param empire
 	 *            the identifier of the empire whose settings will be updated
 	 * 
 	 * @return <code>true</code> on success, <code>false</code> on failure
 	 */
-	public boolean startUpdate( int empireId );
+	public boolean startUpdate( int empire );
+
+
+	/**
+	 * Start a planet settings update
+	 * 
+	 * <p>
+	 * Start a planet settings update by calling the <code>emp.mset_update_start(INT,INT)</code>
+	 * stored procedure.
+	 * 
+	 * @param empire
+	 *            the identifier of the empire who supposedly owns the planet
+	 * @param planet
+	 *            the identifier of the planet whose settings will be changed
+	 * 
+	 * @return <code>true</code> if the planet belongs to the specified empire and has specific
+	 *         mining settings
+	 */
+	public boolean startUpdate( int empire , int planet );
 
 
 	/**
@@ -57,4 +75,19 @@ public interface MiningSettingsDAO
 	 *         invalid.
 	 */
 	public boolean applyUpdate( );
+
+
+	/**
+	 * Toggle the source of a planet's mining settings
+	 * 
+	 * <p>
+	 * Call the <code>emp.mset_toggle_source(INT,INT)</code> stored procedure to toggle the source
+	 * of a planet's mining priorities.
+	 * 
+	 * @param empire
+	 *            the empire's identifier
+	 * @param planet
+	 *            the planet's identifier
+	 */
+	public void togglePlanet( int empire , int planet );
 }
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesController.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesController.java
index 2444da7..940dc45 100644
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesController.java
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesController.java
@@ -27,4 +27,36 @@ public interface ResourcesController
 	 */
 	public void updateEmpireSettings( int empireId , Map< String , Integer > settings );
 
+
+	/**
+	 * Toggle the source of a planet's mining priorities
+	 * 
+	 * <p>
+	 * If the planet is currently using specific priorities, it will revert to empire-wide settings,
+	 * and vice-versa. The planet must belong to the specified empire.
+	 * 
+	 * @param empire
+	 *            the empire's identifier
+	 * @param planet
+	 *            the planet's identifier
+	 */
+	public void togglePlanet( int empire , int planet );
+
+
+	/**
+	 * Update a planet's mining settings
+	 * 
+	 * <p>
+	 * If the planet is using specific mining priorities and belongs to the specified empire, update
+	 * its mining priorities.
+	 * 
+	 * @param empire
+	 *            the empire's identifier
+	 * @param planet
+	 *            the planet's identifier
+	 * @param settings
+	 *            the new priorities
+	 */
+	public void updatePlanetSettings( int empire , int planet , Map< String , Integer > settings );
+
 }
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/MockMiningSettingsDAO.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/MockMiningSettingsDAO.java
index 0b46dce..330c0f2 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/MockMiningSettingsDAO.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/MockMiningSettingsDAO.java
@@ -15,15 +15,27 @@ import com.deepclone.lw.interfaces.game.resources.MiningSettingsDAO;
 public class MockMiningSettingsDAO
 		implements MiningSettingsDAO
 {
-	/** The empire identifier with which {@link #startUpdate(int)} was called */
-	private Integer updateEmpire = null;
+	/**
+	 * The empire identifier with which {@link #startUpdate(int)} or {@link #togglePlanet(int, int)}
+	 * was called
+	 */
+	private Integer empire = null;
+
+	/** The planet identifier with which {@link #togglePlanet(int, int)} was called */
+	private Integer planet = null;
 
 	/** The amount of calls to {@link #setNewPriority(String, int)} */
 	private int callsToSet = 0;
 
+	/** Whether {@link #startUpdate(int)} was called */
+	private boolean startUpdateCalled = false;
+
 	/** Whether {@link #applyUpdate()} was called */
 	private boolean applyCalled = false;
 
+	/** Whether {@link #togglePlanet(int, int)} was called */
+	private boolean togglePlanetCalled = false;
+
 	/** Whether {@link #startUpdate(int)} will succeed or fail */
 	private boolean startUpdateSucceeds = true;
 
@@ -37,10 +49,24 @@ public class MockMiningSettingsDAO
 	private Integer maxSetCalls = null;
 
 
-	/** @return the empire identifier to update */
-	public Integer getUpdateEmpire( )
+	/** @return the empire identifier */
+	public Integer getEmpire( )
 	{
-		return this.updateEmpire;
+		return this.empire;
+	}
+
+
+	/** @return the planet identifier */
+	public Integer getPlanet( )
+	{
+		return this.planet;
+	}
+
+
+	/** @return <code>true</code> if {@link #startUpdate(int)()} was called */
+	public boolean wasStartUpdateCalled( )
+	{
+		return this.startUpdateCalled;
 	}
 
 
@@ -58,6 +84,13 @@ public class MockMiningSettingsDAO
 	}
 
 
+	/** @return <code>true</code> if {@link #togglePlanet(int, int)} was called */
+	public boolean wasTogllePlanetCalled( )
+	{
+		return this.togglePlanetCalled;
+	}
+
+
 	/**
 	 * Determine whether calls to {@link #startUpdate(int)} will succeed
 	 * 
@@ -98,7 +131,18 @@ public class MockMiningSettingsDAO
 	@Override
 	public boolean startUpdate( int empireId )
 	{
-		this.updateEmpire = empireId;
+		this.startUpdateCalled = true;
+		this.empire = empireId;
+		return this.startUpdateSucceeds;
+	}
+
+
+	@Override
+	public boolean startUpdate( int empire , int planet )
+	{
+		this.startUpdateCalled = true;
+		this.empire = empire;
+		this.planet = planet;
 		return this.startUpdateSucceeds;
 	}
 
@@ -118,4 +162,13 @@ public class MockMiningSettingsDAO
 		return this.applyUpdateSucceeds;
 	}
 
+
+	@Override
+	public void togglePlanet( int empire , int planet )
+	{
+		this.togglePlanetCalled = true;
+		this.empire = empire;
+		this.planet = planet;
+	}
+
 }
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestResourcesControllerBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestResourcesControllerBean.java
index b836ab9..393b577 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestResourcesControllerBean.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestResourcesControllerBean.java
@@ -26,6 +26,9 @@ public class TestResourcesControllerBean
 	/** Empire identifier used in the tests */
 	private static final Integer EMPIRE_ID = 42;
 
+	/** Planet identifier used in the tests */
+	private static final Integer PLANET_ID = 43;
+
 	/** Mining settings used in the tests */
 	private static final Map< String , Integer > MINING_SETTINGS;
 
@@ -70,7 +73,25 @@ public class TestResourcesControllerBean
 	{
 		this.miningSettingsDAO.setStartUpdateSucceeds( false );
 		this.ctrl.updateEmpireSettings( EMPIRE_ID , MINING_SETTINGS );
-		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getUpdateEmpire( ) );
+		assertTrue( this.miningSettingsDAO.wasStartUpdateCalled( ) );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getEmpire( ) );
+		assertEquals( 0 , this.miningSettingsDAO.getCallsToSet( ) );
+		assertFalse( this.miningSettingsDAO.wasApplyCalled( ) );
+	}
+
+
+	/**
+	 * When calling {@link MiningSettingsDAO#startUpdate(int,int)} fails, the planet settings update
+	 * is interrupted.
+	 */
+	@Test
+	public void testPlanetStartUpdateFails( )
+	{
+		this.miningSettingsDAO.setStartUpdateSucceeds( false );
+		this.ctrl.updatePlanetSettings( EMPIRE_ID , PLANET_ID , MINING_SETTINGS );
+		assertTrue( this.miningSettingsDAO.wasStartUpdateCalled( ) );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getEmpire( ) );
+		assertEquals( PLANET_ID , this.miningSettingsDAO.getPlanet( ) );
 		assertEquals( 0 , this.miningSettingsDAO.getCallsToSet( ) );
 		assertFalse( this.miningSettingsDAO.wasApplyCalled( ) );
 	}
@@ -85,7 +106,8 @@ public class TestResourcesControllerBean
 	{
 		this.miningSettingsDAO.setMaxSetCalls( 2 );
 		this.ctrl.updateEmpireSettings( EMPIRE_ID , MINING_SETTINGS );
-		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getUpdateEmpire( ) );
+		assertTrue( this.miningSettingsDAO.wasStartUpdateCalled( ) );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getEmpire( ) );
 		assertEquals( 3 , this.miningSettingsDAO.getCallsToSet( ) );
 		assertFalse( this.miningSettingsDAO.wasApplyCalled( ) );
 	}
@@ -100,7 +122,8 @@ public class TestResourcesControllerBean
 	public void testSettingsSuccess( )
 	{
 		this.ctrl.updateEmpireSettings( EMPIRE_ID , MINING_SETTINGS );
-		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getUpdateEmpire( ) );
+		assertTrue( this.miningSettingsDAO.wasStartUpdateCalled( ) );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getEmpire( ) );
 		assertEquals( 4 , this.miningSettingsDAO.getCallsToSet( ) );
 		assertTrue( this.miningSettingsDAO.wasApplyCalled( ) );
 	}
@@ -114,8 +137,21 @@ public class TestResourcesControllerBean
 	{
 		this.miningSettingsDAO.setApplyUpdateSucceeds( false );
 		this.ctrl.updateEmpireSettings( EMPIRE_ID , MINING_SETTINGS );
-		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getUpdateEmpire( ) );
+		assertTrue( this.miningSettingsDAO.wasStartUpdateCalled( ) );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getEmpire( ) );
 		assertEquals( 4 , this.miningSettingsDAO.getCallsToSet( ) );
 		assertTrue( this.miningSettingsDAO.wasApplyCalled( ) );
 	}
+
+	/**
+	 * "Toggle planet source" calls {@link MiningSettingsDAO#togglePlanet(int, int)}
+	 */
+	@Test
+	public void tesTogglePlanet()
+	{
+		this.ctrl.togglePlanet( EMPIRE_ID , PLANET_ID );
+		assertTrue( this.miningSettingsDAO.wasTogllePlanetCalled( ) );
+		assertEquals( EMPIRE_ID , this.miningSettingsDAO.getEmpire( ) );
+		assertEquals( PLANET_ID , this.miningSettingsDAO.getPlanet( ) );
+	}
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ToggleMiningSettingsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ToggleMiningSettingsCommand.java
new file mode 100644
index 0000000..c15dc0e
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ToggleMiningSettingsCommand.java
@@ -0,0 +1,38 @@
+package com.deepclone.lw.cmd.player.planets;
+
+
+/**
+ * Command sent to toggle between empire-wide and planet-specific mining settings
+ * 
+ * <p>
+ * Sending this command to the server will try to switch between empire-wide and planet-specific
+ * settings on a given planet. The planet must be owned by the empire sending the command.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class ToggleMiningSettingsCommand
+		extends ViewPlanetCommand
+{
+
+	/**
+	 * Serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1.
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+
+	/**
+	 * Initialise the command
+	 * 
+	 * @param planet
+	 *            the planet's identifier
+	 */
+	public ToggleMiningSettingsCommand( int planet )
+	{
+		super( planet );
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/UpdatePlanetMiningSettingsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/UpdatePlanetMiningSettingsCommand.java
new file mode 100644
index 0000000..c29291c
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/UpdatePlanetMiningSettingsCommand.java
@@ -0,0 +1,56 @@
+package com.deepclone.lw.cmd.player.planets;
+
+
+import java.util.Map;
+
+
+
+/**
+ * Command that updates a planet's mining settings
+ * 
+ * <p>
+ * This command must be sent with the identifier of a planet that belongs to the current empire. If
+ * the planet doesn't exist, if its ownership changed, or if it's configured to use empire-wide
+ * settings, the command will be ignored.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class UpdatePlanetMiningSettingsCommand
+		extends ViewPlanetCommand
+{
+
+	/**
+	 * Serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** The new mining settings */
+	private final Map< String , Integer > settings;
+
+
+	/**
+	 * Initialise the command using a planet identifier and mining settings
+	 * 
+	 * @param planet
+	 *            identifier of the planet to update 
+	 * @param settings
+	 *            a map that associates resource identifiers to priorities
+	 */
+	public UpdatePlanetMiningSettingsCommand( int planet , Map< String , Integer > settings )
+	{
+		super( planet );
+		this.settings = settings;
+	}
+
+
+	/** @return the mining settings to apply */
+	public Map< String , Integer > getSettings( )
+	{
+		return this.settings;
+	}
+
+}
diff --git a/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
index 0f15268..6d132af 100644
--- a/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
+++ b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
@@ -164,6 +164,36 @@ public class PlayerSession
 	}
 
 
+	/**
+	 * Send a {@link ToggleMiningSettingsCommand} to the server
+	 * 
+	 * @param pId
+	 *            the planet whose mining settings are to be switched between empire-wide and
+	 *            planet-specific
+	 */
+	public void toggleMiningSettingsFor( int pId )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		this.execute( new ToggleMiningSettingsCommand( pId ) );
+	}
+
+
+	/**
+	 * Send a {@link UpdatePlanetMiningSettingsCommand} to the server
+	 * 
+	 * @param pId
+	 *            the identifier of the planet to udpate
+	 * @param settings
+	 *            the map of settings, as associations between resource identifiers and priority
+	 *            values
+	 */
+	public void updatePlanetMiningSettings( int pId , Map< String , Integer > settings )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		this.execute( new UpdatePlanetMiningSettingsCommand( pId , settings ) );
+	}
+
+
 	public ViewPlanetResponse rename( int planetId , String name )
 			throws SessionException , SessionServerException , SessionMaintenanceException
 	{
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl
index 7f3649c..e6b735c 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet.ftl
@@ -376,55 +376,8 @@
 
 			</#if>
 			
-			<#list data.own.resources as resource>
-				<#if resource.resourceProvider?has_content>
-					<#local showResources=true>
-					<#break>
-				</#if>
-			</#list>
-			
-			<#if showResources?has_content>
-				<@tab id="natres" title="Natural resources">
-				
-					<@listview>
-						<@lv_line headers=true>
-							<@lv_column width="x">Resource</@lv_column>
-							<@lv_column width=100 right=true>Quantity&nbsp;</@lv_column>
-							<@lv_column width=100>&nbsp;&nbsp;Capacity</@lv_column>
-							<@lv_column width=100 centered=true>Extraction<br/>difficulty</@lv_column>
-							<@lv_column width=100 centered=true>Priority</@lv_column>
-						</@lv_line>
-						
-						<#list data.own.resources as resource>
-							<#if resource.resourceProvider?has_content>
-								<#local resProv=resource.resourceProvider>
-
-								<@lv_line>
-									<@lv_column>
-										${resource.title?xhtml}
-										<div class="auto-hide">${resource.description?xhtml}</div>
-									</@lv_column>
-									<@lv_column right=true>${resProv.quantity?string(",##0")}&nbsp;</@lv_column>
-									<@lv_column>/&nbsp;${resProv.capacity?string(",##0")}</@lv_column>
-									<@lv_column centered=true>${resProv.difficulty}&nbsp;%</@lv_column>
-									<@lv_column centered=true>
-										<#switch resProv.priority>
-											<#case 0>lowest<#break>
-											<#case 1>low<#break>
-											<#case 2>normal<#break>
-											<#case 3>high<#break>
-											<#case 4>highest<#break>
-										</#switch>
-									</@lv_column>
-								</@lv_line>
-
-							</#if>
-						</#list>
-					</@listview>
-
-				</@tab>
-			</#if>
-
+			<#include "planet/natres.ftl" />
+			<@RenderNaturalResources />
 		</#if>
 		
 	</@tabs>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl
new file mode 100644
index 0000000..b110914
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl
@@ -0,0 +1,79 @@
+<#macro RenderNaturalResources>			
+	<#list data.own.resources as resource>
+		<#if resource.resourceProvider?has_content>
+			<#local showResources=true>
+			<#break>
+		</#if>
+	</#list>
+	
+	<#if showResources?has_content>
+		<@tab id="natres" title="Natural resources">
+			<form action="planet-${data.id}-update-mset.action" method="POST">
+
+				<@listview>
+					<@lv_line headers=true>
+						<@lv_column width="x">Resource</@lv_column>
+						<@lv_column width=100 right=true>Quantity&nbsp;</@lv_column>
+						<@lv_column width=100>&nbsp;&nbsp;Capacity</@lv_column>
+						<@lv_column width=100 centered=true>Extraction<br/>difficulty</@lv_column>
+						<@lv_column width=100 centered=true>Priority</@lv_column>
+					</@lv_line>
+					
+					<#list data.own.resources as resource>
+						<#if resource.resourceProvider?has_content>
+							<#local resProv=resource.resourceProvider>
+		
+							<@lv_line>
+								<@lv_column>
+									${resource.title?xhtml}
+									<div class="auto-hide">${resource.description?xhtml}</div>
+								</@lv_column>
+								<@lv_column right=true>${resProv.quantity?string(",##0")}&nbsp;</@lv_column>
+								<@lv_column>/&nbsp;${resProv.capacity?string(",##0")}</@lv_column>
+								<@lv_column centered=true>${resProv.difficulty}&nbsp;%</@lv_column>
+								<@lv_column centered=true>
+									<#if data.own.ownMiningSettings>
+										<select name="pms-${resource.identifier?xhtml}" class="input">
+											<option style="padding: 0 5px" value="0" <#if resProv.priority = 0>selected="selected"</#if>>lowest</option>
+											<option style="padding: 0 5px" value="1" <#if resProv.priority = 1>selected="selected"</#if>>low</option>
+											<option style="padding: 0 5px" value="2" <#if resProv.priority = 2>selected="selected"</#if>>normal</option>
+											<option style="padding: 0 5px" value="3" <#if resProv.priority = 3>selected="selected"</#if>>high</option>
+											<option style="padding: 0 5px" value="4" <#if resProv.priority = 4>selected="selected"</#if>>highest</option>
+										</select>
+									<#else>
+										<#switch resProv.priority>
+											<#case 0>lowest<#break>
+											<#case 1>low<#break>
+											<#case 2>normal<#break>
+											<#case 3>high<#break>
+											<#case 4>highest<#break>
+										</#switch>
+									</#if>
+								</@lv_column>
+							</@lv_line>
+		
+						</#if>
+					</#list>
+					
+					<@lv_line headers=true>
+						<@lv_column width="x" colspan=5>&nbsp;</@lv_column>
+					</@lv_line>
+					<@lv_line>
+						<@lv_column right=true colspan=5>
+							<#if data.own.ownMiningSettings>
+								Using planet-specific settings
+								<input type="submit" name="toggle-settings" value="Use empire settings" class="input" style="margin: 15px 0 0 0" />
+								<input type="submit" name="update-pms" value="Update priorities" class="input" style="margin: 15px 0 0 0" />
+							<#else>
+								<input type="submit" name="toggle-settings" value="Use specific settings" class="input" style="margin: 15px 0 0 0" />
+							</#if>
+						</@lv_column>
+					</@lv_line>
+
+				</@listview>
+
+			</form>
+
+		</@tab>
+	</#if>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl
index 5f6fadd..9c69b05 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet.ftl
@@ -374,54 +374,8 @@
 				</@tab>
 			</#if>
 
-			<#list data.own.resources as resource>
-				<#if resource.resourceProvider?has_content>
-					<#local showResources=true>
-					<#break>
-				</#if>
-			</#list>
-			
-			<#if showResources?has_content>
-				<@tab id="natres" title="Ressources naturelles">
-				
-					<@listview>
-						<@lv_line headers=true>
-							<@lv_column width="x">Ressource</@lv_column>
-							<@lv_column width=100 right=true>Quantité&nbsp;</@lv_column>
-							<@lv_column width=100>&nbsp;&nbsp;Capacité</@lv_column>
-							<@lv_column width=100 centered=true>Difficulté<br/>d'extraction</@lv_column>
-							<@lv_column width=100 centered=true>Priorité</@lv_column>
-						</@lv_line>
-						
-						<#list data.own.resources as resource>
-							<#if resource.resourceProvider?has_content>
-								<#local resProv=resource.resourceProvider>
-
-								<@lv_line>
-									<@lv_column>
-										${resource.title?xhtml}
-										<div class="auto-hide">${resource.description?xhtml}</div>
-									</@lv_column>
-									<@lv_column right=true>${resProv.quantity?string(",##0")}&nbsp;</@lv_column>
-									<@lv_column>/&nbsp;${resProv.capacity?string(",##0")}</@lv_column>
-									<@lv_column centered=true>${resProv.difficulty}&nbsp;%</@lv_column>
-									<@lv_column centered=true>
-										<#switch resProv.priority>
-											<#case 0>très basse<#break>
-											<#case 1>basse<#break>
-											<#case 2>normale<#break>
-											<#case 3>haute<#break>
-											<#case 4>très haute<#break>
-										</#switch>
-									</@lv_column>
-								</@lv_line>
-
-							</#if>
-						</#list>
-					</@listview>
-
-				</@tab>
-			</#if>
+			<#include "planet/natres.ftl" />
+			<@RenderNaturalResources />
 
 		</#if>
 
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet/natres.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet/natres.ftl
new file mode 100644
index 0000000..2f30d41
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planet/natres.ftl
@@ -0,0 +1,80 @@
+<#macro RenderNaturalResources>			
+	<#list data.own.resources as resource>
+		<#if resource.resourceProvider?has_content>
+			<#local showResources=true>
+			<#break>
+		</#if>
+	</#list>
+	
+	<#if showResources?has_content>
+				<@tab id="natres" title="Ressources naturelles">
+			<form action="planet-${data.id}-update-mset.action" method="POST">
+
+				<@listview>
+					<@lv_line headers=true>
+						<@lv_column width="x">Ressource</@lv_column>
+						<@lv_column width=100 right=true>Quantité&nbsp;</@lv_column>
+						<@lv_column width=100>&nbsp;&nbsp;Capacité</@lv_column>
+						<@lv_column width=100 centered=true>Difficulté<br/>d'extraction</@lv_column>
+						<@lv_column width=100 centered=true>Priorité</@lv_column>
+					</@lv_line>
+					
+					<#list data.own.resources as resource>
+						<#if resource.resourceProvider?has_content>
+							<#local resProv=resource.resourceProvider>
+		
+							<@lv_line>
+								<@lv_column>
+									${resource.title?xhtml}
+									<div class="auto-hide">${resource.description?xhtml}</div>
+								</@lv_column>
+								<@lv_column right=true>${resProv.quantity?string(",##0")}&nbsp;</@lv_column>
+								<@lv_column>/&nbsp;${resProv.capacity?string(",##0")}</@lv_column>
+								<@lv_column centered=true>${resProv.difficulty}&nbsp;%</@lv_column>
+								<@lv_column centered=true>
+									<#if data.own.ownMiningSettings>
+										<select name="pms-${resource.identifier?xhtml}" class="input">
+											<option style="padding: 0 5px" value="0" <#if resProv.priority = 0>selected="selected"</#if>>très basse</option>
+											<option style="padding: 0 5px" value="1" <#if resProv.priority = 1>selected="selected"</#if>>basse</option>
+											<option style="padding: 0 5px" value="2" <#if resProv.priority = 2>selected="selected"</#if>>normale</option>
+											<option style="padding: 0 5px" value="3" <#if resProv.priority = 3>selected="selected"</#if>>haute</option>
+											<option style="padding: 0 5px" value="4" <#if resProv.priority = 4>selected="selected"</#if>>très haute</option>
+										</select>
+									<#else>
+										<#switch resProv.priority>
+											<#case 0>très basse<#break>
+											<#case 1>basse<#break>
+											<#case 2>normale<#break>
+											<#case 3>haute<#break>
+											<#case 4>très haute<#break>
+										</#switch>
+									</#if>
+								</@lv_column>
+							</@lv_line>
+		
+						</#if>
+					</#list>
+					
+					<@lv_line headers=true>
+						<@lv_column width="x" colspan=5>&nbsp;</@lv_column>
+					</@lv_line>
+					<@lv_line>
+						<@lv_column right=true colspan=5>
+							<#if data.own.ownMiningSettings>
+								Cette planète utilise des priorités d'extraction qui lui sont spécifiques.
+								<input type="submit" name="toggle-settings" value="Utiliser les priorités de l'empire" class="input" style="margin: 15px 0 0 0" />
+								<input type="submit" name="update-pms" value="Mettre à jour les priorités" class="input" style="margin: 15px 0 0 0" />
+							<#else>
+								Cette planète utilise les priorités d'extraction de l'empire.
+								<input type="submit" name="toggle-settings" value="Utiliser des priorités spécifiques" class="input" style="margin: 15px 0 0 0" />
+							</#if>
+						</@lv_column>
+					</@lv_line>
+
+				</@listview>
+
+			</form>
+
+		</@tab>
+	</#if>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java
index e0afc3c..2bbb419 100644
--- a/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java
+++ b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/PlanetPage.java
@@ -1,6 +1,10 @@
 package com.deepclone.lw.web.main.game;
 
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
 import javax.servlet.http.HttpServletRequest;
 
 import org.springframework.stereotype.Controller;
@@ -80,8 +84,8 @@ public class PlanetPage
 
 
 	@RequestMapping( value = "/planet-{planetId}-cancel-abandon.action" , method = RequestMethod.POST )
-	public String cancelAbandon( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model ,
-			@PathVariable String planetId )
+	public String cancelAbandon( HttpServletRequest request , @ModelAttribute( "language" ) String language ,
+			Model model , @PathVariable String planetId )
 			throws SessionException , SessionServerException , SessionMaintenanceException
 	{
 		int pId;
@@ -233,4 +237,108 @@ public class PlanetPage
 		PlayerSession pSession = this.getSession( PlayerSession.class , request );
 		return this.render( model , "game" , language , "planet" , pSession.flushQueue( pId , true ) );
 	}
+
+
+	/**
+	 * Handler for commands that modify planet mining settings
+	 * 
+	 * <p>
+	 * This method handles all mining settings commands, including both switching between empire-
+	 * and planet-specific settings and updating all priorities.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * @param model
+	 *            the model
+	 * @param planetId
+	 *            the planet's identifier string from the path
+	 * @param toggle
+	 *            this parameter from the form's <code>toggle-settings</code> value will be set if
+	 *            the "Switch to empire-wide/planet-specific settings" button was clicked.
+	 * 
+	 * @return a redirect to the overview page if the planet identifier was invalid, or a redirect
+	 *         to the planet page's natural resources tab otherwise.
+	 * 
+	 * @throws SessionException
+	 *             if some error occurs on the server
+	 * @throws SessionServerException
+	 *             if the server is unreachable
+	 * @throws SessionMaintenanceException
+	 *             if the game is under maintenance
+	 */
+	@RequestMapping( value = "/planet-{planetId}-update-mset.action" , method = RequestMethod.POST )
+	public String updateMiningSettings( HttpServletRequest request , Model model , @PathVariable String planetId ,
+			@RequestParam( value = "toggle-settings" , required = false ) String toggle )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		int pId;
+		try {
+			pId = Integer.parseInt( planetId );
+		} catch ( NumberFormatException e ) {
+			return this.redirect( "overview" );
+		}
+
+		PlayerSession session = this.getSession( PlayerSession.class , request );
+		if ( toggle == null ) {
+			Map< String , Integer > settings = this.getMiningSettings( request );
+			if ( settings != null ) {
+				session.updatePlanetMiningSettings( pId , settings );
+			}
+		} else {
+			session.toggleMiningSettingsFor( pId );
+		}
+
+		return this.redirect( "planet-" + Integer.toString( pId ) + "#natres" );
+	}
+
+
+	/**
+	 * Extract mining priorities from the HTTP request
+	 * 
+	 * <p>
+	 * Look for all submitted fields that begin with "pms-" then try to extract their values into a
+	 * map that associates resource identifiers to priorities.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * 
+	 * @return the map containing the submitted mining settings, or <code>null</code> if one of the
+	 *         values was incorrect.
+	 */
+	private Map< String , Integer > getMiningSettings( HttpServletRequest request )
+	{
+		Map< String , Object > input = this.getInput( request );
+		Map< String , Integer > miningSettings = new HashMap< String , Integer >( );
+		for ( Entry< String , Object > entry : input.entrySet( ) ) {
+			// Ignore items which are not mining settings
+			String name = entry.getKey( );
+			if ( !name.startsWith( "pms-" ) ) {
+				continue;
+			}
+			name = name.substring( 4 );
+
+			// Get values
+			if ( ! ( entry.getValue( ) instanceof String[] ) ) {
+				continue;
+			}
+			String[] values = (String[]) entry.getValue( );
+			if ( values.length < 1 ) {
+				continue;
+			}
+
+			// Pre-validate them
+			int value;
+			try {
+				value = Integer.parseInt( values[ 0 ] );
+			} catch ( NumberFormatException e ) {
+				value = -1;
+			}
+			if ( value < 0 || value > 4 ) {
+				return null;
+			}
+
+			miningSettings.put( name , value );
+		}
+		return miningSettings;
+	}
 }

From fd117f6f7baee0a3f70611c6ef2c9c2fc7e76b7a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 7 Feb 2012 19:40:57 +0100
Subject: [PATCH 46/94] Fixed consequences of Spring config clean-up

* While the server works fine, the tools - which use hardcoded paths to
specific parts of the Spring config - were not updated last time. They
should work now.
---
 .../src/main/java/com/deepclone/lw/cli/CreateSuperuser.java     | 2 +-
 .../src/main/java/com/deepclone/lw/cli/CreateUser.java          | 2 +-
 .../src/main/java/com/deepclone/lw/cli/ImportBuildables.java    | 2 +-
 .../src/main/java/com/deepclone/lw/cli/ImportResources.java     | 2 +-
 .../src/main/java/com/deepclone/lw/cli/ImportTechs.java         | 2 +-
 .../src/main/java/com/deepclone/lw/cli/ImportText.java          | 2 +-
 6 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java
index 4c92d2b..2446ae1 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateSuperuser.java
@@ -43,7 +43,7 @@ public class CreateSuperuser
 
 		// Load transaction manager bean
 		String[] cfg = {
-			"configuration/transaction-bean.xml"
+			"configuration/transactions.xml"
 		};
 		return new ClassPathXmlApplicationContext( cfg , true , ctx );
 	}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateUser.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateUser.java
index 77bc8f5..b418617 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateUser.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/CreateUser.java
@@ -48,7 +48,7 @@ public class CreateUser
 
 		// Load transaction manager bean
 		String[] cfg = {
-			"configuration/transaction-bean.xml"
+			"configuration/transactions.xml"
 		};
 		return new ClassPathXmlApplicationContext( cfg , true , ctx );
 	}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
index ab27ed1..a0f4228 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
@@ -163,7 +163,7 @@ public class ImportBuildables
 
 		// Load Hibernate bean
 		String[] cfg = {
-				"configuration/context-configuration.xml" , "configuration/transaction-bean.xml"
+				"configuration/transactions.xml"
 		};
 		return new ClassPathXmlApplicationContext( cfg , true , ctx );
 	}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportResources.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportResources.java
index b66063b..ab2bb0a 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportResources.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportResources.java
@@ -77,7 +77,7 @@ public class ImportResources
 		ctx.refresh( );
 
 		return new ClassPathXmlApplicationContext( new String[] {
-			"configuration/transaction-bean.xml"
+			"configuration/transactions.xml"
 		} , true , ctx );
 	}
 
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
index cd15b30..bc74f83 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
@@ -121,7 +121,7 @@ public class ImportTechs
 
 		// Load beans
 		String[] cfg = {
-				"configuration/context-configuration.xml" , "configuration/transaction-bean.xml"
+				"configuration/transactions.xml"
 		};
 		return new ClassPathXmlApplicationContext( cfg , true , ctx );
 	}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
index 339182d..3dcf4ef 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
@@ -70,7 +70,7 @@ public class ImportText
 		ctx.refresh( );
 
 		return new ClassPathXmlApplicationContext( new String[] {
-			"configuration/transaction-bean.xml"
+			"configuration/transactions.xml"
 		} , true , ctx );
 	}
 

From cb2553d1e53ea7d891dcec3d5fe7e7bfbbb4b919 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Wed, 8 Feb 2012 08:38:56 +0100
Subject: [PATCH 47/94] Build system and documentation update

* Added tests of the command line tools to the post-build scripts

* Local deployment documentation mentions ImportResources
---
 .../10-database-tests.sh}                     |  0
 build/post-build.d/20-import-tools.sh         | 49 +++++++++++++++++++
 legacyworlds/doc/local-deployment.txt         |  4 +-
 3 files changed, 52 insertions(+), 1 deletion(-)
 rename build/{post-build.sh => post-build.d/10-database-tests.sh} (100%)
 mode change 100755 => 100644
 create mode 100644 build/post-build.d/20-import-tools.sh

diff --git a/build/post-build.sh b/build/post-build.d/10-database-tests.sh
old mode 100755
new mode 100644
similarity index 100%
rename from build/post-build.sh
rename to build/post-build.d/10-database-tests.sh
diff --git a/build/post-build.d/20-import-tools.sh b/build/post-build.d/20-import-tools.sh
new file mode 100644
index 0000000..2b20f04
--- /dev/null
+++ b/build/post-build.d/20-import-tools.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+TESTDIR=`mktemp -d`
+cp -Rapv legacyworlds-server-DIST/target/legacyworlds/legacyworlds-server-* $TESTDIR
+
+
+cd $TESTDIR/legacyworlds-server-*/sql
+cat > db-config.txt <<EOF
+admin=$USER
+db=tests
+user=tests
+password=tests
+EOF
+psql -vQUIET=1 -vON_ERROR_STOP=1 -e --file database.sql || exit 1
+cd ..
+
+cat > data-source.xml <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<bean name="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
+		<property name="driverClassName" value="org.postgresql.Driver" />
+		<property name="url" value="jdbc:postgresql:tests" />
+		<property name="username" value="tests" />
+		<property name="password" value="tests" />
+	</bean>
+
+</beans>
+EOF
+
+java legacyworlds-server-main-*.jar --run-tool ImportText data/i18n-text.xml || exit 1
+java legacyworlds-server-main-*.jar --run-tool ImportResources data/resources.xml || exit 1
+java legacyworlds-server-main-*.jar --run-tool ImportTechs data/techs.xml || exit 1
+java legacyworlds-server-main-*.jar --run-tool ImportBuildables data/buildables.xml || exit 1
+
+java legacyworlds-server-main-*.jar &
+sleep 10
+if ! ps ux | grep -q 'java -jar legacyworlds-server-main'; then
+	exit 1;
+fi
+java legacyworlds-server-main-*.jar --run-tool Stop || {
+	killall java
+	exit 1;
+}
+
+java -jar legacyworlds-server-main-1.0.0-0.jar --run-tool CreateUser 'test@example.org 12blah34bleh en' || exit 1
+java -jar legacyworlds-server-main-1.0.0-0.jar --run-tool CreateSuperuser 'test@example.org Turlututu' || exit 1
diff --git a/legacyworlds/doc/local-deployment.txt b/legacyworlds/doc/local-deployment.txt
index 4ca29a8..24669c3 100644
--- a/legacyworlds/doc/local-deployment.txt
+++ b/legacyworlds/doc/local-deployment.txt
@@ -41,6 +41,8 @@ from the root of the server's distribution:
 		--run-tool ImportText data/i18n-text.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \
 		--run-tool ImportTechs data/techs.xml
+	java -jar legacyworlds-server-main-1.0.0-0.jar \
+		--run-tool ImportResources data/resources.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \
 		--run-tool ImportBuildables data/buildables.xml 
 
@@ -83,4 +85,4 @@ Replace 'user@example.org' with the user's actual email address, and
 	This will deploy the main and administrative sites to /lwmain and /lwadmin,
 respectively.
 	* If you need to redeploy the sites later, you need to run
-		mvn package tomcat:redeploy
\ No newline at end of file
+		mvn package tomcat:redeploy

From c94958a0580567465654b0078251200cc0eb46b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Wed, 8 Feb 2012 08:56:07 +0100
Subject: [PATCH 48/94] Added log files to Git ignore list

* Added both "normal" log files and compressed files created when
full-debug.log is rotated.
---
 .gitignore | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitignore b/.gitignore
index 94fbf38..cda677a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ target
 .project
 legacyworlds-server-main/data-source.xml
 legacyworlds-server-data/db-structure/db-config.txt
+*.log
+full-debug*.zip

From afa1224391a2257a5f2f346a44bf013d7b62d771 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Wed, 8 Feb 2012 15:38:12 +0100
Subject: [PATCH 49/94] Production adjustment fix

* The production adjustment function was completely off (and always has
been). It has been fixed.

* The problem with the adjustment function not being reported means that
no-one ever encountered it. As a consequence, happiness strike threshold
has been increased to 50%

/!\ Full database reset required (or at least much easier than other
options).
---
 .../lw/beans/sys/ConstantsRegistrarBean.java  |  2 +-
 .../parts/040-functions/050-computation.sql   | 38 +++++++++++---
 .../050-computation/020-adjust-production.sql | 51 +++++++++++++++++++
 .../050-computation/020-adjust-production.sql | 12 +++++
 4 files changed, 95 insertions(+), 8 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/020-adjust-production.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/020-adjust-production.sql

diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 75d3a9c..23bdad8 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -94,7 +94,7 @@ public class ConstantsRegistrarBean
 		cDesc = "Empire size limit (relative to the ideal size) beyond which things get tough.";
 		defs.add( new ConstantDefinition( hcNames[ 7 ] , cat , cDesc , 2.0 , 1.1 , true ) );
 		cDesc = "Happiness level below which strikes begin.";
-		defs.add( new ConstantDefinition( hcNames[ 8 ] , cat , cDesc , 0.25 , 0.0 , 1.0 ) );
+		defs.add( new ConstantDefinition( hcNames[ 8 ] , cat , cDesc , 0.5 , 0.0 , 1.0 ) );
 		cDesc = "Happiness change at each update, relative to the size of the population.";
 		defs.add( new ConstantDefinition( hcNames[ 9 ] , cat , cDesc , 0.001 , 0.00001 , 0.99999 ) );
 		cDesc = "Maximal population units for which happiness will change.";
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/050-computation.sql b/legacyworlds-server-data/db-structure/parts/040-functions/050-computation.sql
index ebc8ed6..0d6c1bb 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/050-computation.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/050-computation.sql
@@ -301,21 +301,45 @@ $$ LANGUAGE plpgsql;
 
 
 
---
--- Production adjustment
---
-CREATE OR REPLACE FUNCTION verse.adjust_production( prod REAL , happiness REAL )
+/*
+ * Production adjustment
+ * ----------------------
+ *
+ * Adjust productions depending on a planet's happiness ratio. When the
+ * happiness ratio is greater than the game.happiness.strike constant, the
+ * production is not affected. However, under that threshold, the production
+ * decreases proportionally, until it reaches 0.
+ * 
+ * Parameters:
+ *		_prod		The production's valule
+ *		_h_ratio	The happiness ratio
+ *
+ * Returns:
+ *		?			The adjusted production value
+ */
+DROP FUNCTION IF EXISTS verse.adjust_production( REAL , REAL ) CASCADE;
+CREATE OR REPLACE FUNCTION verse.adjust_production( _prod REAL , _h_ratio REAL )
 	RETURNS REAL
+	LANGUAGE SQL
 	STRICT IMMUTABLE
 	SECURITY INVOKER
-AS $$
+AS $adjust_production$
+
 	SELECT ( CASE
 		WHEN $2 < sys.get_constant( 'game.happiness.strike' ) THEN
-			( $1 * ( 1 - ( $2 / sys.get_constant( 'game.happiness.strike' ) ) ) )::REAL
+			( $1 * $2 / sys.get_constant( 'game.happiness.strike' ) )::REAL
 		ELSE
 			$1
 	END );
-$$ LANGUAGE SQL;
+
+$adjust_production$;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.adjust_production( REAL , REAL )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION verse.adjust_production( REAL , REAL )
+	TO :dbuser;
 
 
 --
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/020-adjust-production.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/020-adjust-production.sql
new file mode 100644
index 0000000..7059de1
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/020-adjust-production.sql
@@ -0,0 +1,51 @@
+/*
+ * Test the verse.adjust_production() function
+ */
+BEGIN;
+	-- Create a table of input values
+	CREATE TABLE test_input(
+		prod		REAL ,
+		happ		REAL
+	);
+	
+	-- Set the happiness strike level to 50%
+	SELECT sys.uoc_constant( 'game.happiness.strike' , '(test)' , 'Happiness' , 0.5 );
+	
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 3 );
+	
+	INSERT INTO test_input VALUES
+		( 10 , 1 ) , ( 100 , 1 ) ,
+		( 10 , 0.9 ) , ( 100 , 0.9 ) ,
+		( 10 , 0.8 ) , ( 100 , 0.8 ) ,
+		( 10 , 0.7 ) , ( 100 , 0.7 ) ,
+		( 10 , 0.6 ) , ( 100 , 0.6 ) ,
+		( 10 , 0.5 ) , ( 100 , 0.5 );
+	SELECT diag_test_name( 'verse.adjust_production() - Production is unaffected when happiness >= game.happiness.strike' );
+	SELECT is_empty(
+		$$ SELECT * FROM test_input WHERE prod <> verse.adjust_production( prod , happ ); $$
+	);
+	DELETE FROM test_input;
+	
+	INSERT INTO test_input VALUES
+		( 10 , 0.5 ) , ( 10 , 0.4 ) ,
+		( 10 , 0.3 ) , ( 10 , 0.2 ) ,
+		( 10 , 0.1 ) , ( 10 , 0 );
+	SELECT diag_test_name( 'verse.adjust_production() - Production is unaffected when happiness >= game.happiness.strike' );
+	SELECT set_eq(
+		$$ SELECT rank( ) OVER( ORDER BY happ ) AS r1 ,
+				rank( ) OVER ( ORDER BY verse.adjust_production( prod , happ ) ) AS r2
+			FROM test_input; $$ ,
+		$$ VALUES ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) , ( 4 , 4 ) , ( 5 , 5 ) , ( 6 , 6 ) $$ 
+	);
+	DELETE FROM test_input;
+
+	SELECT diag_test_name( 'verse.adjust_production() - Happiness = 0 => production = 0' );
+	INSERT INTO test_input VALUES
+		( 1 , 0 ) , ( 10 , 0 ) , ( 100 , 0 ) , ( 1000 , 0 ) , ( 10000 , 0 );
+	SELECT is_empty(
+		$$ SELECT * FROM test_input WHERE verse.adjust_production( prod , happ ) <> 0; $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/020-adjust-production.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/020-adjust-production.sql
new file mode 100644
index 0000000..95ecf3e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/020-adjust-production.sql
@@ -0,0 +1,12 @@
+/*
+ * Test privileges on verse.adjust_production()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'verse.adjust_production( ) - EXECUTE privilege' );
+	SELECT lives_ok( $$ SELECT verse.adjust_production( 1 , 1 ) $$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From f3aa5637580ef10fcfc1436fe7037054fca45b99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Wed, 8 Feb 2012 11:54:01 +0100
Subject: [PATCH 50/94] Planet resources interface fix

* Some text in the English version of the planet page's "natural
resources" tab incorrectly used the term "settings" instead of
"priorities".

* The English version of the planet page's "natural resources" tab did
not display any explanation when the planet was following empire-wide
mining priorities.
---
 .../Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl      | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl
index b110914..001a30d 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planet/natres.ftl
@@ -61,11 +61,12 @@
 					<@lv_line>
 						<@lv_column right=true colspan=5>
 							<#if data.own.ownMiningSettings>
-								Using planet-specific settings
-								<input type="submit" name="toggle-settings" value="Use empire settings" class="input" style="margin: 15px 0 0 0" />
+								This planet follows planet-specific mining priorities.
+								<input type="submit" name="toggle-settings" value="Use empire priorities" class="input" style="margin: 15px 0 0 0" />
 								<input type="submit" name="update-pms" value="Update priorities" class="input" style="margin: 15px 0 0 0" />
 							<#else>
-								<input type="submit" name="toggle-settings" value="Use specific settings" class="input" style="margin: 15px 0 0 0" />
+								This planet follows empire-wide mining priorities.
+								<input type="submit" name="toggle-settings" value="Use specific priorities" class="input" style="margin: 15px 0 0 0" />
 							</#if>
 						</@lv_column>
 					</@lv_line>

From bf6bea5a79c2fa5d657f2289a688f50de4dbc60b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 9 Feb 2012 10:54:00 +0100
Subject: [PATCH 51/94] Extracted quantities update as soon as possible

* The quantities of resources extracted from mines will now be updated
as soon as they have a reason to. This includes planet assignment,
abandonment, ownership changes, and updates to mining priorities.

* The mining update will now remove the current resource income from the
providers, and only then re-compute all extracted quantities. This is
more logical and corresponds to the way the other game updates work.

* Fixed bug in extraction computation where the size of the planet's
happy population was used instead of the happy/total ratio when
adjusting the mining production for riots.

* The following SQL scripts must be re-executed to upgrade a database:
  -> 040-functions/040-empire.sql
  -> 040-functions/145-resource-providers.sql
  -> 040-functions/147-empire-mining.sql
  -> 050-updates/120-planet-mining.sql
---
 .../parts/040-functions/040-empire.sql        |  15 +-
 .../040-functions/145-resource-providers.sql  | 283 ++++++++++++++++++
 ...mpire-mining.sql => 147-empire-mining.sql} |  35 ++-
 .../parts/050-updates/120-planet-mining.sql   | 207 +++----------
 .../040-empire/010-create-empire.sql          |  49 ++-
 .../050-scaled-mining-weights-view.sql}       |  10 +-
 .../060-total-mining-weights-view.sql}        |   8 +-
 .../070-mining-compute-extraction.sql         | 135 +++++++++
 .../080-verse-mining-get-input.sql            |  88 ++++++
 .../090-emp-mining-get-input.sql              |  69 +++++
 .../010-mset-update-start.sql                 |   0
 .../015-mset-update-start-planet.sql          |   0
 .../020-mset-update-set.sql                   |   0
 .../030-mset-update-apply.sql                 |   0
 .../040-mset-toggle-source.sql                |   0
 ...c-get-data.sql => 010-gu-pmc-get-data.sql} |   0
 .../020-process-planet-mining-updates.sql     |  89 ++++++
 .../040-gu-pmc-update-resource.sql            |  91 ------
 .../050-process-planet-mining-updates.sql     |  58 ----
 .../050-scaled-mining-weights-view.sql        |  11 +
 .../060-total-mining-weights-view.sql         |  11 +
 .../070-mining-compute-extraction.sql         |  13 +
 .../080-verse-mining-get-input.sql            |  13 +
 .../090-emp-mining-get-input.sql              |  13 +
 .../010-mset-update-start.sql                 |   0
 .../015-mset-update-start-planet.sql          |   0
 .../020-mset-update-set.sql                   |   0
 .../030-mset-update-apply.sql                 |   0
 .../040-mset-toggle-source.sql                |   0
 ...c-get-data.sql => 010-gu-pmc-get-data.sql} |   0
 .../010-gu-pmc-weights-view.sql               |  11 -
 .../020-gu-pmc-totals-view.sql                |  11 -
 ... => 020-process-planet-mining-updates.sql} |   0
 .../040-gu-pmc-update-resource.sql            |  13 -
 .../setup-gu-pmc-get-data-test.sql            |   2 +-
 35 files changed, 863 insertions(+), 372 deletions(-)
 rename legacyworlds-server-data/db-structure/parts/040-functions/{045-empire-mining.sql => 147-empire-mining.sql} (86%)
 rename legacyworlds-server-data/db-structure/tests/admin/{050-updates/120-planet-mining/010-gu-pmc-weights-view.sql => 040-functions/145-resource-providers/050-scaled-mining-weights-view.sql} (76%)
 rename legacyworlds-server-data/db-structure/tests/admin/{050-updates/120-planet-mining/020-gu-pmc-totals-view.sql => 040-functions/145-resource-providers/060-total-mining-weights-view.sql} (73%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/070-mining-compute-extraction.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/080-verse-mining-get-input.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/090-emp-mining-get-input.sql
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{045-empire-mining => 147-empire-mining}/010-mset-update-start.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{045-empire-mining => 147-empire-mining}/015-mset-update-start-planet.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{045-empire-mining => 147-empire-mining}/020-mset-update-set.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{045-empire-mining => 147-empire-mining}/030-mset-update-apply.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{045-empire-mining => 147-empire-mining}/040-mset-toggle-source.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/{030-gu-pmc-get-data.sql => 010-gu-pmc-get-data.sql} (100%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-process-planet-mining-updates.sql
 delete mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
 delete mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/050-scaled-mining-weights-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/060-total-mining-weights-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/070-mining-compute-extraction.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/080-verse-mining-get-input.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/090-emp-mining-get-input.sql
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{045-empire-mining => 147-empire-mining}/010-mset-update-start.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{045-empire-mining => 147-empire-mining}/015-mset-update-start-planet.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{045-empire-mining => 147-empire-mining}/020-mset-update-set.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{045-empire-mining => 147-empire-mining}/030-mset-update-apply.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{045-empire-mining => 147-empire-mining}/040-mset-toggle-source.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/{030-gu-pmc-get-data.sql => 010-gu-pmc-get-data.sql} (100%)
 delete mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
 delete mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
 rename legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/{050-process-planet-mining-updates.sql => 020-process-planet-mining-updates.sql} (100%)
 delete mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index cb30603..a49c0d6 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -3,12 +3,13 @@
 --
 -- Empire management functions and views
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2012, DeepClone Development
 -- --------------------------------------------------------
 
 
 /*
  * Empire creation
+ * ----------------
  * 
  * This function inserts the rows that represent an empire and its settings.
  * It also initialises the empire's updates.
@@ -27,10 +28,8 @@ CREATE FUNCTION emp.create_empire(
 		STRICT VOLATILE
 		SECURITY INVOKER
 	AS $$
-DECLARE
-	_update			BIGINT;
-	_update_type	update_type;
 BEGIN
+
 	-- Add empire and give initial planet
 	INSERT INTO emp.empires ( name_id , cash )
 		VALUES ( _name_id , _initial_cash );
@@ -45,6 +44,14 @@ BEGIN
 	-- Add empire resources
 	INSERT INTO emp.resources ( empire_id , resource_name_id )
 		SELECT _name_id , resource_name_id FROM defs.resources;
+
+	-- Update resource mining quantities
+	UPDATE verse.planet_resources
+		SET pres_income = emp.mining_compute_extraction( _update_row )
+		FROM emp.mining_get_input( _name_id ) _update_row
+		WHERE planet_id = _update_row.planet
+			AND resource_name_id = _update_row.resource;
+
 END;
 $$ LANGUAGE plpgsql;
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
index 74b758b..ebab70b 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/145-resource-providers.sql
@@ -142,6 +142,7 @@ REVOKE EXECUTE
 
 /*
  * Planet resources type
+ * ----------------------
  * 
  * This type is used to transmit a planet's resources information to the game
  * server. It contains the resource's description, the planet's economic data
@@ -275,3 +276,285 @@ GRANT EXECUTE
 	ON FUNCTION emp.get_planet_resources( INT )
 	TO :dbuser;
 
+
+
+/*
+ * Planet mining update data
+ * --------------------------
+ * 
+ * This type is used by the records used by planet mining updates to compute a
+ * planet's mining output.
+ */
+DROP TYPE IF EXISTS emp.planet_mining_type CASCADE;
+CREATE TYPE emp.planet_mining_type AS (
+	/* The planet's identifier */
+	planet			INT ,
+
+	/* The resource's identifier */
+	resource		INT ,
+
+	/* The provider's quantity of resources */
+	quantity		DOUBLE PRECISION ,
+
+	/* The provider's maximal quantity of resources */
+	quantity_max	DOUBLE PRECISION ,
+
+	/* The provider's extraction difficulty */
+	difficulty		DOUBLE PRECISION ,
+
+	/* The empire who owns the planet, or NULL if the planet is neutral */
+	empire			INT ,
+
+	/* The planet's happiness, or NULL if the planet is neutral */
+	happiness		REAL ,
+
+	/* The weight computed from the resource's extraction priority as
+	 * set by either the empire in general or the planet-specific settings,
+	 * or NULL if the planet is neutral.
+	 */
+	weight			DOUBLE PRECISION ,
+
+	/* The total weight computed from either the empire-wide or the
+	 * planet-specific mining settings, or NULL if the planet is neutral.
+	 */
+	total_weight	DOUBLE PRECISION
+);
+
+
+/*
+ * Mining computation - Weights view
+ * ----------------------------------
+ * 
+ * This view computes the actual values used in the mining computations for
+ * each resource provider on all empire-owned planets.
+ * 
+ * Columns:
+ *		planet_id			The planet's identifier
+ *		resource_name_id	The resource type's identifier
+ *		pmc_weight			The computed weight
+ */
+DROP VIEW IF EXISTS emp.scaled_mining_weights_view CASCADE;
+CREATE VIEW emp.scaled_mining_weights_view
+	AS SELECT planet_id , resource_name_id ,
+				POW( sys.get_constant( 'game.resources.weightBase' ) ,
+					mset_weight ) AS pmc_weight
+			FROM emp.mining_settings_view;
+
+
+/*
+ * Mining computation - Total weights view
+ * ----------------------------------------
+ *
+ * This view computes per-planet totals for actual mining weights.
+ * 
+ * Columns:
+ *		planet_id			The planet's identifier
+ *		pmc_total			The sum of all mining weights on the planet.
+ */
+DROP VIEW IF EXISTS emp.total_mining_weights_view CASCADE;
+CREATE VIEW emp.total_mining_weights_view
+	AS SELECT planet_id , SUM( pmc_weight ) AS pmc_total
+			FROM emp.scaled_mining_weights_view
+			GROUP BY planet_id;
+
+
+/*
+ * Compute the extracted quantity
+ * -------------------------------
+ *
+ * Compute the quantity of resources that will be extracted from a resource
+ * provider at the next game update. This function is used by the mining
+ * update, obviously, but also by the various functions which control mining
+ * settings and by ownership changes.
+ *
+ * Parameters:
+ *		_input		Data about the resource provider to update
+ *
+ * Returns:
+ * 		?			The quantity that will be extracted from the provider
+ */
+DROP FUNCTION IF EXISTS emp.mining_compute_extraction( emp.planet_mining_type );
+CREATE FUNCTION emp.mining_compute_extraction( _input emp.planet_mining_type )
+		RETURNS DOUBLE PRECISION
+		LANGUAGE PLPGSQL
+		STRICT IMMUTABLE
+		SECURITY INVOKER
+	AS $resprov_compute_extraction$
+
+DECLARE
+	_extraction	DOUBLE PRECISION;
+	_allocation	DOUBLE PRECISION;
+	_production	DOUBLE PRECISION;
+	_quantity	DOUBLE PRECISION;
+
+BEGIN
+	IF _input.empire IS NULL THEN
+		RETURN 0;
+	END IF;
+
+	_extraction := verse.get_extraction_factor(
+		_input.quantity / _input.quantity_max ,
+		_input.difficulty );
+	_allocation := _input.weight / _input.total_weight;
+	_production := verse.adjust_production(
+			verse.get_raw_production( _input.planet , 'MINE' ) ,
+			_input.happiness )
+		* sys.get_constant( 'game.resources.extraction' )
+		/ 1440.0; -- FIXME: hardcoded!
+
+	_quantity := _allocation * _production * _extraction;
+	IF _quantity > _input.quantity THEN
+		_quantity := _input.quantity;
+	END IF;
+
+	RETURN _quantity;
+END; 
+$resprov_compute_extraction$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.mining_compute_extraction( emp.planet_mining_type )
+	FROM PUBLIC;
+
+
+/*
+ * Get resource extraction input for a single planet
+ * --------------------------------------------------
+ *
+ * This function locks and retrieves all data required to update a single
+ * planet's resource extraction quantities. It is used when a planet's
+ * owner changes (including planet assignment) or when a planet's specific
+ * mining settings are modified.
+ * 
+ * Parameters:
+ *		_planet		The planet's identifier
+ *
+ * Returns:
+ *		the set of emp.planet_mining_type records for the planet  
+ */
+DROP FUNCTION IF EXISTS verse.mining_get_input( _planet INT );
+CREATE FUNCTION verse.mining_get_input( _planet INT )
+	RETURNS SETOF emp.planet_mining_type
+	LANGUAGE SQL
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $mining_get_input$
+
+		SELECT planet_id AS planet ,
+			resource_name_id AS resource,
+			resprov_quantity AS quantity ,
+			resprov_quantity_max AS quantity_max ,
+			resprov_difficulty AS difficulty ,
+			empire_id AS empire ,
+			( happy_pop / _planet.population ) AS happiness ,
+			pmc_weight AS weight ,
+			pmc_total AS total_weight
+
+		FROM verse.planets _planet
+			INNER JOIN verse.resource_providers _resprov
+				ON planet_id = name_id
+			INNER JOIN verse.planet_resources _pres
+				USING ( planet_id , resource_name_id )
+			LEFT OUTER JOIN (
+						SELECT _emp_planet.empire_id , _emp_planet.planet_id ,
+								_emset.resource_name_id , pmc_weight ,
+								pmc_total , _happ.current AS happy_pop
+		
+							FROM emp.planets _emp_planet
+								INNER JOIN emp.empires _emp
+									ON _emp_planet.empire_id = _emp.name_id
+								INNER JOIN emp.mining_settings _emset
+									USING ( empire_id )
+								INNER JOIN verse.planet_happiness _happ
+									USING ( planet_id )
+								INNER JOIN emp.scaled_mining_weights_view
+									USING ( planet_id , resource_name_id)
+								INNER JOIN emp.total_mining_weights_view
+									USING ( planet_id )
+								LEFT OUTER JOIN (
+											SELECT * FROM emp.planet_mining_settings
+												FOR SHARE
+										) AS _pmset
+									USING ( empire_id , planet_id , resource_name_id )
+	
+							WHERE _emp_planet.planet_id = $1
+
+							FOR SHARE OF _emp_planet , _emp , _emset , _happ 
+					) AS _owner
+				USING ( planet_id , resource_name_id )
+	
+		WHERE _planet.name_id = $1
+	
+		FOR UPDATE OF _resprov , _pres
+		FOR SHARE OF _planet ;
+
+$mining_get_input$;
+
+REVOKE EXECUTE
+	ON FUNCTION verse.mining_get_input( _planet INT )
+	FROM PUBLIC;
+
+
+/*
+ * Get resource extraction input for a whole empire
+ * -------------------------------------------------
+ *
+ * This function retrieves all mining information for a whole empire. It is
+ * used to recompute extracted quantities when global settings are updated.
+ * 
+ * Parameters:
+ *		_empire		The empire's identifier
+ *
+ * Returns:
+ *		the set of emp.planet_mining_type records for the empire  
+ */
+DROP FUNCTION IF EXISTS emp.mining_get_input( _empire INT );
+CREATE FUNCTION emp.mining_get_input( _empire INT )
+	RETURNS SETOF emp.planet_mining_type
+	LANGUAGE SQL
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $mining_get_input$
+
+		SELECT planet_id AS planet ,
+			resource_name_id AS resource,
+			resprov_quantity AS quantity ,
+			resprov_quantity_max AS quantity_max ,
+			resprov_difficulty AS difficulty ,
+			empire_id AS empire ,
+			_happ.current / _planet.population AS happiness ,
+			pmc_weight AS weight ,
+			pmc_total AS total_weight
+
+		FROM emp.planets _emp_planet
+			INNER JOIN emp.empires _emp
+				ON _emp_planet.empire_id = _emp.name_id
+			INNER JOIN emp.mining_settings _emset
+				USING ( empire_id )
+			INNER JOIN verse.planets _planet
+				ON _planet.name_id = _emp_planet.planet_id
+			INNER JOIN verse.resource_providers _resprov
+				USING ( planet_id , resource_name_id )
+			INNER JOIN verse.planet_resources _pres
+				USING ( planet_id , resource_name_id )
+			INNER JOIN verse.planet_happiness _happ
+				USING ( planet_id )
+			INNER JOIN emp.scaled_mining_weights_view
+				USING ( planet_id , resource_name_id )
+			INNER JOIN emp.total_mining_weights_view
+				USING ( planet_id )
+			LEFT OUTER JOIN (
+						SELECT * FROM emp.planet_mining_settings
+							FOR SHARE
+					) AS _pmset
+				USING ( empire_id , planet_id , resource_name_id )
+
+		WHERE _emp_planet.empire_id = $1
+	
+		FOR UPDATE OF _resprov , _pres
+		FOR SHARE OF _emp_planet , _emp , _emset , _happ , _planet ;
+
+$mining_get_input$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.mining_get_input( _empire INT )
+	FROM PUBLIC;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql b/legacyworlds-server-data/db-structure/parts/040-functions/147-empire-mining.sql
similarity index 86%
rename from legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/147-empire-mining.sql
index 8704d68..c2c0096 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-mining.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/147-empire-mining.sql
@@ -205,6 +205,7 @@ BEGIN
 			FROM mset_update
 			LIMIT 1;
 
+		-- Delete then re-insert all settings for the planet
 		DELETE FROM emp.planet_mining_settings
 			WHERE empire_id = _empire AND planet_id = _planet;
 		INSERT INTO emp.planet_mining_settings (
@@ -216,10 +217,22 @@ BEGIN
 					INNER JOIN defs.strings _str
 						ON _str.name = resource_name;
 
+		-- Update the planet's extraction
+		UPDATE verse.planet_resources
+			SET pres_income = emp.mining_compute_extraction( _update_row )
+			FROM verse.mining_get_input( _planet ) _update_row
+			WHERE planet_id = _update_row.planet
+				AND resource_name_id = _update_row.resource;
+
 	EXCEPTION
 
-		-- These are empire-wide settings
 		WHEN undefined_column THEN
+		
+			-- Get empire identifier
+			SELECT INTO _empire empire_id
+				FROM mset_update LIMIT 1;
+
+			-- Update the empire's settings
 			UPDATE emp.mining_settings _settings
 				SET empmset_weight = _update.empmset_weight
 				FROM mset_update _update
@@ -227,6 +240,14 @@ BEGIN
 						ON _str.name = _update.resource_name
 				WHERE _update.empire_id = _settings.empire_id
 					AND _str.id = _settings.resource_name_id;
+
+			-- Update extracted quantities for the whole empire
+			UPDATE verse.planet_resources
+				SET pres_income = emp.mining_compute_extraction( _update_row )
+				FROM emp.mining_get_input( _empire ) _update_row
+				WHERE planet_id = _update_row.planet
+					AND resource_name_id = _update_row.resource;
+
 	END;
 	RETURN TRUE;
 
@@ -291,9 +312,20 @@ BEGIN
 	PERFORM 1 FROM emp.planet_mining_settings _pms
 		WHERE planet_id = _planet AND empire_id = _empire;
 	IF FOUND THEN
+
+		-- Remove planet-specific settings, then update extracted quantities
 		DELETE FROM emp.planet_mining_settings
 			WHERE planet_id = _planet AND empire_id = _empire;
+		UPDATE verse.planet_resources
+			SET pres_income = emp.mining_compute_extraction( _update_row )
+			FROM verse.mining_get_input( _planet ) _update_row
+			WHERE planet_id = _update_row.planet
+				AND resource_name_id = _update_row.resource;
+
 	ELSE
+
+		-- Create planet-specific settings using empire-wide values as the
+		-- defaults. Because of that, no extraction update is necessary.
 		INSERT INTO emp.planet_mining_settings(
 				empire_id , planet_id , resource_name_id , emppmset_weight
 			) SELECT empire_id  , planet_id , resource_name_id , empmset_weight
@@ -301,6 +333,7 @@ BEGIN
 					INNER JOIN emp.mining_settings
 						USING ( resource_name_id )
 				WHERE planet_id = _planet AND empire_id = _empire;
+
 	END IF;
 	
 	RETURN TRUE;
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
index 6f9c53f..1a6ae31 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
@@ -7,87 +7,6 @@
 -- --------------------------------------------------------
 
 
-/*
- * Mining computation weights view
- * --------------------------------
- * 
- * This view computes the actual values used in the mining computations for
- * each resource provider on all empire-owned planets.
- * 
- * Columns:
- *		planet_id			The planet's identifier
- *		resource_name_id	The resource type's identifier
- *		pmc_weight			The computed weight
- */
-DROP VIEW IF EXISTS sys.gu_pmc_weights_view CASCADE;
-CREATE VIEW sys.gu_pmc_weights_view
-	AS SELECT planet_id , resource_name_id ,
-				POW( sys.get_constant( 'game.resources.weightBase' ) ,
-					mset_weight ) AS pmc_weight
-			FROM emp.mining_settings_view;
-
-
-/*
- * Mining computation - total weights view
- * ----------------------------------------
- *
- * This view computes per-planet totals for actual mining weights.
- * 
- * Columns:
- *		planet_id			The planet's identifier
- *		pmc_total			The sum of all mining weights on the planet.
- */
-DROP VIEW IF EXISTS sys.gu_pmc_totals_view CASCADE;
-CREATE VIEW sys.gu_pmc_totals_view
-	AS SELECT planet_id , SUM( pmc_weight ) AS pmc_total
-			FROM sys.gu_pmc_weights_view
-			GROUP BY planet_id;
-
-
-/*
- * Planet mining update data
- * --------------------------
- * 
- * This type is used by the records used by planet mining updates to compute a
- * planet's mining output.
- */
-DROP TYPE IF EXISTS sys.gu_pmc_data_type CASCADE;
-CREATE TYPE sys.gu_pmc_data_type AS (
-	/* The planet's identifier */
-	planet			INT ,
-
-	/* The resource's identifier */
-	resource		INT ,
-
-	/* The provider's quantity of resources */
-	quantity		DOUBLE PRECISION ,
-
-	/* The provider's maximal quantity of resources */
-	quantity_max	DOUBLE PRECISION ,
-
-	/* The provider's extraction difficulty */
-	difficulty		DOUBLE PRECISION ,
-
-	/* The empire who owns the planet, or NULL if the planet is neutral */
-	empire			INT ,
-
-	/* The planet's happiness, or NULL if the planet is neutral */
-	happiness		REAL ,
-
-	/* The weight computed from the resource's extraction priority as
-	 * set by either the empire in general or the planet-specific settings,
-	 * or NULL if the planet is neutral.
-	 */
-	weight			DOUBLE PRECISION ,
-
-	/* The total weight computed from either the empire-wide or the
-	 * planet-specific mining settings, or NULL if the planet is neutral.
-	 */
-	total_weight	DOUBLE PRECISION
-);
-
-
-
 /*
  * Lock the rows and access the data used by the mining update
  * 
@@ -105,14 +24,14 @@ CREATE TYPE sys.gu_pmc_data_type AS (
  *		planet-specific settings for one of the planets we're inspecting,
  *		are locked for share.
  *
- * The data itself is returned as a set of rows using sys.gu_pmc_data_type 
+ * The data itself is returned as a set of rows using emp.planet_mining_type
  * 
  * Parameters:
  *		_tick			The identifier of the current game update
  */ 
 DROP FUNCTION IF EXISTS sys.gu_pmc_get_data( BIGINT );
 CREATE FUNCTION sys.gu_pmc_get_data( _tick BIGINT )
-	RETURNS SETOF sys.gu_pmc_data_type
+	RETURNS SETOF emp.planet_mining_type
 	STRICT VOLATILE
 	SECURITY INVOKER
 AS $gu_pmc_get_data$
@@ -122,7 +41,7 @@ AS $gu_pmc_get_data$
 			resprov_quantity_max AS quantity_max ,
 			resprov_difficulty AS difficulty ,
 			empire_id AS empire ,
-			happiness ,
+			( happy_pop / _planet.population ) AS happiness ,
 			pmc_weight AS weight ,
 			pmc_total AS total_weight
 
@@ -137,8 +56,8 @@ AS $gu_pmc_get_data$
 				USING ( planet_id , resource_name_id )
 			LEFT OUTER JOIN (
 						SELECT _emp_planet.empire_id , _emp_planet.planet_id ,
-								_emset.resource_name_id , pmc_weight ,
-								pmc_total , _happ.current AS happiness
+								_emset.resource_name_id , pmc_weight , pmc_total ,
+								_happ.current AS happy_pop
 		
 							FROM sys.updates _upd_sys
 								INNER JOIN verse.planets_updates _upd_verse
@@ -151,9 +70,9 @@ AS $gu_pmc_get_data$
 									USING ( empire_id )
 								INNER JOIN verse.planet_happiness _happ
 									USING ( planet_id )
-								INNER JOIN sys.gu_pmc_weights_view
+								INNER JOIN emp.scaled_mining_weights_view
 									USING ( planet_id , resource_name_id)
-								INNER JOIN sys.gu_pmc_totals_view
+								INNER JOIN emp.total_mining_weights_view
 									USING ( planet_id )
 								LEFT OUTER JOIN (
 											SELECT * FROM emp.planet_mining_settings
@@ -182,69 +101,14 @@ REVOKE EXECUTE
 
 
 
-/*
- * Update a planet's resource provider and corresponding resource record
- *
- * This function will compute the amount of resources extracted from a
- * provider, and update both the provider itself and the corresponding
- * resource record (setting the income to whatever quantity was extracted).
- * 
- * Parameters:
- *		_input		Data about the resource provider to update
- */
-DROP FUNCTION IF EXISTS sys.gu_pmc_update_resource( sys.gu_pmc_data_type );
-CREATE FUNCTION sys.gu_pmc_update_resource( _input sys.gu_pmc_data_type )
-		RETURNS VOID
-		STRICT VOLATILE
-		SECURITY INVOKER
-	AS $gu_pmc_update_resource$
-
-DECLARE
-	_extraction	DOUBLE PRECISION;
-	_allocation	DOUBLE PRECISION;
-	_production	DOUBLE PRECISION;
-	_quantity	DOUBLE PRECISION;
-
-BEGIN
-
-	_extraction := verse.get_extraction_factor(
-		_input.quantity / _input.quantity_max ,
-		_input.difficulty );
-	_allocation := _input.weight / _input.total_weight;
-	_production := verse.adjust_production(
-			verse.get_raw_production( _input.planet , 'MINE' ) ,
-			_input.happiness )
-		* sys.get_constant( 'game.resources.extraction' )
-		/ 1440.0; -- FIXME: hardcoded!
-
-	PERFORM sys.write_sql_log( 'MiningUpdate' , 'TRACE' , 'Resource #' || _input.resource
-		|| ' @ planet #' || _input.planet || ': extraction ' || _extraction
-		|| ', allocation ' || _allocation || ', production ' || _production );
-	_quantity := _allocation * _production * _extraction;
-	IF _quantity > _input.quantity THEN
-		_quantity := _input.quantity;
-	END IF;
-	
-	UPDATE verse.resource_providers
-		SET resprov_quantity = resprov_quantity - _quantity
-		WHERE planet_id = _input.planet
-			AND resource_name_id = _input.resource;
-	UPDATE verse.planet_resources
-		SET pres_income = _quantity
-		WHERE planet_id = _input.planet
-			AND resource_name_id = _input.resource;
-
-END;
-$gu_pmc_update_resource$ LANGUAGE PLPGSQL;
-
-REVOKE EXECUTE
-	ON FUNCTION sys.gu_pmc_update_resource( sys.gu_pmc_data_type )
-	FROM PUBLIC;
-
-
 
 /*
  * Planet mining game update
+ * --------------------------
+ * 
+ * Lock all records involved with the update, then update the quantity of
+ * resources in resource providers depending on what was mined this turn.
+ * Finally update all extraction quantities.
  * 
  * Obtains all records to update for the current batch, then either set the
  * income to 0 without modifying the resource provider if the planet is
@@ -255,30 +119,35 @@ REVOKE EXECUTE
  */
 DROP FUNCTION IF EXISTS sys.process_planet_mining_updates( BIGINT );
 CREATE FUNCTION sys.process_planet_mining_updates( _tick BIGINT )
-		RETURNS VOID
-		STRICT VOLATILE
-		SECURITY INVOKER
-	AS $process_planet_mining_updates$
+	RETURNS VOID
+	LANGUAGE SQL
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $process_planet_mining_updates$
+	-- Lock all rows
+	SELECT 1 FROM sys.gu_pmc_get_data( $1 );
 
-DECLARE
-	_row		sys.gu_pmc_data_type;
+	-- Execute the actual mining
+	UPDATE verse.resource_providers _resprov
+		SET resprov_quantity = resprov_quantity - _pres.pres_income
+		FROM sys.updates _upd_sys
+			INNER JOIN verse.planets_updates _upd_verse
+				USING ( update_id , updtype_id , updtgt_id )
+			INNER JOIN verse.planet_resources _pres
+				ON _pres.planet_id = _upd_verse.name_id
+		WHERE _upd_sys.update_last = $1
+			AND _upd_sys.update_state = 'PROCESSING'
+			AND _pres.planet_id = _resprov.planet_id
+			AND _pres.resource_name_id = _resprov.resource_name_id;
 
-BEGIN
-	FOR _row IN SELECT * FROM sys.gu_pmc_get_data( _tick )
-	LOOP
-		IF _row.empire IS NULL THEN
-			-- Set resource income to 0 on neutrals
-			UPDATE verse.planet_resources
-				SET pres_income = 0
-				WHERE planet_id = _row.planet
-					AND resource_name_id = _row.resource;
-		ELSE
-			PERFORM sys.gu_pmc_update_resource( _row );
-		END IF;
+	-- Update extraction data
+	UPDATE verse.planet_resources
+		SET pres_income = emp.mining_compute_extraction( _update_row )
+		FROM sys.gu_pmc_get_data( $1 ) _update_row
+		WHERE planet_id = _update_row.planet
+			AND resource_name_id = _update_row.resource;
 
-	END LOOP;
-END;
-$process_planet_mining_updates$ LANGUAGE PLPGSQL;
+$process_planet_mining_updates$;
 
 
 REVOKE EXECUTE
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
index 6520694..5aa570b 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
@@ -3,7 +3,7 @@
  */
 BEGIN;
 	/* We need a pair of natural resources, a basic resource, a planet
-	 * and an empire name.
+	 * and an empire name. We add a resource provider to the planet.
 	 */
 	\i utils/strings.sql
 	\i utils/resources.sql
@@ -14,9 +14,42 @@ BEGIN;
 	SELECT _create_resources( 1 , 'basicRes' );
 	SELECT _create_raw_planets( 1 , 'testPlanet' );
 	SELECT _create_emp_names( 1 , 'testEmp' );
+	
+	INSERT INTO verse.planet_resources( planet_id , resource_name_id )
+		SELECT name_id , resource_name_id
+			FROM verse.planets CROSS JOIN defs.resources;
+
+	INSERT INTO verse.resource_providers (
+			planet_id , resource_name_id , resprov_quantity_max ,
+			resprov_quantity , resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'natRes1' ) , 100 ,
+			100 , 0.2 , 0.5
+		);
+
+	/* We replace the emp.mining_get_input() and emp.mining_compute_extraction()
+	 * functions with something we control fully.
+	 */
+	CREATE OR REPLACE FUNCTION emp.mining_get_input( _empire INT )
+		RETURNS SETOF emp.planet_mining_type
+		LANGUAGE SQL
+	AS $$
+		SELECT _get_map_name( 'testPlanet1' ) AS planet ,
+			_get_string( 'natRes1' ) AS resource ,
+			NULL::DOUBLE PRECISION AS quantity , NULL::DOUBLE PRECISION AS quantity_max ,
+			NULL::DOUBLE PRECISION AS difficulty , NULL::INT AS empire ,
+			NULL::REAL AS happiness , NULL::DOUBLE PRECISION AS weight ,
+			NULL::DOUBLE PRECISION AS total_weight;
+	$$;
+
+	CREATE OR REPLACE FUNCTION emp.mining_compute_extraction( _input emp.planet_mining_type )
+			RETURNS DOUBLE PRECISION LANGUAGE SQL
+	AS $$
+		SELECT 42::DOUBLE PRECISION;
+	$$;
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 7 );
+	SELECT plan( 8 );
 	
 	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) ,
 				_get_map_name( 'testPlanet1' ) ,
@@ -25,10 +58,12 @@ BEGIN;
 	SELECT diag_test_name( 'emp.create_empire() - Empire exists' );
 	SELECT is( COUNT(*)::INT , 1 ) FROM emp.empires
 		WHERE name_id = _get_emp_name( 'testEmp1' );
-	SELECT diag_test_name( 'emp.create_empire() - Empire cash' );
+
+	SELECT diag_test_name( 'emp.create_empire() - Empire cash set' );
 	SELECT is( cash , 200.0::REAL ) FROM emp.empires
 		WHERE name_id = _get_emp_name( 'testEmp1' );
-	SELECT diag_test_name( 'emp.create_empire() - Empire debt' );
+
+	SELECT diag_test_name( 'emp.create_empire() - Empire debt set' );
 	SELECT is( debt , 0.0::REAL ) FROM emp.empires
 		WHERE name_id = _get_emp_name( 'testEmp1' );
 
@@ -57,5 +92,11 @@ BEGIN;
 		FROM emp.resources
 		WHERE empire_id = _get_emp_name( 'testEmp1' );
 
+	SELECT diag_test_name( 'emp.create_empire() - Resource mining has been updated' );
+	SELECT is( pres_income::DOUBLE PRECISION , 42::DOUBLE PRECISION )
+		FROM verse.planet_resources
+		WHERE planet_id = _get_map_name( 'testPlanet1' )
+			AND resource_name_id = _get_string( 'natRes1' );
+
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/050-scaled-mining-weights-view.sql
similarity index 76%
rename from legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/050-scaled-mining-weights-view.sql
index d32e62f..3ab6c0f 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/050-scaled-mining-weights-view.sql
@@ -1,5 +1,5 @@
 /*
- * Test sys.gu_pmc_weights_view
+ * Test emp.scaled_mining_weights_view
  */
 BEGIN;
 	/* Create a table which will server as an alternate source for
@@ -28,14 +28,14 @@ BEGIN;
 	/***** TESTS BEGIN HERE *****/
 	SELECT plan( 2 );
 
-	SELECT diag_test_name( 'sys.gu_pmc_weights_view - Rows present' );
+	SELECT diag_test_name( 'emp.scaled_mining_weights_view - Rows present' );
 	SELECT isnt( COUNT(*)::INT , 0 )
-		FROM sys.gu_pmc_weights_view;
+		FROM emp.scaled_mining_weights_view;
 
-	SELECT diag_test_name( 'sys.gu_pmc_weights_view - weight = game.resources.weightBase ^ setting' );
+	SELECT diag_test_name( 'emp.scaled_mining_weights_view - weight = game.resources.weightBase ^ setting' );
 	SELECT sys.uoc_constant( 'game.resources.weightBase' , '(test)' , 'Resources' , 10.0 );
 	SELECT is_empty( $$
-		SELECT * FROM sys.gu_pmc_weights_view
+		SELECT * FROM emp.scaled_mining_weights_view
 			WHERE pmc_weight IS NULL
 				OR pmc_weight <> POW( 10 , resource_name_id )
 	$$ );
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/060-total-mining-weights-view.sql
similarity index 73%
rename from legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/060-total-mining-weights-view.sql
index e1cfa4c..91e6ef8 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/060-total-mining-weights-view.sql
@@ -1,9 +1,9 @@
 /*
- * Test sys.gu_pmc_totals_view
+ * Test emp.total_mining_weights_view
  */
 BEGIN;
 	/* Create a table which will server as an alternate source for
-	 * sys.gu_pmc_weights_view ; the table is not temporary (PostgreSQL
+	 * emp.scaled_mining_weights_view ; the table is not temporary (PostgreSQL
 	 * won't allow replacing the view otherwise), but will be dropped
 	 * on rollback anyway. 
 	 */
@@ -13,7 +13,7 @@ BEGIN;
 		pmc_weight			DOUBLE PRECISION
 	);
 	
-	CREATE OR REPLACE VIEW sys.gu_pmc_weights_view
+	CREATE OR REPLACE VIEW emp.scaled_mining_weights_view
 		AS SELECT * FROM fake_mining_weights;
 
 	/* Insert fake records for two different planets */
@@ -27,7 +27,7 @@ BEGIN;
 	SELECT plan( 1 );
 	
 	SELECT set_eq(
-		$$ SELECT * FROM sys.gu_pmc_totals_view $$ ,
+		$$ SELECT * FROM emp.total_mining_weights_view $$ ,
 		$$ VALUES ( 1 , 3.0 ) , ( 2 , 9.0 ) $$
 	);
 
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/070-mining-compute-extraction.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/070-mining-compute-extraction.sql
new file mode 100644
index 0000000..8748a59
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/070-mining-compute-extraction.sql
@@ -0,0 +1,135 @@
+/*
+ * Test the emp.mining_compute_extraction() function
+ */
+BEGIN;
+
+	/* Define the necessary constant */
+	SELECT sys.uoc_constant( 'game.resources.extraction' , '(test)' , 'Resources' , 10 );
+	SELECT sys.uoc_constant( 'game.happiness.strike' , '(test)' , 'Resources' , 0.5 );
+	
+	/* Make sure the functions are not immutable during the tests */
+	ALTER FUNCTION sys.get_constant( TEXT ) VOLATILE;
+	ALTER FUNCTION emp.mining_compute_extraction( emp.planet_mining_type ) VOLATILE;
+	
+	/* Replace verse.get_raw_production() with a function that returns a
+	 * value from a "temporary" table.
+	 */
+	CREATE TABLE fake_mining_production( production REAL );
+	CREATE OR REPLACE FUNCTION verse.get_raw_production( pid INT , pt building_output_type )
+		RETURNS REAL LANGUAGE SQL VOLATILE AS 'SELECT production FROM fake_mining_production';
+	INSERT INTO fake_mining_production VALUES ( 100 );
+	
+	/* Create a table that is used as the input for tests */
+	CREATE TABLE tests_input OF emp.planet_mining_type;
+	
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 9 );
+
+	INSERT INTO tests_input VALUES
+		( 0 , 0 , 1000 , 1000 , 0 , NULL , NULL , NULL , NULL ) ,  
+		( 1 , 1 , 10000 , 10000 , 0 , NULL , NULL , NULL , NULL ) ,  
+		( 2 , 2 , 100000 , 100000 , 0 , NULL , NULL , NULL , NULL );
+
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - No empire -> no extraction' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM tests_input
+			WHERE emp.mining_compute_extraction( ROW(
+					planet , resource , quantity , quantity_max ,
+					difficulty , empire , happiness , weight ,
+					total_weight ) )
+				<> 0 $$ );
+
+	DELETE FROM tests_input;
+	INSERT INTO tests_input VALUES
+		( 0 , 0 , 1000 , 1000 , 0 , 1 , 1 , 1 , 1 ) ,  
+		( 1 , 1 , 10000 , 10000 , 0 , 1 , 1 , 1 , 1 ) ,  
+		( 2 , 2 , 100000 , 100000 , 0 , 1 , 1 , 1 , 1 );
+
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - Extracted quantity > 0 if empire and resources are present' );
+	SELECT is_empty( $$
+		SELECT *
+			FROM tests_input
+			WHERE emp.mining_compute_extraction( ROW(
+					planet , resource , quantity , quantity_max ,
+					difficulty , empire , happiness , weight ,
+					total_weight ) )
+				= 0 $$ );
+
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - Extracted quantity does not change for same initial ratio' );
+	SELECT is( COUNT( DISTINCT emp.mining_compute_extraction( ROW(
+							planet , resource , quantity , quantity_max ,
+							difficulty , empire , happiness , weight ,
+							total_weight ) )
+				)::INT , 1 ) FROM tests_input;
+
+	UPDATE tests_input SET weight = 1 + planet , total_weight = 3;
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - Result increases when weight/total weight ratio increases' );
+	SELECT set_eq(
+		$$ SELECT rank() OVER ( ORDER BY weight ) AS r1 ,
+					rank() OVER( ORDER BY emp.mining_compute_extraction( ROW(
+							planet , resource , quantity , quantity_max ,
+							difficulty , empire , happiness , weight ,
+							total_weight ) ) ) AS r2
+				FROM tests_input $$ ,
+		$$ VALUES ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) $$
+	);
+
+	UPDATE tests_input SET weight = total_weight , happiness = 0.5 + 0.25 * planet;
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - Happiness does not affect result when greater than strike level' );
+	SELECT is( COUNT( DISTINCT emp.mining_compute_extraction( ROW(
+							planet , resource , quantity , quantity_max ,
+							difficulty , empire , happiness , weight ,
+							total_weight ) )
+				)::INT , 1 ) FROM tests_input;
+
+	UPDATE tests_input SET weight = total_weight , happiness = 0.2 * planet;
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - Result increases with happiness when lower than strike level' );
+	SELECT set_eq(
+		$$ SELECT rank() OVER ( ORDER BY happiness ) AS r1 ,
+					rank() OVER( ORDER BY emp.mining_compute_extraction( ROW(
+							planet , resource , quantity , quantity_max ,
+							difficulty , empire , happiness , weight ,
+							total_weight ) ) ) AS r2
+				FROM tests_input $$ ,
+		$$ VALUES ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) $$
+	);
+
+	UPDATE fake_mining_production SET production = 10000;
+	UPDATE tests_input SET quantity = 1 , quantity_max = 1 , happiness = 1;
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - Result <= quantity' );
+	SELECT is_empty(
+		$$ SELECT * FROM tests_input
+			WHERE quantity < emp.mining_compute_extraction( ROW(
+							planet , resource , quantity , quantity_max ,
+							difficulty , empire , happiness , weight ,
+							total_weight ) ) $$
+	);
+
+	UPDATE fake_mining_production SET production = 1;
+	UPDATE tests_input SET quantity = 100 * planet , quantity_max = 200;
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - Result decreases when quantity/capacity ratio decreases' );
+	SELECT set_eq(
+		$$ SELECT rank() OVER ( ORDER BY quantity ) AS r1 ,
+					rank() OVER( ORDER BY emp.mining_compute_extraction( ROW(
+							planet , resource , quantity , quantity_max ,
+							difficulty , empire , happiness , weight ,
+							total_weight ) ) ) AS r2
+				FROM tests_input $$ ,
+		$$ VALUES ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) $$
+	);
+
+	UPDATE tests_input SET quantity = quantity_max , difficulty = 0.33 * planet;
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - Result decreases when difficulty increases' );
+	SELECT set_eq(
+		$$ SELECT rank() OVER ( ORDER BY difficulty DESC ) AS r1 ,
+					rank() OVER( ORDER BY emp.mining_compute_extraction( ROW(
+							planet , resource , quantity , quantity_max ,
+							difficulty , empire , happiness , weight ,
+							total_weight ) ) ) AS r2
+				FROM tests_input $$ ,
+		$$ VALUES ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/080-verse-mining-get-input.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/080-verse-mining-get-input.sql
new file mode 100644
index 0000000..89b9639
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/080-verse-mining-get-input.sql
@@ -0,0 +1,88 @@
+/*
+ * Tests for the verse.mining_get_input() function
+ */
+BEGIN;
+	\i utils/common-setup/setup-gu-pmc-get-data-test.sql
+	
+	SELECT plan( 9 );
+
+	SELECT diag_test_name( 'verse.mining_get_input() - Neutral planet without resource providers -> no rows' );
+	SELECT is_empty( $$ SELECT * FROM verse.mining_get_input( _get_map_name( 'planet1' ) ) $$ );
+
+	CREATE TEMPORARY TABLE test_results
+		AS SELECT * FROM verse.mining_get_input( _get_map_name( 'planet2' ) );
+	SELECT diag_test_name( 'verse.mining_get_input() - Neutral planet with resource providers - Rows included' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet2' )
+			AND difficulty = 0.2 AND empire IS NULL
+			AND happiness IS NULL AND weight IS NULL
+			AND total_weight IS NULL;
+	SELECT diag_test_name( 'verse.mining_get_input() - Neutral planet with resource providers - No extra rows' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE NOT ( planet = _get_map_name ( 'planet2' )
+					AND difficulty = 0.2 AND empire IS NULL
+					AND happiness IS NULL AND weight IS NULL
+					AND total_weight IS NULL );
+	$$ );
+	DROP TABLE test_results;
+
+	CREATE TEMPORARY TABLE test_results
+		AS SELECT * FROM verse.mining_get_input( _get_map_name( 'planet3' ) );
+	SELECT diag_test_name( 'verse.mining_get_input() - Planet using empire settings - Rows included' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet3' ) AND difficulty = 0.3
+			AND empire = _get_emp_name( 'empire1' )
+			AND happiness IS NOT NULL AND weight = 100
+			AND total_weight = 200;
+	SELECT diag_test_name( 'verse.mining_get_input() - Planet using empire settings - No extra rows' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE NOT ( planet = _get_map_name ( 'planet3' )
+					AND difficulty = 0.3
+					AND empire = _get_emp_name( 'empire1' )
+					AND happiness IS NOT NULL
+					AND weight = 100 AND total_weight = 200 );
+	$$ );
+	DROP TABLE test_results;
+
+	CREATE TEMPORARY TABLE test_results
+		AS SELECT * FROM verse.mining_get_input( _get_map_name( 'planet4' ) );
+	SELECT diag_test_name( 'verse.mining_get_input() - Planet using specific settings - Rows included' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet4' ) AND difficulty = 0.4
+			AND empire = _get_emp_name( 'empire2' )
+			AND happiness IS NOT NULL AND (
+				( resource = _get_string( 'resource1' ) AND weight = 10 )
+				OR ( resource = _get_string( 'resource2' ) AND weight = 1000 ) )
+			AND total_weight = 1010;
+	SELECT diag_test_name( 'verse.mining_get_input() - Planet using specific settings - No extra rows' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE NOT ( planet = _get_map_name ( 'planet4' )
+					AND difficulty = 0.4 AND empire = _get_emp_name( 'empire2' )
+					AND happiness IS NOT NULL AND total_weight = 1010
+					AND ( ( resource = _get_string( 'resource1' ) AND weight = 10 )
+						OR ( resource = _get_string( 'resource2' ) AND weight = 1000 ) ) );
+	$$ );
+	DROP TABLE test_results;
+
+	SELECT diag_test_name( 'verse.mining_get_input() - Owned planet without resource providers -> no rows' );
+	SELECT is_empty( $$ SELECT * FROM verse.mining_get_input( _get_map_name( 'planet5' ) ) $$ );
+
+	CREATE TEMPORARY TABLE test_results
+		AS SELECT * FROM verse.mining_get_input( _get_map_name( 'planet6' ) );
+	SELECT diag_test_name( 'verse.mining_get_input() - Selects planets independently of update state' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet6' ) AND difficulty = 0.6
+			AND empire = _get_emp_name( 'empire4' )
+			AND happiness IS NOT NULL AND weight = 100
+			AND total_weight = 200;
+	DROP TABLE test_results;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/090-emp-mining-get-input.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/090-emp-mining-get-input.sql
new file mode 100644
index 0000000..bc5736a
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/145-resource-providers/090-emp-mining-get-input.sql
@@ -0,0 +1,69 @@
+/*
+ * Tests for the emp.mining_get_input() function
+ */
+BEGIN;
+	\i utils/common-setup/setup-gu-pmc-get-data-test.sql
+	
+	SELECT plan( 7 );
+
+	CREATE TEMPORARY TABLE test_results
+		AS SELECT * FROM emp.mining_get_input( _get_emp_name( 'empire1' ) );
+	SELECT diag_test_name( 'emp.mining_get_input() - Empire with a planet using empire settings - Rows included' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet3' ) AND difficulty = 0.3
+			AND empire = _get_emp_name( 'empire1' )
+			AND happiness IS NOT NULL AND weight = 100
+			AND total_weight = 200;
+	SELECT diag_test_name( 'emp.mining_get_input() - Empire with a planet using empire settings - No extra rows' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE NOT ( planet = _get_map_name ( 'planet3' )
+					AND difficulty = 0.3
+					AND empire = _get_emp_name( 'empire1' )
+					AND happiness IS NOT NULL
+					AND weight = 100 AND total_weight = 200 );
+	$$ );
+	DROP TABLE test_results;
+
+	CREATE TEMPORARY TABLE test_results
+		AS SELECT * FROM emp.mining_get_input( _get_emp_name( 'empire2' ) );
+	SELECT diag_test_name( 'emp.mining_get_input() - Empire with a planet using specific settings - Rows included' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet4' ) AND difficulty = 0.4
+			AND empire = _get_emp_name( 'empire2' )
+			AND happiness IS NOT NULL AND (
+				( resource = _get_string( 'resource1' ) AND weight = 10 )
+				OR ( resource = _get_string( 'resource2' ) AND weight = 1000 ) )
+			AND total_weight = 1010;
+	SELECT diag_test_name( 'emp.mining_get_input() - Empire with a planet using specific settings - No extra rows' );
+	SELECT is_empty( $$
+		SELECT * FROM test_results
+			WHERE NOT ( planet = _get_map_name ( 'planet4' )
+					AND difficulty = 0.4 AND empire = _get_emp_name( 'empire2' )
+					AND happiness IS NOT NULL AND total_weight = 1010
+					AND ( ( resource = _get_string( 'resource1' ) AND weight = 10 )
+						OR ( resource = _get_string( 'resource2' ) AND weight = 1000 ) ) );
+	$$ );
+	DROP TABLE test_results;
+
+	SELECT diag_test_name( 'emp.mining_get_input() - Owned planet without resource providers -> no rows' );
+	SELECT is_empty( $$ SELECT * FROM emp.mining_get_input( _get_emp_name( 'empire3' ) ) $$ );
+
+	CREATE TEMPORARY TABLE test_results
+		AS SELECT * FROM emp.mining_get_input( _get_emp_name( 'empire4' ) );
+	SELECT diag_test_name( 'emp.mining_get_input() - Selects planets independently of update state' );
+	SELECT is( COUNT(*)::INT , 2)
+		FROM test_results
+		WHERE planet = _get_map_name ( 'planet6' ) AND difficulty = 0.6
+			AND empire = _get_emp_name( 'empire4' )
+			AND happiness IS NOT NULL AND weight = 100
+			AND total_weight = 200;
+	DROP TABLE test_results;
+
+	SELECT diag_test_name( 'emp.mining_get_input() - Empire with no planets -> no rows' );
+	SELECT is_empty( $$ SELECT * FROM emp.mining_get_input( _get_emp_name( 'empire5' ) ) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/010-mset-update-start.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/010-mset-update-start.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/010-mset-update-start.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/015-mset-update-start-planet.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/015-mset-update-start-planet.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/015-mset-update-start-planet.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/020-mset-update-set.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/020-mset-update-set.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/020-mset-update-set.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/030-mset-update-apply.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/030-mset-update-apply.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/030-mset-update-apply.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/040-mset-toggle-source.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/040-mset-toggle-source.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-mining/040-mset-toggle-source.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/147-empire-mining/040-mset-toggle-source.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-get-data.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
rename to legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/010-gu-pmc-get-data.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-process-planet-mining-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-process-planet-mining-updates.sql
new file mode 100644
index 0000000..3aabf15
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/020-process-planet-mining-updates.sql
@@ -0,0 +1,89 @@
+/*
+ * Test the sys.process_planet_mining_updates() function
+ */
+BEGIN;
+	/* Disable other update types */
+	DELETE FROM sys.update_types
+		WHERE updtype_name <> 'PlanetMining';
+
+	/* We need two planets with identicals resource records and resource providers. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+
+	SELECT _create_natural_resources( 1 , 'resource' );
+	SELECT _create_raw_planets( 2 , 'planet' );
+
+	INSERT INTO verse.resource_providers (
+			planet_id , resource_name_id , resprov_quantity_max ,
+			resprov_quantity , resprov_difficulty , resprov_recovery
+		) VALUES (
+			_get_map_name( 'planet1' ) , _get_string( 'resource1' ) , 100 ,
+			100 , 0.2 , 0.5
+		) , (
+			_get_map_name( 'planet2' ) , _get_string( 'resource1' ) , 100 ,
+			100 , 0.2 , 0.5
+		);
+
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id , pres_income , pres_upkeep )
+		SELECT p.name_id , r.resource_name_id , 50 , 0
+			FROM verse.planets p CROSS JOIN defs.resources r;
+
+	/* First planet will be updated, second will not */
+	UPDATE sys.updates su
+		SET update_state = 'PROCESSING' , update_last = 0
+		FROM verse.planets_updates vu
+		WHERE vu.update_id = su.update_id
+			AND vu.name_id =  _get_map_name( 'planet1' );
+	UPDATE sys.updates su
+		SET update_state = 'PROCESSED' , update_last = 0
+		FROM verse.planets_updates vu
+		WHERE vu.update_id = su.update_id
+			AND vu.name_id = _get_map_name( 'planet2' );
+
+	/* Replace sys.gu_pmc_get_data() and emp.mining_compute_extraction(); both
+	 * functions will write to temporary tables. 
+	 */
+	CREATE TABLE _get_data_calls( tick BIGINT );
+	CREATE OR REPLACE FUNCTION sys.gu_pmc_get_data( _tick BIGINT )
+		RETURNS SETOF emp.planet_mining_type LANGUAGE SQL
+	AS $$
+		INSERT INTO _get_data_calls VALUES ( $1 );
+		SELECT _get_map_name( 'planet1' ) AS planet ,
+			_get_string( 'resource1' ) AS resource ,
+			NULL::DOUBLE PRECISION AS quantity , NULL::DOUBLE PRECISION AS quantity_max ,
+			NULL::DOUBLE PRECISION AS difficulty , NULL::INT AS empire ,
+			NULL::REAL AS happiness , NULL::DOUBLE PRECISION AS weight ,
+			NULL::DOUBLE PRECISION AS total_weight;
+	$$;
+	
+	CREATE OR REPLACE FUNCTION emp.mining_compute_extraction( _input emp.planet_mining_type )
+			RETURNS DOUBLE PRECISION LANGUAGE SQL
+	AS $$
+		SELECT 42::DOUBLE PRECISION;
+	$$;
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 3 );
+	
+	SELECT sys.process_planet_mining_updates( 0 );
+
+	SELECT diag_test_name( 'sys.process_planet_mining_updates() - Resource providers updated' );
+	SELECT set_eq(
+		$$ SELECT planet_id , resprov_quantity FROM verse.resource_providers $$ ,
+		$$ VALUES ( _get_map_name( 'planet1' ) , 50 ) , ( _get_map_name( 'planet2' ) , 100 ) $$
+	);
+
+	SELECT diag_test_name( 'sys.process_planet_mining_updates() - Planet resources updated' );
+	SELECT set_eq(
+		$$ SELECT planet_id , pres_income FROM verse.planet_resources $$ ,
+		$$ VALUES ( _get_map_name( 'planet1' ) , 42 ) , ( _get_map_name( 'planet2' ) , 50 ) $$
+	);
+
+	SELECT diag_test_name( 'sys.process_planet_mining_updates() - Two calls to gu_pmc_get_data()' );
+	SELECT is( COUNT(*)::INT , 2 ) FROM _get_data_calls;
+
+	SELECT * FROM finish( );
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
deleted file mode 100644
index f25019f..0000000
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Test the sys.gu_pmc_update_resource() function
- */
-BEGIN;
-	/*
-	 * We need to create a set of both resource providers and planet resource
-	 * records that will be updated by the function when it is called. We
-	 * disable foreign keys on both tables, as it makes things simpler. We
-	 * also replace verse.get_raw_production(), verse.get_extraction_factor( )
-	 * and verse.adjust_production() so that they return fixed values, and we
-	 * need to set the game.resources.extraction constant.
-	 */
-	ALTER TABLE verse.resource_providers
-		DROP CONSTRAINT fk_resprov_planet ,
-		DROP CONSTRAINT fk_resprov_resource;
-	ALTER TABLE verse.planet_resources
-		DROP CONSTRAINT fk_pres_planet ,
-		DROP CONSTRAINT fk_pres_resource;
-
-	INSERT INTO verse.resource_providers(
-			planet_id , resource_name_id , resprov_quantity_max, resprov_quantity ,
-			resprov_difficulty , resprov_recovery
-		) VALUES (
-			1 , 1 , 1000 , 1000 , 0 , 0.5
-		);
-
-	INSERT INTO verse.planet_resources(
-			planet_id , resource_name_id , pres_income , pres_upkeep
-		) VALUES (
-			1 , 1 , 0 , 0
-		);
-
-	CREATE OR REPLACE FUNCTION verse.get_raw_production( pid INT , pt building_output_type )
-		RETURNS REAL
-	AS $$
-		SELECT 1.0::REAL;
-	$$ LANGUAGE SQL;
-
-	CREATE OR REPLACE FUNCTION verse.adjust_production( prod REAL , happiness REAL )
-		RETURNS REAL
-	AS $$
-		SELECT 1.0::REAL;
-	$$ LANGUAGE SQL;
-
-	CREATE OR REPLACE FUNCTION verse.get_extraction_factor( _fill_ratio DOUBLE PRECISION , _difficulty DOUBLE PRECISION )
-		RETURNS DOUBLE PRECISION
-	AS $$
-		SELECT ( CASE WHEN $1 = 1.0 AND $2 = 0 THEN 0.002 ELSE 0.0 END )::DOUBLE PRECISION;
-	$$ LANGUAGE SQL;
-
-	SELECT sys.uoc_constant( 'game.resources.extraction' , '(test)' , 'Resources' , 1440.0 );
-	ALTER FUNCTION sys.get_constant( TEXT ) VOLATILE;
-
-
-	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 4 );
-
-	SELECT sys.gu_pmc_update_resource( ROW(
-		1 , 1 , 1000.0 , 1000.0 , 0.0 , NULL , 1.0 , 0.5 , 1.0
-	) );
-	
-	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Provider udpated' );
-	SELECT is( resprov_quantity , 999.999::DOUBLE PRECISION )
-		FROM verse.resource_providers
-		WHERE planet_id = 1 AND resource_name_id = 1;
-	
-	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Planet resources income udpated' );
-	SELECT is( pres_income , 0.001::DOUBLE PRECISION )
-		FROM verse.planet_resources
-		WHERE planet_id = 1 AND resource_name_id = 1;
-
-	UPDATE verse.resource_providers SET resprov_quantity = resprov_quantity_max;
-	UPDATE sys.constant_definitions
-		SET c_value = 14400000.0
-		WHERE name = 'game.resources.extraction';
-	SELECT sys.gu_pmc_update_resource( ROW(
-		1 , 1 , 1000.0 , 1000.0 , 0.0 , NULL , 1.0 , 0.5 , 1.0
-	) );
-
-	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Bounded extraction quantity (1/2)' );
-	SELECT is( resprov_quantity , 990.0::DOUBLE PRECISION )
-		FROM verse.resource_providers
-		WHERE planet_id = 1 AND resource_name_id = 1;
-
-	SELECT diag_test_name( 'sys.gu_pmc_update_resource( ) - Bounded extraction quantity (2/2)' );
-	SELECT is( pres_income , 10.0::DOUBLE PRECISION )
-		FROM verse.planet_resources
-		WHERE planet_id = 1 AND resource_name_id = 1;
-
-	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
deleted file mode 100644
index 19da05c..0000000
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Test the sys.process_planet_mining_updates() function
- */
-BEGIN;
-	/*
-	 * Create a fake planet resource record which will be updated by the
-	 * function (dropping the foreign key is therefore needed).
-	 */
-	ALTER TABLE verse.planet_resources
-		DROP CONSTRAINT fk_pres_planet ,
-		DROP CONSTRAINT fk_pres_resource;
-
-	INSERT INTO verse.planet_resources(
-			planet_id , resource_name_id , pres_income , pres_upkeep
-		) VALUES (
-			1 , 1 , 42 , 0
-		);
-
-	/*
-	 * Create a table which contains the values which will be returned by
-	 * the fake sys.gu_pmc_get_data( ).
-	 */
-	CREATE TABLE _fake_update_data OF sys.gu_pmc_data_type;
-	INSERT INTO _fake_update_data VALUES (
-		1 , 1 , 1000.0 , 1000.0 , 0.5 , NULL , NULL , NULL , NULL ) ,
-		( 2 , 1 , 1000.0 , 1000.0 , 0.5 , 1 , 0.5 , 1.0 , 1.0 );
-	CREATE OR REPLACE FUNCTION sys.gu_pmc_get_data( _tick BIGINT )
-		RETURNS SETOF sys.gu_pmc_data_type
-	AS $$
-		SELECT * FROM _fake_update_data;
-	$$ LANGUAGE SQL;
-
-	/*
-	 * Replace sys.gu_pmc_update_resource() so it deletes records from the
-	 * table.
-	 */
-	CREATE OR REPLACE FUNCTION sys.gu_pmc_update_resource( _input sys.gu_pmc_data_type )
-		RETURNS VOID
-	AS $$
-		DELETE FROM _fake_update_data WHERE planet = $1.planet AND resource = $1.resource;
-	$$ LANGUAGE SQL;
-	
-
-	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 2 );
-	
-	SELECT sys.process_planet_mining_updates( 0 );
-
-	SELECT diag_test_name( 'sys.process_planet_mining_updates() - Neutral planets updated' );
-	SELECT is( pres_income , 0::DOUBLE PRECISION ) FROM verse.planet_resources;
-
-	SELECT diag_test_name( 'sys.process_planet_mining_updates() - Owned planets updated' );
-	SELECT is_empty(
-		$$ SELECT * FROM _fake_update_data WHERE planet = 2 $$
-	);
-
-	SELECT * FROM finish( );
-ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/050-scaled-mining-weights-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/050-scaled-mining-weights-view.sql
new file mode 100644
index 0000000..76f2b0e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/050-scaled-mining-weights-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on emp.scaled_mining_weights_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.scaled_mining_weights_view - No SELECT privilege' );
+	SELECT throws_ok( 'SELECT * FROM emp.scaled_mining_weights_view' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/060-total-mining-weights-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/060-total-mining-weights-view.sql
new file mode 100644
index 0000000..5d0f0ae
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/060-total-mining-weights-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on emp.total_mining_weights_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.total_mining_weights_view - No SELECT privilege' );
+	SELECT throws_ok( 'SELECT * FROM emp.total_mining_weights_view' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/070-mining-compute-extraction.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/070-mining-compute-extraction.sql
new file mode 100644
index 0000000..64a95e3
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/070-mining-compute-extraction.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on emp.mining_compute_extraction()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.mining_compute_extraction() - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT emp.mining_compute_extraction( ROW( 1 , 2 , 3 , 4 , 5 , NULL , NULL , NULL , NULL ) )
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/080-verse-mining-get-input.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/080-verse-mining-get-input.sql
new file mode 100644
index 0000000..95a1561
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/080-verse-mining-get-input.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on verse.mining_get_input()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'verse.mining_get_input() - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT verse.mining_get_input( 1 )
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/090-emp-mining-get-input.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/090-emp-mining-get-input.sql
new file mode 100644
index 0000000..ac77387
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/145-resource-providers/090-emp-mining-get-input.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on emp.mining_get_input()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.mining_get_input() - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT emp.mining_get_input( 1 )
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/010-mset-update-start.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/010-mset-update-start.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/010-mset-update-start.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/010-mset-update-start.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/015-mset-update-start-planet.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/015-mset-update-start-planet.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/015-mset-update-start-planet.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/015-mset-update-start-planet.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/020-mset-update-set.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/020-mset-update-set.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/020-mset-update-set.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/030-mset-update-apply.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/030-mset-update-apply.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/030-mset-update-apply.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/030-mset-update-apply.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/040-mset-toggle-source.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/040-mset-toggle-source.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-mining/040-mset-toggle-source.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/147-empire-mining/040-mset-toggle-source.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/030-gu-pmc-get-data.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-get-data.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/030-gu-pmc-get-data.sql
rename to legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-get-data.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
deleted file mode 100644
index adb0975..0000000
--- a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/010-gu-pmc-weights-view.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Test privileges on sys.gu_pmc_weights_view
- */
-BEGIN;
-	SELECT plan( 1 );
-
-	SELECT diag_test_name( 'sys.gu_pmc_weights_view - Privileges' );
-	SELECT throws_ok( 'SELECT * FROM sys.gu_pmc_weights_view' , 42501 );
-	
-	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
deleted file mode 100644
index f4d2b70..0000000
--- a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-gu-pmc-totals-view.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Test privileges on sys.gu_pmc_totals_view
- */
-BEGIN;
-	SELECT plan( 1 );
-
-	SELECT diag_test_name( 'sys.gu_pmc_totals_view - Privileges' );
-	SELECT throws_ok( 'SELECT * FROM sys.gu_pmc_totals_view' , 42501 );
-	
-	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/050-process-planet-mining-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-process-planet-mining-updates.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/050-process-planet-mining-updates.sql
rename to legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/020-process-planet-mining-updates.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
deleted file mode 100644
index 7de5605..0000000
--- a/legacyworlds-server-data/db-structure/tests/user/050-updates/120-planet-mining/040-gu-pmc-update-resource.sql
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Test privileges on sys.gu_pmc_update_resource( )
- */
-BEGIN;
-	SELECT plan( 1 );
-
-	SELECT diag_test_name( 'sys.gu_pmc_update_resource - Privileges' );
-	SELECT throws_ok( $$
-		SELECT * FROM sys.gu_pmc_update_resource( ROW( NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL ) );
-	$$ , 42501 );
-	
-	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
index 57d62d3..11bb7b4 100644
--- a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
+++ b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
@@ -37,7 +37,7 @@ DELETE FROM sys.update_types
 SELECT sys.uoc_constant( 'game.resources.weightBase' , '(test)' , 'Resources' , 10.0 );
 SELECT _create_natural_resources( 2 , 'resource' );
 SELECT _create_raw_planets( 6 , 'planet' );
-SELECT _create_emp_names( 4 , 'empire' );
+SELECT _create_emp_names( 5 , 'empire' );
 INSERT INTO emp.empires ( name_id , cash )
 	SELECT id , 0 FROM naming.empire_names;
 

From 96670d45becba71f187a33510c0ac92c2477bc25 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Wed, 15 Feb 2012 14:45:43 +0100
Subject: [PATCH 52/94] Resources information on planet list

* Added resource information records to the planet list's response.

* Added a database view and the corresponding row mapper and DAO method
which can be used as the data source for the planet list's resource
information. For now this view always returns 0 for both civilian and
military investments.

* Added new tab to display resource information on the planet list page.
The old version of the economy tab will be kept until the corresponding
data no longer exists.

* The following SQL scripts must be re-executed to upgrade a database:
  -> 040-functions/167-planet-list.sql
---
 .../game/resources/FullPlanetListRecord.java  |  65 +++
 .../PlanetListResourceRecordMapper.java       |  72 ++++
 .../ResourcesInformationDAOBean.java          |  41 ++
 .../lw/beans/empire/EmpireManagementBean.java |  17 +-
 .../parts/040-functions/167-planet-list.sql   |  54 ++-
 .../010-plist-resources-view.sql              |  78 ++++
 .../010-plist-resources-view.sql              |  11 +
 .../resources/ResourcesInformationDAO.java    |  14 +
 .../TestPlanetListResourceRecordMapper.java   |  91 +++++
 .../lw/cmd/player/gdata/PlanetListData.java   | 384 ++++++++++++++++--
 .../gdata/PlanetListResourceRecord.java       | 161 ++++++++
 .../Raw/WEB-INF/fm/en/types/planets.ftl       |   5 +-
 .../WEB-INF/fm/en/types/planets/economy.ftl   |  56 +++
 .../Raw/WEB-INF/fm/fr/types/planets.ftl       |   5 +-
 .../WEB-INF/fm/fr/types/planets/economy.ftl   |  56 +++
 15 files changed, 1079 insertions(+), 31 deletions(-)
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/FullPlanetListRecord.java
 create mode 100644 legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/PlanetListResourceRecordMapper.java
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/167-planet-list/010-plist-resources-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/167-planet-list/010-plist-resources-view.sql
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestPlanetListResourceRecordMapper.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListResourceRecord.java
 create mode 100644 legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets/economy.ftl
 create mode 100644 legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets/economy.ftl

diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/FullPlanetListRecord.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/FullPlanetListRecord.java
new file mode 100644
index 0000000..786a96a
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/FullPlanetListRecord.java
@@ -0,0 +1,65 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import com.deepclone.lw.cmd.player.gdata.PlanetListResourceRecord;
+
+
+
+/**
+ * Intermediary class for planet list resource records extraction
+ * 
+ * <p>
+ * This class is used as an intermediary to extract {@link PlanetListResourceRecord} instances from
+ * <code>emp.plist_resources_view</code>. It contains the instance itself, and an additional field
+ * which indicates the planet's identifier.
+ * 
+ * <p>
+ * Instances of this class are returned by {@link PlanetListResourceRecordMapper} and then processed
+ * to generate the map returned by {@link ResourcesInformationDAOBean#getPlanetListData(int)}.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+class FullPlanetListRecord
+{
+	/** The planet's identifier */
+	private int planetId;
+
+	/** The resource record */
+	private final PlanetListResourceRecord record = new PlanetListResourceRecord( );
+
+
+	/**
+	 * Gets the planet's identifier.
+	 * 
+	 * @return the planet's identifier
+	 */
+	public int getPlanetId( )
+	{
+		return this.planetId;
+	}
+
+
+	/**
+	 * Sets the planet's identifier.
+	 * 
+	 * @param planetId
+	 *            the new planet's identifier
+	 */
+	public void setPlanetId( int planetId )
+	{
+		this.planetId = planetId;
+	}
+
+
+	/**
+	 * Gets the resource record.
+	 * 
+	 * @return the resource record
+	 */
+	public PlanetListResourceRecord getRecord( )
+	{
+		return this.record;
+	}
+
+}
diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/PlanetListResourceRecordMapper.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/PlanetListResourceRecordMapper.java
new file mode 100644
index 0000000..c09f25b
--- /dev/null
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/PlanetListResourceRecordMapper.java
@@ -0,0 +1,72 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.cmd.player.gdata.PlanetListResourceRecord;
+
+
+
+/**
+ * Planet list resource records mapper
+ * 
+ * <p>
+ * This class is responsible for mapping rows from <code>emp.plist_resources_view</code> into
+ * {@link FullPlanetListRecord} instances.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class PlanetListResourceRecordMapper
+		implements RowMapper< FullPlanetListRecord >
+{
+
+	/**
+	 * Map a row from <code>emp.plist_resources_view</code>
+	 * 
+	 * <p>
+	 * Convert a row from <code>emp.plist_resources_view</code> into a {@link FullPlanetListRecord}
+	 * instance. The resulting instance will have both its planet identifier and actual record set
+	 * according to the row's contents.
+	 */
+	@Override
+	public FullPlanetListRecord mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		FullPlanetListRecord fullRecord = new FullPlanetListRecord( );
+
+		fullRecord.setPlanetId( rs.getInt( "planet_id" ) );
+		this.extractRecord( fullRecord.getRecord( ) , rs );
+
+		return fullRecord;
+	}
+
+
+	/**
+	 * Extract the contents of the actual record
+	 * 
+	 * <p>
+	 * This method extracts the contents of the record that will be sent to the client from the
+	 * result set.
+	 * 
+	 * @param record
+	 *            the planet list resource record to update
+	 * @param rs
+	 *            the result set to extract information from
+	 * 
+	 * @throws SQLException
+	 *             if a SQLException is encountered getting column values
+	 */
+	private void extractRecord( PlanetListResourceRecord record , ResultSet rs )
+			throws SQLException
+	{
+		record.setName( rs.getString( "resource_name" ) );
+		record.setIncome( rs.getLong( "pres_income" ) );
+		record.setUpkeep( rs.getLong( "pres_upkeep" ) );
+		record.setCivInvestment( rs.getLong( "civ_investment" ) );
+		record.setMilInvestment( rs.getLong( "mil_investment" ) );
+	}
+
+}
diff --git a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesInformationDAOBean.java b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesInformationDAOBean.java
index 651d4e5..ca6b415 100644
--- a/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesInformationDAOBean.java
+++ b/legacyworlds-server-beans-resources/src/main/java/com/deepclone/lw/beans/game/resources/ResourcesInformationDAOBean.java
@@ -1,13 +1,17 @@
 package com.deepclone.lw.beans.game.resources;
 
 
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 
+import com.deepclone.lw.cmd.player.gdata.PlanetListResourceRecord;
 import com.deepclone.lw.cmd.player.gdata.empire.EmpireResourceRecord;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetResourceRecord;
 import com.deepclone.lw.interfaces.game.resources.ResourcesInformationDAO;
@@ -37,12 +41,18 @@ class ResourcesInformationDAOBean
 	/** SQL query that fetches an empire's resources information */
 	private static final String Q_EMPIRE_RESOURCES = "SELECT * FROM emp.resources_view WHERE empire_id = ?";
 
+	/** SQL query that fetches the resource records for an empire's planet list */
+	private static final String Q_PLIST_RESOURCES = "SELECT * FROM emp.plist_resources_view WHERE empire_id = ?";
+
 	/** Row mapper for planet resources */
 	private final PlanetResourceMapper mPlanetResource;
 
 	/** Row mapper for empire resources */
 	private final EmpireResourceMapper mEmpireResource;
 
+	/** Row mapper for planet list records */
+	private final PlanetListResourceRecordMapper mPlanetListRecord;
+
 	/** Spring JDBC interface */
 	private JdbcTemplate dTemplate;
 
@@ -52,6 +62,7 @@ class ResourcesInformationDAOBean
 	{
 		this.mPlanetResource = new PlanetResourceMapper( );
 		this.mEmpireResource = new EmpireResourceMapper( );
+		this.mPlanetListRecord = new PlanetListResourceRecordMapper( );
 	}
 
 
@@ -96,4 +107,34 @@ class ResourcesInformationDAOBean
 		return this.dTemplate.query( Q_EMPIRE_RESOURCES , this.mEmpireResource , empire );
 	}
 
+
+	/**
+	 * Run the planet list resources query and extract the data
+	 * 
+	 * <p>
+	 * This implementation queries <code>emp.plist_resources_view</code> to obtain the planet list's
+	 * records, then maps the resulting rows using the intermediary {@link FullPlanetListRecord}
+	 * class, and finally associates records to the corresponding planet identifiers.
+	 */
+	@Override
+	public Map< Integer , List< PlanetListResourceRecord > > getPlanetListData( int empire )
+	{
+		Map< Integer , List< PlanetListResourceRecord > > result;
+		result = new HashMap< Integer , List< PlanetListResourceRecord > >( );
+
+		List< FullPlanetListRecord > queryOutput;
+		queryOutput = this.dTemplate.query( Q_PLIST_RESOURCES , this.mPlanetListRecord , empire );
+
+		for ( FullPlanetListRecord fullRecord : queryOutput ) {
+			List< PlanetListResourceRecord > records = result.get( fullRecord.getPlanetId( ) );
+			if ( records == null ) {
+				records = new LinkedList< PlanetListResourceRecord >( );
+				result.put( fullRecord.getPlanetId( ) , records );
+			}
+			records.add( fullRecord.getRecord( ) );
+		}
+
+		return result;
+	}
+
 }
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
index 2d769f3..7a4c6fe 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
@@ -1,8 +1,10 @@
 package com.deepclone.lw.beans.empire;
 
 
+import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -16,6 +18,7 @@ import com.deepclone.lw.cmd.player.elist.EnemyListResponse;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.PlanetListData;
+import com.deepclone.lw.cmd.player.gdata.PlanetListResourceRecord;
 import com.deepclone.lw.cmd.player.gdata.battles.BattleListEntry;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
 import com.deepclone.lw.cmd.player.gdata.empire.ResearchLineData;
@@ -177,9 +180,19 @@ public class EmpireManagementBean
 	@Override
 	public ListPlanetsResponse getPlanetList( int empireId )
 	{
-		GamePageData page = this.getGeneralInformation( empireId );
 		List< PlanetListData > planets = this.empireDao.getPlanetList( empireId );
-		return new ListPlanetsResponse( page , planets );
+
+		Map< Integer , List< PlanetListResourceRecord >> resources;
+		resources = this.resourcesInformationDao.getPlanetListData( empireId );
+		for ( PlanetListData planet : planets ) {
+			List< PlanetListResourceRecord > planetResources = resources.get( planet.getId( ) );
+			if ( planetResources == null ) {
+				planetResources = new ArrayList< PlanetListResourceRecord >( );
+			}
+			planet.setResources( planetResources );
+		}
+
+		return new ListPlanetsResponse( this.getGeneralInformation( empireId ) , planets );
 	}
 
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/167-planet-list.sql b/legacyworlds-server-data/db-structure/parts/040-functions/167-planet-list.sql
index ca6ed36..e9ed6d1 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/167-planet-list.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/167-planet-list.sql
@@ -3,7 +3,7 @@
 --
 -- Views for empires' planet lists
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2012, DeepClone Development
 -- --------------------------------------------------------
 
 
@@ -114,6 +114,58 @@ CREATE VIEW emp.planets_list_fleets
 			END );
 
 
+/*
+ * Planet list resources information
+ * ----------------------------------
+ * 
+ * This view is used to display the resources-related information in the
+ * planet list pages. All rows in the view are ordered using the usual
+ * resource ordering view.
+ * 
+ * FIXME: time-related constants are hardcoded.
+ * FIXME: civilian and military investments are set to 0.
+ * 
+ * Columns:
+ *		empire_id		The empire's identifier
+ *		planet_id		The planet's identifier
+ *		pres_income		The income for this type of resources on a period
+ *							of 12h RT / one month GT.
+ *		pres_upkeep		The upkeep for this type of resources on a period
+ *							of 12h RT / one month GT.
+ *		civ_investment	The current amount invested in the civillian build
+ *							queue (FIXME: forced to 0)
+ *		mil_investment	The current amount invested in the military build
+ *							queue (FIXME: forced to 0)
+ */
+DROP VIEW IF EXISTS emp.plist_resources_view;
+CREATE VIEW emp.plist_resources_view
+	AS SELECT _emp_planet.empire_id , _emp_planet.planet_id ,
+				_name.translated_string AS resource_name ,
+				FLOOR( _pres.pres_income * 720.0 )::BIGINT AS pres_income ,
+				CEIL( _pres.pres_upkeep * 720.0 )::BIGINT AS pres_upkeep ,
+				0::BIGINT AS civ_investment ,
+				0::BIGINT AS mil_investment
+			FROM emp.planets _emp_planet
+				INNER JOIN verse.planet_resources _pres
+					USING ( planet_id )
+				INNER JOIN naming.empire_names _emp_name
+					ON _emp_name.id = _emp_planet.empire_id
+				INNER JOIN users.credentials _creds
+					ON _creds.address_id = _emp_name.owner_id
+				INNER JOIN defs.translations _name
+					ON _name.string_id = resource_name_id
+						AND _name.lang_id = _creds.language_id
+				INNER JOIN defs.ordered_resources_view _res_def
+					USING ( resource_name_id )
+			WHERE _pres.pres_income > 0
+				OR _pres.pres_upkeep > 0
+			ORDER BY _res_def.resource_ordering;
+
+GRANT SELECT
+	ON emp.plist_resources_view
+	TO :dbuser;
+
+
 --
 -- Actual planet list
 --
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/167-planet-list/010-plist-resources-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/167-planet-list/010-plist-resources-view.sql
new file mode 100644
index 0000000..86c18bf
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/167-planet-list/010-plist-resources-view.sql
@@ -0,0 +1,78 @@
+/*
+ * Tests for emp.plist_resources_view
+ */
+BEGIN;
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+
+	/* Create a couple of resources, three planets and two empire names */
+	SELECT _create_resources( 2 , 'resource' );
+	SELECT _create_raw_planets( 3 , 'planet' );
+	SELECT _create_emp_names( 2 , 'empire' );
+
+	/* One of the empires possesses two planets */
+	SELECT emp.create_empire( _get_emp_name( 'empire1' ) ,
+				_get_map_name( 'planet1' ) ,
+				200.0 );
+	INSERT INTO emp.planets( empire_id , planet_id )
+		VALUES ( _get_emp_name( 'empire1' ) , _get_map_name( 'planet2' ) );
+
+	/* First planet has income for one resource and upkeep for the other */
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id , pres_income )
+		VALUES ( _get_map_name( 'planet1' ) , _get_string( 'resource1' ) , 0.23 );
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id , pres_upkeep )
+		VALUES ( _get_map_name( 'planet1' ) , _get_string( 'resource2' ) , 0.22 );
+
+	/* Second planet has no income or upkeep */
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+		SELECT _get_map_name( 'planet2' ) , resource_name_id
+			FROM defs.resources;
+
+	/* The second empire has no planets */
+	INSERT INTO emp.empires( name_id , cash )
+		VALUES ( _get_emp_name( 'empire2' ) , 123 );
+
+	/* Third planet has income and upkeep for all resources */
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id , pres_income , pres_upkeep )
+		SELECT _get_map_name( 'planet3' ) , resource_name_id ,
+				4 + row_number( ) OVER( ) * 2 , row_number( ) OVER( ) * 2 + 5
+			FROM defs.resources;
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'emp.plist_resources_view - No rows for neutral planets' );
+	SELECT is_empty( $$
+		SELECT * FROM emp.plist_resources_view
+			WHERE planet_id = _get_map_name( 'planet3' );
+	$$ );
+
+	SELECT diag_test_name( 'emp.plist_resources_view - No rows for empires with no planets' );
+	SELECT is_empty( $$
+		SELECT * FROM emp.plist_resources_view
+			WHERE empire_id = _get_emp_name( 'empire2' );
+	$$ );
+
+	SELECT diag_test_name( 'emp.plist_resources_view - No rows for owned planets with zero income and upkeep' );
+	SELECT is_empty( $$
+		SELECT * FROM emp.plist_resources_view
+			WHERE planet_id = _get_map_name( 'planet2' );
+	$$ );
+
+	SELECT diag_test_name( 'emp.plist_resources_view - Rows for owned planets with income and upkeep' );
+	SELECT set_eq( $$
+		SELECT empire_id , resource_name , pres_income , pres_upkeep ,
+				civ_investment , mil_investment
+			FROM emp.plist_resources_view
+			WHERE planet_id = _get_map_name( 'planet1' );
+	$$ , $$ VALUES(
+			_get_emp_name( 'empire1' ) , 'Test string #1' , 165 , 0 , 0 , 0
+		) , (
+			_get_emp_name( 'empire1' ) , 'Test string #2' , 0 , 159 , 0 , 0
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/167-planet-list/010-plist-resources-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/167-planet-list/010-plist-resources-view.sql
new file mode 100644
index 0000000..ddd0cb9
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/167-planet-list/010-plist-resources-view.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on emp.plist_resources_view
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.plist_resources_view - SELECT privilege' );
+	SELECT lives_ok( 'SELECT * FROM emp.plist_resources_view' );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesInformationDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesInformationDAO.java
index 5c88115..44acfd1 100644
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesInformationDAO.java
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/resources/ResourcesInformationDAO.java
@@ -2,7 +2,9 @@ package com.deepclone.lw.interfaces.game.resources;
 
 
 import java.util.List;
+import java.util.Map;
 
+import com.deepclone.lw.cmd.player.gdata.PlanetListResourceRecord;
 import com.deepclone.lw.cmd.player.gdata.empire.EmpireResourceRecord;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetResourceRecord;
 
@@ -40,4 +42,16 @@ public interface ResourcesInformationDAO
 	 * @return the list of empire economic records
 	 */
 	public List< EmpireResourceRecord > getEmpireInformation( int empire );
+
+
+	/**
+	 * Obtain resources information for an empire's planet list
+	 * 
+	 * @param empire
+	 *            the empire whose planet list is being generated
+	 * 
+	 * @return a map of planet list resource records associating planet identifiers to lists of
+	 *         resource records
+	 */
+	public Map< Integer , List< PlanetListResourceRecord > > getPlanetListData( int empire );
 }
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestPlanetListResourceRecordMapper.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestPlanetListResourceRecordMapper.java
new file mode 100644
index 0000000..3839e80
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/game/resources/TestPlanetListResourceRecordMapper.java
@@ -0,0 +1,91 @@
+package com.deepclone.lw.beans.game.resources;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.cmd.player.gdata.PlanetListResourceRecord;
+import com.deepclone.lw.testing.MockResultSet;
+
+
+
+/**
+ * Tests for the {@link PlanetListResourceRecordMapper}
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestPlanetListResourceRecordMapper
+{
+
+	/** Planet identifier value */
+	private static final int TEST_PLANET_ID = 1;
+
+	/** Income value */
+	private static final long TEST_INCOME = 2L;
+
+	/** Upkeep value */
+	private static final long TEST_UPKEEP = 3L;
+
+	/** Civilian investment value */
+	private static final long TEST_CIV_INVESTMENT = 4L;
+
+	/** Military investment value */
+	private static final long TEST_MIL_INVESTMENT = 5L;
+
+	/** Fake result set used as the data source */
+	private ResultSet data;
+
+	/** Row mapper to test */
+	private PlanetListResourceRecordMapper mapper;
+
+
+	/**
+	 * Initialise the row mapper as well as the fake result set
+	 */
+	@Before
+	@SuppressWarnings( "unchecked" )
+	public void setUp( )
+	{
+		HashMap< String , Object > row = new HashMap< String , Object >( );
+		row.put( "planet_id" , TEST_PLANET_ID );
+		row.put( "pres_income" , TEST_INCOME );
+		row.put( "pres_upkeep" , TEST_UPKEEP );
+		row.put( "civ_investment" , TEST_CIV_INVESTMENT );
+		row.put( "mil_investment" , TEST_MIL_INVESTMENT );
+
+		this.data = MockResultSet.create( new HashMap[] {
+			row
+		} );
+
+		this.mapper = new PlanetListResourceRecordMapper( );
+	}
+
+
+	/**
+	 * Try mapping a row
+	 */
+	@Test
+	public void testMapRow( )
+			throws SQLException
+	{
+		this.data.absolute( 1 );
+
+		FullPlanetListRecord fullRecord = this.mapper.mapRow( this.data , 1 );
+		assertEquals( TEST_PLANET_ID , fullRecord.getPlanetId( ) );
+
+		PlanetListResourceRecord record = fullRecord.getRecord( );
+		assertNotNull( record );
+		assertEquals( TEST_INCOME , record.getIncome( ) );
+		assertEquals( TEST_UPKEEP , record.getUpkeep( ) );
+		assertEquals( TEST_CIV_INVESTMENT , record.getCivInvestment( ) );
+		assertEquals( TEST_MIL_INVESTMENT , record.getMilInvestment( ) );
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListData.java
index 27c4571..7814d38 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListData.java
@@ -2,331 +2,663 @@ package com.deepclone.lw.cmd.player.gdata;
 
 
 import java.io.Serializable;
+import java.util.List;
 
 
 
+/**
+ * An entry in the planet list
+ * 
+ * <p>
+ * This class represents an entry in the planet list. It contains all available information about a
+ * single planet.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
 public class PlanetListData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	/**
+	 * Serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M1 with ID 1
+	 * <li>Modified in B6M2, ID set to 2
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 2L;
 
+	/** Identifier of the planet */
 	private int id;
+	/** Name of the planet */
 	private String name;
 
+	/** Abscissa of the planet's system */
 	private int x;
+	/** Ordinates of the planet's system */
 	private int y;
+	/** Orbit of the planet in its system */
 	private int orbit;
 
+	/** Current population */
 	private long population;
+	/** Happiness percentage */
 	private int happiness;
 
+	/** Resources information (income, upkeep, etc...) */
+	private List< PlanetListResourceRecord > resources;
+
+	/** Monetary income of the planet */
 	private long income;
+	/** Monetary upkeep of the planet */
 	private long upkeep;
 
+	/** Current military production */
 	private long militaryProduction;
+	/** Current industrial production */
 	private long industrialProduction;
+	/** Current "extra population growth" production */
 	private long growthProduction;
 
+	/** Money invested in the civilian queue */
 	private long civInvestment;
+	/** Amount of buildings in the civilian queue */
 	private int civAmount;
+	/** Whether the current operation on the civilian queue is a destruction */
 	private boolean civDestroy;
+	/** Name of the buildings being destroyed or constructed */
 	private String civName;
 
+	/** Money invested in the military build queue */
 	private long milInvestment;
+	/** Amount of ships in the civilian queue */
 	private int milAmount;
+	/** Name of the ships being constructed */
 	private String milName;
 
+	/** Fleet power from turrets */
 	private long fpStatic;
+	/** Power of the owner's fleets */
 	private long fpOwn;
+	/** Power of the friendly fleets */
 	private long fpFriendly;
+	/** Power of the hostile fleets */
 	private long fpHostile;
 
+	/** Battle identifier or <code>null</code> if there is no battle */
 	private Long battle;
 
 
+	/**
+	 * Gets the identifier of the planet.
+	 * 
+	 * @return the identifier of the planet
+	 */
 	public int getId( )
 	{
-		return id;
+		return this.id;
 	}
 
 
+	/**
+	 * Sets the identifier of the planet.
+	 * 
+	 * @param id
+	 *            the new identifier of the planet
+	 */
 	public void setId( int id )
 	{
 		this.id = id;
 	}
 
 
+	/**
+	 * Gets the name of the planet.
+	 * 
+	 * @return the name of the planet
+	 */
 	public String getName( )
 	{
-		return name;
+		return this.name;
 	}
 
 
+	/**
+	 * Sets the name of the planet.
+	 * 
+	 * @param name
+	 *            the new name of the planet
+	 */
 	public void setName( String name )
 	{
 		this.name = name;
 	}
 
 
+	/**
+	 * Gets the abscissa of the planet's system.
+	 * 
+	 * @return the abscissa of the planet's system
+	 */
 	public int getX( )
 	{
-		return x;
+		return this.x;
 	}
 
 
+	/**
+	 * Sets the abscissa of the planet's system.
+	 * 
+	 * @param x
+	 *            the new abscissa of the planet's system
+	 */
 	public void setX( int x )
 	{
 		this.x = x;
 	}
 
 
+	/**
+	 * Gets the ordinates of the planet's system.
+	 * 
+	 * @return the ordinates of the planet's system
+	 */
 	public int getY( )
 	{
-		return y;
+		return this.y;
 	}
 
 
+	/**
+	 * Sets the ordinates of the planet's system.
+	 * 
+	 * @param y
+	 *            the new ordinates of the planet's system
+	 */
 	public void setY( int y )
 	{
 		this.y = y;
 	}
 
 
+	/**
+	 * Gets the orbit of the planet in its system.
+	 * 
+	 * @return the orbit of the planet in its system
+	 */
 	public int getOrbit( )
 	{
-		return orbit;
+		return this.orbit;
 	}
 
 
+	/**
+	 * Sets the orbit of the planet in its system.
+	 * 
+	 * @param orbit
+	 *            the new orbit of the planet in its system
+	 */
 	public void setOrbit( int orbit )
 	{
 		this.orbit = orbit;
 	}
 
 
+	/**
+	 * Gets the current population.
+	 * 
+	 * @return the current population
+	 */
 	public long getPopulation( )
 	{
-		return population;
+		return this.population;
 	}
 
 
+	/**
+	 * Sets the current population.
+	 * 
+	 * @param population
+	 *            the new current population
+	 */
 	public void setPopulation( long population )
 	{
 		this.population = population;
 	}
 
 
+	/**
+	 * Gets the happiness percentage.
+	 * 
+	 * @return the happiness percentage
+	 */
 	public int getHappiness( )
 	{
-		return happiness;
+		return this.happiness;
 	}
 
 
+	/**
+	 * Sets the happiness percentage.
+	 * 
+	 * @param happiness
+	 *            the new happiness percentage
+	 */
 	public void setHappiness( int happiness )
 	{
 		this.happiness = happiness;
 	}
 
 
-	public long getIncome( )
+	/**
+	 * Gets the resources information records
+	 * 
+	 * @return the resources information records
+	 */
+	public List< PlanetListResourceRecord > getResources( )
 	{
-		return income;
+		return this.resources;
 	}
 
 
+	/**
+	 * Sets the resources information records
+	 * 
+	 * @param resources
+	 *            the new resources information records
+	 */
+	public void setResources( List< PlanetListResourceRecord > resources )
+	{
+		this.resources = resources;
+	}
+
+
+	/**
+	 * Gets the monetary income of the planet.
+	 * 
+	 * @return the monetary income of the planet
+	 */
+	public long getIncome( )
+	{
+		return this.income;
+	}
+
+
+	/**
+	 * Sets the monetary income of the planet.
+	 * 
+	 * @param income
+	 *            the new monetary income of the planet
+	 */
 	public void setIncome( long income )
 	{
 		this.income = income;
 	}
 
 
+	/**
+	 * Gets the monetary upkeep of the planet.
+	 * 
+	 * @return the monetary upkeep of the planet
+	 */
 	public long getUpkeep( )
 	{
-		return upkeep;
+		return this.upkeep;
 	}
 
 
+	/**
+	 * Sets the monetary upkeep of the planet.
+	 * 
+	 * @param upkeep
+	 *            the new monetary upkeep of the planet
+	 */
 	public void setUpkeep( long upkeep )
 	{
 		this.upkeep = upkeep;
 	}
 
 
+	/**
+	 * Gets the current military production.
+	 * 
+	 * @return the current military production
+	 */
 	public long getMilitaryProduction( )
 	{
-		return militaryProduction;
+		return this.militaryProduction;
 	}
 
 
+	/**
+	 * Sets the current military production.
+	 * 
+	 * @param militaryProduction
+	 *            the new current military production
+	 */
 	public void setMilitaryProduction( long militaryProduction )
 	{
 		this.militaryProduction = militaryProduction;
 	}
 
 
+	/**
+	 * Gets the current industrial production.
+	 * 
+	 * @return the current industrial production
+	 */
 	public long getIndustrialProduction( )
 	{
-		return industrialProduction;
+		return this.industrialProduction;
 	}
 
 
+	/**
+	 * Sets the current industrial production.
+	 * 
+	 * @param industrialProduction
+	 *            the new current industrial production
+	 */
 	public void setIndustrialProduction( long industrialProduction )
 	{
 		this.industrialProduction = industrialProduction;
 	}
 
 
+	/**
+	 * Gets the current "extra population growth" production.
+	 * 
+	 * @return the current "extra population growth" production
+	 */
 	public long getGrowthProduction( )
 	{
-		return growthProduction;
+		return this.growthProduction;
 	}
 
 
+	/**
+	 * Sets the current "extra population growth" production.
+	 * 
+	 * @param growthProduction
+	 *            the new current "extra population growth" production
+	 */
 	public void setGrowthProduction( long growthProduction )
 	{
 		this.growthProduction = growthProduction;
 	}
 
 
+	/**
+	 * Gets the money invested in the civilian queue.
+	 * 
+	 * @return the money invested in the civilian queue
+	 */
 	public long getCivInvestment( )
 	{
-		return civInvestment;
+		return this.civInvestment;
 	}
 
 
+	/**
+	 * Sets the money invested in the civilian queue.
+	 * 
+	 * @param civInvestment
+	 *            the new money invested in the civilian queue
+	 */
 	public void setCivInvestment( long civInvestment )
 	{
 		this.civInvestment = civInvestment;
 	}
 
 
+	/**
+	 * Gets the amount of buildings in the civilian queue.
+	 * 
+	 * @return the amount of buildings in the civilian queue
+	 */
 	public int getCivAmount( )
 	{
-		return civAmount;
+		return this.civAmount;
 	}
 
 
+	/**
+	 * Sets the amount of buildings in the civilian queue.
+	 * 
+	 * @param civAmount
+	 *            the new amount of buildings in the civilian queue
+	 */
 	public void setCivAmount( int civAmount )
 	{
 		this.civAmount = civAmount;
 	}
 
 
+	/**
+	 * Checks whether the current operation on the civilian queue is a destruction.
+	 * 
+	 * @return <code>true</code> if the current operation on the civilian queue is a destruction
+	 */
 	public boolean isCivDestroy( )
 	{
-		return civDestroy;
+		return this.civDestroy;
 	}
 
 
+	/**
+	 * Sets whether the current operation on the civilian queue is a destruction.
+	 * 
+	 * @param civDestroy
+	 *            <code>true</code> if the current operation on the civilian queue is a destruction
+	 */
 	public void setCivDestroy( boolean civDestroy )
 	{
 		this.civDestroy = civDestroy;
 	}
 
 
+	/**
+	 * Gets the name of the buildings being destroyed or constructed.
+	 * 
+	 * @return the name of the buildings being destroyed or constructed
+	 */
 	public String getCivName( )
 	{
-		return civName;
+		return this.civName;
 	}
 
 
+	/**
+	 * Sets the name of the buildings being destroyed or constructed.
+	 * 
+	 * @param civName
+	 *            the new name of the buildings being destroyed or constructed
+	 */
 	public void setCivName( String civName )
 	{
 		this.civName = civName;
 	}
 
 
+	/**
+	 * Gets the money invested in the military build queue.
+	 * 
+	 * @return the money invested in the military build queue
+	 */
 	public long getMilInvestment( )
 	{
-		return milInvestment;
+		return this.milInvestment;
 	}
 
 
+	/**
+	 * Sets the money invested in the military build queue.
+	 * 
+	 * @param milInvestment
+	 *            the new money invested in the military build queue
+	 */
 	public void setMilInvestment( long milInvestment )
 	{
 		this.milInvestment = milInvestment;
 	}
 
 
+	/**
+	 * Gets the amount of ships in the civilian queue.
+	 * 
+	 * @return the amount of ships in the civilian queue
+	 */
 	public int getMilAmount( )
 	{
-		return milAmount;
+		return this.milAmount;
 	}
 
 
+	/**
+	 * Sets the amount of ships in the civilian queue.
+	 * 
+	 * @param milAmount
+	 *            the new amount of ships in the civilian queue
+	 */
 	public void setMilAmount( int milAmount )
 	{
 		this.milAmount = milAmount;
 	}
 
 
+	/**
+	 * Gets the name of the ships being constructed.
+	 * 
+	 * @return the name of the ships being constructed
+	 */
 	public String getMilName( )
 	{
-		return milName;
+		return this.milName;
 	}
 
 
+	/**
+	 * Sets the name of the ships being constructed.
+	 * 
+	 * @param milName
+	 *            the new name of the ships being constructed
+	 */
 	public void setMilName( String milName )
 	{
 		this.milName = milName;
 	}
 
 
+	/**
+	 * Gets the fleet power from turrets.
+	 * 
+	 * @return the fleet power from turrets
+	 */
 	public long getFpStatic( )
 	{
-		return fpStatic;
+		return this.fpStatic;
 	}
 
 
+	/**
+	 * Sets the fleet power from turrets.
+	 * 
+	 * @param fpStatic
+	 *            the new fleet power from turrets
+	 */
 	public void setFpStatic( long fpStatic )
 	{
 		this.fpStatic = fpStatic;
 	}
 
 
+	/**
+	 * Gets the power of the owner's fleets.
+	 * 
+	 * @return the power of the owner's fleets
+	 */
 	public long getFpOwn( )
 	{
-		return fpOwn;
+		return this.fpOwn;
 	}
 
 
+	/**
+	 * Sets the power of the owner's fleets.
+	 * 
+	 * @param fpOwn
+	 *            the new power of the owner's fleets
+	 */
 	public void setFpOwn( long fpOwn )
 	{
 		this.fpOwn = fpOwn;
 	}
 
 
+	/**
+	 * Gets the power of the friendly fleets.
+	 * 
+	 * @return the power of the friendly fleets
+	 */
 	public long getFpFriendly( )
 	{
-		return fpFriendly;
+		return this.fpFriendly;
 	}
 
 
+	/**
+	 * Sets the power of the friendly fleets.
+	 * 
+	 * @param fpFriendly
+	 *            the new power of the friendly fleets
+	 */
 	public void setFpFriendly( long fpFriendly )
 	{
 		this.fpFriendly = fpFriendly;
 	}
 
 
+	/**
+	 * Gets the power of the hostile fleets.
+	 * 
+	 * @return the power of the hostile fleets
+	 */
 	public long getFpHostile( )
 	{
-		return fpHostile;
+		return this.fpHostile;
 	}
 
 
+	/**
+	 * Sets the power of the hostile fleets.
+	 * 
+	 * @param fpHostile
+	 *            the new power of the hostile fleets
+	 */
 	public void setFpHostile( long fpHostile )
 	{
 		this.fpHostile = fpHostile;
 	}
 
 
+	/**
+	 * Gets the battle identifier or <code>null</code> if there is no battle.
+	 * 
+	 * @return the battle identifier or <code>null</code> if there is no battle
+	 */
 	public Long getBattle( )
 	{
-		return battle;
+		return this.battle;
 	}
 
 
+	/**
+	 * Sets the battle identifier.
+	 * 
+	 * @param battle
+	 *            the new battle identifier or <code>null</code> if there is no battle
+	 */
 	public void setBattle( Long battle )
 	{
 		this.battle = battle;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListResourceRecord.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListResourceRecord.java
new file mode 100644
index 0000000..d1c16ac
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListResourceRecord.java
@@ -0,0 +1,161 @@
+package com.deepclone.lw.cmd.player.gdata;
+
+
+import java.io.Serializable;
+
+
+
+/**
+ * A resource record for the planet list
+ * 
+ * <p>
+ * This class is used by {@link PlanetListData} to carry information about resources for the planet
+ * list page.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class PlanetListResourceRecord
+		implements Serializable
+
+{
+
+	/**
+	 * Serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** The name of the resource */
+	private String name;
+
+	/** The income for that resource type */
+	private long income;
+
+	/** The upkeep for that resource type */
+	private long upkeep;
+
+	/** The amount of that resource invested into the civilian construction queue */
+	private long civInvestment;
+
+	/** The amount of that resource invested into the military construction queue */
+	private long milInvestment;
+
+
+	/**
+	 * Gets the name of the resource.
+	 * 
+	 * @return the name of the resource
+	 */
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	/**
+	 * Sets the name of the resource.
+	 * 
+	 * @param name
+	 *            the new name of the resource
+	 */
+	public void setName( String name )
+	{
+		this.name = name;
+	}
+
+
+	/**
+	 * Gets the income for that resource type.
+	 * 
+	 * @return the income for that resource type
+	 */
+	public long getIncome( )
+	{
+		return this.income;
+	}
+
+
+	/**
+	 * Sets the income for that resource type.
+	 * 
+	 * @param income
+	 *            the new income for that resource type
+	 */
+	public void setIncome( long income )
+	{
+		this.income = income;
+	}
+
+
+	/**
+	 * Gets the upkeep for that resource type.
+	 * 
+	 * @return the upkeep for that resource type
+	 */
+	public long getUpkeep( )
+	{
+		return this.upkeep;
+	}
+
+
+	/**
+	 * Sets the upkeep for that resource type.
+	 * 
+	 * @param upkeep
+	 *            the new upkeep for that resource type
+	 */
+	public void setUpkeep( long upkeep )
+	{
+		this.upkeep = upkeep;
+	}
+
+
+	/**
+	 * Gets the amount of that resource invested into the civilian construction queue.
+	 * 
+	 * @return the amount of that resource invested into the civilian construction queue
+	 */
+	public long getCivInvestment( )
+	{
+		return this.civInvestment;
+	}
+
+
+	/**
+	 * Sets the amount of that resource invested into the civilian construction queue.
+	 * 
+	 * @param civInvestment
+	 *            the new amount of that resource invested into the civilian construction queue
+	 */
+	public void setCivInvestment( long civInvestment )
+	{
+		this.civInvestment = civInvestment;
+	}
+
+
+	/**
+	 * Gets the amount of that resource invested into the military construction queue.
+	 * 
+	 * @return the amount of that resource invested into the military construction queue
+	 */
+	public long getMilInvestment( )
+	{
+		return this.milInvestment;
+	}
+
+
+	/**
+	 * Sets the amount of that resource invested into the military construction queue.
+	 * 
+	 * @param milInvestment
+	 *            the new amount of that resource invested into the military construction queue
+	 */
+	public void setMilInvestment( long milInvestment )
+	{
+		this.milInvestment = milInvestment;
+	}
+
+}
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets.ftl
index 0321889..c8bc9cb 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets.ftl
@@ -28,7 +28,7 @@
 				</@listview>
 			</@tab>
 
-			<@tab id="eco" title="Economy"> 
+			<@tab id="eco" title="Economy (OLD)"> 
 				<@listview>
 					<@lv_line headers=true>
 						<@lv_column width="x">Name</@lv_column>
@@ -56,6 +56,9 @@
 				</@listview>
 			</@tab>
 
+			<#include "planets/economy.ftl" />
+			<@RenderEconomy />
+
 			<@tab id="prod" title="Production"> 
 				<@listview>
 					<@lv_line headers=true>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets/economy.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets/economy.ftl
new file mode 100644
index 0000000..58f20ee
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/planets/economy.ftl
@@ -0,0 +1,56 @@
+<#macro RenderEconomy>			
+	<@tab id="economy" title="Economy"> 
+		<@listview>
+			<@lv_line headers=true>
+				<@lv_column width="x">Name</@lv_column>
+				<@lv_column width="x">Resource</@lv_column>
+				<@lv_column width=110 right=true>Income</@lv_column>
+				<@lv_column width=110 right=true>Upkeep</@lv_column>
+				<@lv_column width=110 right=true>Profit</@lv_column>
+			</@lv_line>
+
+			<#list pl as planet>
+				<#if planet.resources?size = 0>
+					<@lv_line>
+						<@lv_column><a href="planet-${planet.id}#resources">${planet.name?xhtml}</a></@lv_column>
+						<@lv_column colspan=4 centered=true><em>No income or upkeep</em></@lv_column>
+					</@lv_line>
+				<#else>
+					<#list planet.resources as resource>
+						<@lv_line>
+							<@lv_column>
+								<#if resource_index = 0>
+									<a href="planet-${planet.id}#resources">${planet.name?xhtml}</a>
+								<#else>
+									&nbsp;
+								</#if>
+							</@lv_column>
+							<@lv_column>${resource.name?xhtml}</@lv_column>
+							<@lv_column right=true>${resource.income?string(",##0")}</@lv_column>
+							<@lv_column right=true>${resource.upkeep?string(",##0")}</@lv_column>
+							<@lv_column right=true>
+								<#if resource.upkeep gt resource.income>
+									<span style="color: red">
+								</#if>
+								${( resource.income - resource.upkeep )?string(",##0")}
+								<#if resource.upkeep gt resource.income>
+									</span>
+								</#if>
+							</@lv_column>
+						</@lv_line>
+					</#list>
+				</#if>
+			</#list>
+
+			<@lv_line headers=true>
+				<@lv_column width="x" colspan=6>&nbsp;</@lv_column>
+			</@lv_line>
+			<@lv_line>
+				<@lv_column centered=true colspan=6>
+					Incomes, upkeeps and profits are displayed for a period of one game month (12 actual hours).
+				</@lv_column>
+			</@lv_line>
+
+		</@listview>
+	</@tab>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets.ftl
index e8fb997..51760fb 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets.ftl
@@ -28,7 +28,7 @@
 				</@listview>
 			</@tab>
 
-			<@tab id="eco" title="Économie"> 
+			<@tab id="eco" title="Économie (VIEUX)"> 
 				<@listview>
 					<@lv_line headers=true>
 						<@lv_column width="x">Nom</@lv_column>
@@ -56,6 +56,9 @@
 				</@listview>
 			</@tab>
 
+			<#include "planets/economy.ftl" />
+			<@RenderEconomy />
+
 			<@tab id="prod" title="Production"> 
 				<@listview>
 					<@lv_line headers=true>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets/economy.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets/economy.ftl
new file mode 100644
index 0000000..695a8b6
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/planets/economy.ftl
@@ -0,0 +1,56 @@
+<#macro RenderEconomy>			
+	<@tab id="economy" title="Economie">
+		<@listview>
+			<@lv_line headers=true>
+				<@lv_column width="x">Name</@lv_column>
+				<@lv_column width="x">Resource</@lv_column>
+				<@lv_column width=110 right=true>Revenus</@lv_column>
+				<@lv_column width=110 right=true>Charges</@lv_column>
+				<@lv_column width=110 right=true>Bénéfices</@lv_column>
+			</@lv_line>
+
+			<#list pl as planet>
+				<#if planet.resources?size = 0>
+					<@lv_line>
+						<@lv_column><a href="planet-${planet.id}#resources">${planet.name?xhtml}</a></@lv_column>
+						<@lv_column colspan=4 centered=true><em>Aucune entrée pour cette planète</em></@lv_column>
+					</@lv_line>
+				<#else>
+					<#list planet.resources as resource>
+						<@lv_line>
+							<@lv_column>
+								<#if resource_index = 0>
+									<a href="planet-${planet.id}#resources">${planet.name?xhtml}</a>
+								<#else>
+									&nbsp;
+								</#if>
+							</@lv_column>
+							<@lv_column>${resource.name?xhtml}</@lv_column>
+							<@lv_column right=true>${resource.income?string(",##0")}</@lv_column>
+							<@lv_column right=true>${resource.upkeep?string(",##0")}</@lv_column>
+							<@lv_column right=true>
+								<#if resource.upkeep gt resource.income>
+									<span style="color: red">
+								</#if>
+								${( resource.income - resource.upkeep )?string(",##0")}
+								<#if resource.upkeep gt resource.income>
+									</span>
+								</#if>
+							</@lv_column>
+						</@lv_line>
+					</#list>
+				</#if>
+			</#list>
+
+			<@lv_line headers=true>
+				<@lv_column width="x" colspan=6>&nbsp;</@lv_column>
+			</@lv_line>
+			<@lv_line>
+				<@lv_column centered=true colspan=6>
+					Les revenus, charges et bénéfices sont indiqués pour une durée d'un mois en temps de jeu, soit 12 heures réelles.
+				</@lv_column>
+			</@lv_line>
+
+		</@listview>
+	</@tab>
+</#macro>
\ No newline at end of file

From cb65a6e6435883503c943c38e8b17b59e71aa946 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 16 Feb 2012 10:22:30 +0100
Subject: [PATCH 53/94] Empire resources and vacation mode

* Modified the empire resources update to check for vacation mode and
apply the modifier from vacation.cashDivider if necessary.

* Some parts of the database need to be reloaded:
  -> 050-updates/015-empire-resources.sql
---
 .../050-updates/015-empire-resources.sql      | 46 +++++++++-----
 .../010-process-empire-resources-updates.sql  | 61 ++++++++++++++++---
 .../010-process-empire-resources-updates.sql  |  2 +-
 3 files changed, 83 insertions(+), 26 deletions(-)

diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql b/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
index 6004945..64d1448 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
@@ -29,30 +29,46 @@ AS $process_empire_resources_updates$
 	UPDATE emp.resources _emp_resources
 
 		SET empres_possessed = CASE
-				WHEN _emp_resources.empres_possessed + _pl_resources.pres_income - _pl_resources.pres_upkeep > 0 THEN
-					_emp_resources.empres_possessed + _pl_resources.pres_income - _pl_resources.pres_upkeep
+				WHEN _emp_resources.empres_possessed + _raw.res_delta > 0 THEN
+					_emp_resources.empres_possessed + _raw.res_delta
 				ELSE
 					0
 				END ,
 			empres_owed = CASE
-				WHEN _emp_resources.empres_possessed + _pl_resources.pres_income - _pl_resources.pres_upkeep < 0 THEN
-					 _pl_resources.pres_upkeep - _emp_resources.empres_possessed - _pl_resources.pres_income 
+				WHEN _emp_resources.empres_possessed + _raw.res_delta < 0 THEN
+					 -( _emp_resources.empres_possessed + _raw.res_delta ) 
 				ELSE
 					0
 				END
 
-		FROM sys.updates _upd_sys
-			INNER JOIN emp.empires_updates _upd_emp
-				USING ( updtgt_id , updtype_id , update_id )
-			INNER JOIN emp.planets _emp_planets
-				ON empire_id = name_id
-			INNER JOIN verse.planet_resources _pl_resources
-				USING ( planet_id )
+		FROM (
+			SELECT _upd_emp.name_id AS empire_id ,  _pl_resources.resource_name_id ,
+					( _pl_resources.pres_income - _pl_resources.pres_upkeep ) / ( CASE
+						WHEN _vacation.account_id IS NULL THEN
+							1
+						ELSE
+							sys.get_constant( 'vacation.cashDivider' )
+					END ) AS res_delta
 
-		WHERE _upd_sys.update_last = $1
-			AND _upd_sys.update_state = 'PROCESSING'
-			AND _emp_resources.empire_id = _upd_emp.name_id
-			AND _emp_resources.resource_name_id = _pl_resources.resource_name_id;
+				FROM sys.updates _upd_sys
+					INNER JOIN emp.empires_updates _upd_emp
+						USING ( updtgt_id , updtype_id , update_id )
+					INNER JOIN emp.planets _emp_planets
+						ON empire_id = name_id
+					INNER JOIN verse.planet_resources _pl_resources
+						USING ( planet_id )
+					INNER JOIN naming.empire_names _emp_name
+						ON _emp_name.id = _upd_emp.name_id
+					LEFT OUTER JOIN users.vacations _vacation
+						ON _vacation.account_id = _emp_name.owner_id
+							AND _vacation.status = 'PROCESSED'
+
+				WHERE _upd_sys.update_last = $1
+					AND _upd_sys.update_state = 'PROCESSING'
+			) _raw
+
+		WHERE _emp_resources.empire_id = _raw.empire_id
+			AND _emp_resources.resource_name_id = _raw.resource_name_id;
 
 $process_empire_resources_updates$ LANGUAGE SQL;
 
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
index 5e670c7..969aa97 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
@@ -10,9 +10,9 @@ BEGIN;
 
 	/* We need:
 	 *  - three types of resources,
-	 *  - two empire with resources records, and the corresponding update
+	 *  - three empires with resources records, and the corresponding update
 	 *    record,
-	 *  - two planets, set to belong to the aforementioned empires, with
+	 *  - three planets, set to belong to the aforementioned empires, with
 	 *    resources records attached. 
 	 */
 	\i utils/strings.sql
@@ -21,13 +21,14 @@ BEGIN;
 	\i utils/naming.sql
 	\i utils/universe.sql
 	SELECT _create_resources( 3 , 'testResource' );
-	SELECT _create_raw_planets( 2 , 'testPlanet' );
-	SELECT _create_emp_names( 2 , 'testEmp' );
-	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet1' ) , 1 );
+	SELECT _create_raw_planets( 3 , 'testPlanet' );
+	SELECT _create_emp_names( 3 , 'testEmp' );
 	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
 		SELECT _get_map_name( 'testPlanet1' ) , resource_name_id FROM defs.resources;
 	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
 		SELECT _get_map_name( 'testPlanet2' ) , resource_name_id FROM defs.resources;
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+		SELECT _get_map_name( 'testPlanet3' ) , resource_name_id FROM defs.resources;
 
 	/* Set up the planets' resource records so that:
 	 *	- income > upkeep for testResource1 ,
@@ -39,20 +40,33 @@ BEGIN;
 	UPDATE verse.planet_resources SET pres_upkeep = 34
 		WHERE resource_name_id = _get_string( 'testResource2' );
 
+	/* Create testEmp1 empire */
+	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) , _get_map_name( 'testPlanet1' ) , 1 );
+
+	/* Create testEmp2 empire and add a vacation mode entry; also set the vacation.cashDivider constant */
+	SELECT emp.create_empire( _get_emp_name( 'testEmp2' ) , _get_map_name( 'testPlanet2' ) , 1 );
+	ALTER TABLE users.vacations
+		DROP CONSTRAINT fk_vacations_accounts;
+	INSERT INTO users.vacations ( account_id , since , status )
+		SELECT owner_id , now( ) , 'PROCESSED'
+			FROM naming.empire_names
+				WHERE id = _get_emp_name( 'testEmp2' );
+	SELECT sys.uoc_constant( 'vacation.cashDivider' , '(test)' , 'Resources' , 2 );
+
 	/* Make sure update 0 is set to be processed for testEmp1 */
 	UPDATE sys.updates
 		SET update_state = 'PROCESSING' , update_last = 0;
 
-	/* Create testEmp2 empire, make sure it will not be processed */
-	SELECT emp.create_empire( _get_emp_name( 'testEmp2' ) , _get_map_name( 'testPlanet2' ) , 1 );
+	/* Create testEmp3 empire, make sure it will not be processed */
+	SELECT emp.create_empire( _get_emp_name( 'testEmp3' ) , _get_map_name( 'testPlanet3' ) , 1 );
 	UPDATE sys.updates _su
 		SET update_state = 'PROCESSED' , update_last = 0
 		FROM emp.empires_updates _eu
 		WHERE _eu.update_id = _su.update_id
-			AND _eu.name_id = _get_emp_name( 'testEmp2' );
+			AND _eu.name_id = _get_emp_name( 'testEmp3' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 19 );
+	SELECT plan( 31 );
 
 	SELECT sys.process_empire_resources_updates( 10 );
 	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Wrong update identifier (1/6)' );
@@ -75,6 +89,7 @@ BEGIN;
 		WHERE resource_name_id = _get_string( 'testResource3' );
 
 	SELECT sys.process_empire_resources_updates( 0 );
+
 	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 1 - Possessed quantity increased' );
 	SELECT is( empres_possessed , 12::DOUBLE PRECISION ) FROM emp.resources
 		WHERE resource_name_id = _get_string( 'testResource1' )
@@ -99,10 +114,36 @@ BEGIN;
 	SELECT is( empres_owed , 0::DOUBLE PRECISION ) FROM emp.resources
 		WHERE resource_name_id = _get_string( 'testResource3' )
 			AND empire_id = _get_emp_name( 'testEmp1' );
+
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 1 / Vacation - Possessed quantity increased' );
+	SELECT is( empres_possessed , 6::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource1' )
+			AND empire_id = _get_emp_name( 'testEmp2' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 1 / Vacation - Owed quantity unchanged' );
+	SELECT is( empres_owed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource1' )
+			AND empire_id = _get_emp_name( 'testEmp2' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 2 / Vacation - Possessed quantity unchanged' );
+	SELECT is( empres_possessed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource2' )
+			AND empire_id = _get_emp_name( 'testEmp2' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 2 / Vacation - Owed quantity increased' );
+	SELECT is( empres_owed , 17::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource2' )
+			AND empire_id = _get_emp_name( 'testEmp2' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 3 / Vacation - Possessed quantity unchanged' );
+	SELECT is( empres_possessed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource3' )
+			AND empire_id = _get_emp_name( 'testEmp2' );
+	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resource 3 / Vacation - Owed quantity unchanged' );
+	SELECT is( empres_owed , 0::DOUBLE PRECISION ) FROM emp.resources
+		WHERE resource_name_id = _get_string( 'testResource3' )
+			AND empire_id = _get_emp_name( 'testEmp2' );
+
 	SELECT diag_test_name( 'EMPIRE_RESOURCES update - Resources unchanged for already processed empire' );
 	SELECT is( SUM( empres_owed + empres_possessed ) , 0::DOUBLE PRECISION )
 		FROM emp.resources
-		WHERE empire_id = _get_emp_name( 'testEmp2' );
+		WHERE empire_id = _get_emp_name( 'testEmp3' );
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/015-empire-resources/010-process-empire-resources-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
index 80c7ff8..eba8788 100644
--- a/legacyworlds-server-data/db-structure/tests/user/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
@@ -4,7 +4,7 @@
 BEGIN;
 	SELECT plan( 1 );
 
-	SELECT diag_test_name( 'sys.process_empire_resources_updates() - Privileges' );
+	SELECT diag_test_name( 'sys.process_empire_resources_updates() - No EXECUTE privilege' );
 	SELECT throws_ok( 'SELECT sys.process_empire_resources_updates( 1 )' , 42501 );
 	
 	SELECT * FROM finish( );

From b90491ca73c3bfbdc84a7945c3372dca6fe89939 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 16 Feb 2012 11:07:43 +0100
Subject: [PATCH 54/94] Technology definition tables

* Added a pair of tables that will contain graph-like technology
definitions. The first table describes a technology, while the second
table is used to list dependencies between technologies. The new tables
contain the basic checks and foreign keys. However, integrity checks on
the dependency graph are not implemented.

* The following SQL files need to be re-executed:
  -> 030-data/080-techs.sql
---
 .../db-structure/parts/030-data/080-techs.sql | 89 ++++++++++++++++++-
 1 file changed, 88 insertions(+), 1 deletion(-)

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
index c539ac2..a19931a 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
@@ -3,10 +3,97 @@
 --
 -- Technology definitions
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2012, DeepClone Development
 -- --------------------------------------------------------
 
 
+/*
+ * Technology definitions
+ * -----------------------
+ * 
+ * This table contains all defined technologies. It includes references to
+ * the various I18N strings used to describe the technology, as well as its
+ * cost in research points.
+ * 
+ * FIXME: for now it also includes the monetary cost, but that will go away
+ *		when the full resource system is completed.
+ */
+CREATE TABLE defs.technologies(
+	/* Name of the technology (I18N string) */
+	technology_name_id			INT NOT NULL PRIMARY KEY ,
+
+	/* Category of the technology (I18N string) */
+	technology_category_id		INT NOT NULL ,
+
+	/* Text to display when the technology is discovered (I18N string) */
+	technology_discovery_id		INT NOT NULL ,
+	
+	/* A more "factual" description of the technology (I18N string) */
+	technology_description_id	INT NOT NULL ,
+	
+	/* Monetary price of the technology - FIXME: will be removed later */
+	technology_price			INT NOT NULL
+									CHECK( technology_price > 0 ) ,
+
+	/* Cost of the technology in terms of research points */
+	technology_points			INT NOT NULL
+									CHECK( technology_points > 0 )
+);
+
+CREATE INDEX idx_techs_category
+	ON defs.technologies( technology_category_id );
+CREATE UNIQUE INDEX idx_techs_discovery
+	ON defs.technologies( technology_discovery_id );
+CREATE UNIQUE INDEX idx_techs_description
+	ON defs.technologies( technology_description_id );
+
+ALTER TABLE defs.technologies
+	ADD CONSTRAINT fk_techs_name
+		FOREIGN KEY ( technology_name_id ) REFERENCES defs.strings ,
+	ADD CONSTRAINT fk_techs_category
+		FOREIGN KEY ( technology_category_id ) REFERENCES defs.strings ,
+	ADD CONSTRAINT fk_techs_discovery
+		FOREIGN KEY ( technology_discovery_id ) REFERENCES defs.strings ,
+	ADD CONSTRAINT fk_techs_description
+		FOREIGN KEY ( technology_description_id ) REFERENCES defs.strings;
+
+
+
+/*
+ * Technology dependencies
+ * ------------------------
+ *
+ * This table list dependencies between technologies. It stores pairs of
+ * dependent / dependency references.
+ */
+CREATE TABLE defs.technology_dependencies(
+	/* Identifier of the dependency itself. Makes integrity checks easier */
+	techdep_id					SERIAL NOT NULL PRIMARY KEY ,
+	
+	/* Identifier of the dependent technology */
+	technology_name_id			INT NOT NULL ,
+
+	/* Identifier of the technology being depended on */
+	technology_name_id_depends	INT NOT NULL
+);
+
+CREATE UNIQUE INDEX idx_techdeps_techs
+	ON defs.technology_dependencies( technology_name_id , technology_name_id_depends );
+CREATE INDEX idx_techdeps_dependency
+	ON defs.technology_dependencies( technology_name_id_depends );
+
+ALTER TABLE defs.technology_dependencies
+	ADD CONSTRAINT fk_techdeps_dependent
+		FOREIGN KEY ( technology_name_id ) REFERENCES defs.technologies ,
+	ADD CONSTRAINT fk_techdeps_dependency
+		FOREIGN KEY ( technology_name_id_depends ) REFERENCES defs.technologies;
+
+
+
+/*
+ * Old B6M1 research system below.
+ */
+
 --
 -- Technology lines
 --

From 4f083830f237f67e7acc455f59b01f7303859d7f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 16 Feb 2012 18:30:58 +0100
Subject: [PATCH 55/94] Technology dependencies caching and integrity checks

* Added table that will contain the cached technology dependencies.

* Implemented trigger functions that update the cache and make sure
there are no cycles or redundancies in the technology graph.

* The following SQL files need to be (re-)executed:
  -> 030-data/080-techs.sql (for the defs.techdep_cache table)
  -> 040-functions/026-technology-dependencies.sql (new file)
---
 .../db-structure/parts/030-data/080-techs.sql |  73 ++++
 .../026-technology-dependencies.sql           | 314 ++++++++++++++++++
 .../010-technologies-cache-entries.sql        |  49 +++
 .../020-techdeps-cache-entries.sql            | 115 +++++++
 .../030-techdeps-cycles.sql                   |  49 +++
 .../040-techdeps-redundancy.sql               |  79 +++++
 .../010-tdcache-copy-tree.sql                 |  14 +
 .../020-tdcache-set-child.sql                 |  14 +
 .../030-tgf-technologies-ai.sql               |  14 +
 .../040-tgf-techdeps-bi.sql                   |  14 +
 .../050-tgf-techdeps-ai.sql                   |  14 +
 11 files changed, 749 insertions(+)
 create mode 100644 legacyworlds-server-data/db-structure/parts/040-functions/026-technology-dependencies.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/010-technologies-cache-entries.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/020-techdeps-cache-entries.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/030-techdeps-cycles.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/040-techdeps-redundancy.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/010-tdcache-copy-tree.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/020-tdcache-set-child.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/030-tgf-technologies-ai.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/040-tgf-techdeps-bi.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/050-tgf-techdeps-ai.sql

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
index a19931a..289c7b2 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
@@ -89,6 +89,79 @@ ALTER TABLE defs.technology_dependencies
 		FOREIGN KEY ( technology_name_id_depends ) REFERENCES defs.technologies;
 
 
+/*
+ * Technology dependencies graph cache
+ * ------------------------------------
+ *
+ * This table is a cache of all dependency relationships between technologies.
+ * It serves two purposes. First, it allows dependencies to be checked to
+ * maintain the graph's integrity. Second, it allows all dependencies or
+ * reverse dependencies of a technology to be looked up in one single SELECT
+ * statement.
+ *
+ * It works by maintaining, for each node of the graph, a set of trees which
+ * correspond to the dependencies or reverse dependencies. Each part of a
+ * tree that is not determined solely by the dependency itself is a copy of
+ * another tree. Because of that, multiple copies of the same tree may exist
+ * in the records for a single technology.
+ *
+ * Technologies which have no dependencies or reverse dependencies are also
+ * present in the table, as it makes things easier when dependencies are added.
+ */
+CREATE TABLE defs.techdep_cache(
+	/* Identifier of the technology represented by this node */
+	technology_name_id			INT NOT NULL,
+
+	/* Whether the node is part of a forward or reverse dependencies tree */
+	tdcache_reverse				BOOLEAN NOT NULL ,
+
+	/* Identifier of the cache entry itself */
+	tdcache_id					SERIAL NOT NULL ,
+
+	/* Identifier of the parent cache entry, if any */
+	tdcache_id_parent			INT ,
+
+	/* Depth of the dependency relationship */
+	tdcache_depth				INT NOT NULL ,
+
+	/* Identifier of the technology of the entry this entry is a copy of */
+	technology_name_id_copyof	INT NOT NULL ,
+
+	/* Identifier of the entry this entry is a copy of */
+	tdcache_id_copyof			INT ,
+
+	/* Identifier of the dependency because of which the entry exists, or
+	 * NULL if the entry exists because of a technology definition.
+	 */
+	techdep_id					INT ,
+
+	PRIMARY KEY( technology_name_id , tdcache_reverse , tdcache_id )
+);
+
+CREATE INDEX idx_tdcache_reversetechs
+	ON defs.techdep_cache ( tdcache_reverse , tdcache_id_parent );
+CREATE INDEX idx_tdcache_copyof
+	ON defs.techdep_cache ( technology_name_id_copyof );
+CREATE INDEX idx_tdcache_techdep
+	ON defs.techdep_cache ( techdep_id );
+
+ALTER TABLE defs.techdep_cache
+	ADD CONSTRAINT fk_tdcache_technology
+		FOREIGN KEY ( technology_name_id ) REFERENCES defs.technologies
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_tdcache_techdep
+		FOREIGN KEY ( techdep_id ) REFERENCES defs.technology_dependencies
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_tdcache_copyof
+		FOREIGN KEY( technology_name_id_copyof , tdcache_reverse , tdcache_id_copyof )
+			REFERENCES defs.techdep_cache( technology_name_id , tdcache_reverse , tdcache_id )
+				ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_tdcache_parent
+		FOREIGN KEY( technology_name_id , tdcache_reverse , tdcache_id_parent )
+			REFERENCES defs.techdep_cache( technology_name_id , tdcache_reverse , tdcache_id )
+				ON DELETE CASCADE;
+
+
 
 /*
  * Old B6M1 research system below.
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/026-technology-dependencies.sql b/legacyworlds-server-data/db-structure/parts/040-functions/026-technology-dependencies.sql
new file mode 100644
index 0000000..ff75daa
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/026-technology-dependencies.sql
@@ -0,0 +1,314 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Functions and triggers that maintain the technology
+-- dependency cache and, generally speaking, the integrity
+-- of the technology graph.
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Dependency tree copy function
+ * ------------------------------
+ * 
+ * This function is used when a dependency is added. It allows a dependency
+ * tree to be copied as the child of another node on a different tree.
+ *
+ * Parameters:
+ *		_reverse		Whether the tree being copied is a reverse or
+ *							forward dependency tree
+ *		_src			Identifier of the source tree
+ *		_dest			Identifier of the destination tree
+ *		_cache			Identifier of the cache entry to use as a parent
+ *		_depth			Depth of the parent node
+ *		_dependency		Identifier of the dependency being added
+ */
+DROP FUNCTION IF EXISTS defs.tdcache_copy_tree(
+		BOOLEAN , INT , INT , INT , INT , INT ) CASCADE;
+CREATE FUNCTION defs.tdcache_copy_tree(
+			_reverse	BOOLEAN ,
+			_src		INT ,
+			_dest		INT ,
+			_cache		INT ,
+			_depth		INT ,
+			_dependency	INT )
+		RETURNS VOID
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+	AS $tdcache_copy_tree$
+
+DECLARE
+	_record		RECORD;
+	_new_entry	INT;
+
+BEGIN
+	CREATE TEMPORARY TABLE tdcache_copy_ids(
+		old_id	INT ,
+		new_id	INT
+	);
+
+	FOR _record IN
+		SELECT * FROM defs.techdep_cache
+			WHERE technology_name_id = _src
+				AND tdcache_reverse = _reverse
+			ORDER BY tdcache_depth ASC
+	LOOP
+		-- Set source of copy
+		IF _record.tdcache_id_copyof IS NULL THEN
+			_record.technology_name_id_copyof := _src;
+			_record.tdcache_id_copyof := _record.tdcache_id;
+		END IF;
+
+		IF _record.tdcache_id_parent IS NULL THEN
+			-- Set parent and dependency if there is none
+			_record.tdcache_id_parent := _cache;
+			_record.techdep_id := _dependency;
+		ELSE
+			-- Remap child nodes to the tree's copy
+			SELECT INTO _record.tdcache_id_parent new_id
+				FROM tdcache_copy_ids
+				WHERE old_id = _record.tdcache_id_parent;
+		END IF;
+
+		-- Set depth
+		_record.tdcache_depth := _record.tdcache_depth + _depth;
+
+		-- Insert new cache entry and add mapping to temporary table
+		INSERT INTO defs.techdep_cache(
+				technology_name_id , tdcache_reverse , tdcache_id_parent ,
+				tdcache_depth , technology_name_id_copyof ,
+				tdcache_id_copyof , techdep_id
+			) VALUES (
+				_dest , _reverse , _record.tdcache_id_parent ,
+				_record.tdcache_depth , _record.technology_name_id_copyof ,
+				_record.tdcache_id_copyof , _record.techdep_id
+			) RETURNING tdcache_id INTO _new_entry;
+
+		INSERT INTO tdcache_copy_ids
+			VALUES ( _record.tdcache_id , _new_entry );
+	END LOOP;
+
+	DROP TABLE tdcache_copy_ids;
+END;
+$tdcache_copy_tree$;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.tdcache_copy_tree(
+			BOOLEAN , INT , INT , INT , INT , INT )
+	FROM PUBLIC;
+
+
+
+/*
+ * Add a sub-tree to all copies of some node
+ * ------------------------------------------
+ * 
+ * This function copies a source tree to all copies for a given technology and
+ * direction.
+ * 
+ * Parameters:
+ *		_reverse	Whether the tree being copied represents reverse or
+ *						forward dependencies
+ *		_src		The identifier of the technology whose dependency tree
+ *						needs to be copied
+ *		_dest		The identifier of the technology onto which the source
+ *						tree is to be grafted
+ *		_dependency	The identifier of the dependency because of which the
+ *						copy is being carried out
+ */
+DROP FUNCTION IF EXISTS defs.tdcache_set_child( BOOLEAN , INT , INT , INT );
+CREATE FUNCTION defs.tdcache_set_child(
+			_reverse	BOOLEAN ,
+			_src		INT ,
+			_dest		INT ,
+			_dependency	INT )
+		RETURNS VOID
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+	AS $tdcache_set_child$
+
+DECLARE
+	_tree	INT;
+	_entry	INT;
+	_depth	INT;
+
+BEGIN
+	FOR _tree , _entry , _depth IN
+		SELECT technology_name_id , tdcache_id , tdcache_depth + 1
+			FROM defs.techdep_cache
+			WHERE tdcache_reverse = _reverse
+				AND technology_name_id_copyof = _dest
+	LOOP
+		PERFORM defs.tdcache_copy_tree( _reverse , _src , _tree , _entry , _depth , _dependency );
+	END LOOP;
+
+END;
+$tdcache_set_child$;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.tdcache_set_child( BOOLEAN , INT , INT , INT )
+	FROM PUBLIC;
+
+
+
+/*
+ * Trigger function that handles new technology definitions
+ * ---------------------------------------------------------
+ * 
+ * When a new technology definition is added, a pair of records needs to be
+ * inserted into the dependency cache. These records correspond to the root
+ * of the trees for the technology for both forward and reverse dependencies.
+ */
+DROP FUNCTION IF EXISTS defs.tgf_technologies_ai( ) CASCADE;
+CREATE FUNCTION defs.tgf_technologies_ai( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $tgf_technologies_ai$
+BEGIN
+
+	INSERT INTO defs.techdep_cache (
+			technology_name_id , tdcache_reverse , tdcache_depth , technology_name_id_copyof
+		) VALUES (
+			NEW.technology_name_id , FALSE , 0 , NEW.technology_name_id
+		) , (
+			NEW.technology_name_id , TRUE , 0 , NEW.technology_name_id
+		);
+	RETURN NEW;
+
+END;
+$tgf_technologies_ai$;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.tgf_technologies_ai()
+	FROM PUBLIC;
+
+CREATE TRIGGER tg_technologies_ai
+	AFTER INSERT ON defs.technologies
+		FOR EACH ROW EXECUTE PROCEDURE defs.tgf_technologies_ai( );
+
+
+
+/*
+ * Trigger function that verifies new dependencies
+ * ------------------------------------------------
+ *
+ * This trigger function locks all related trees, then check that the new
+ * dependency does not lead to cycles or redundant dependencies.
+ */
+DROP FUNCTION IF EXISTS defs.tgf_techdeps_bi( ) CASCADE;
+CREATE FUNCTION defs.tgf_techdeps_bi( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $tgf_techdeps_bi$
+BEGIN
+	-- Lock all trees
+	PERFORM 1
+		FROM defs.techdep_cache n1
+			INNER JOIN defs.techdep_cache n2
+				USING ( technology_name_id )
+		WHERE n1.technology_name_id_copyof IN (
+				NEW.technology_name_id , NEW.technology_name_id_depends )
+		FOR UPDATE OF n2;
+
+	-- Check for cycles
+	PERFORM 1 FROM defs.techdep_cache
+		WHERE technology_name_id = NEW.technology_name_id
+			AND technology_name_id_copyof = NEW.technology_name_id_depends
+			AND tdcache_reverse;
+	IF FOUND THEN
+		RAISE EXCEPTION 'Cycle detected'
+			USING ERRCODE = 'check_violation';
+	END IF;
+
+	-- Check for redundant dependencies
+	PERFORM  1
+		FROM defs.techdep_cache n1
+			INNER JOIN defs.technology_dependencies d
+				ON d.technology_name_id = n1.technology_name_id_copyof
+		WHERE n1.technology_name_id = NEW.technology_name_id
+			AND n1.tdcache_reverse
+			AND d.technology_name_id_depends = NEW.technology_name_id_depends;
+	IF FOUND THEN
+		RAISE EXCEPTION '% is a dependency of a technology that depends on %' ,
+				NEW.technology_name_id_depends , NEW.technology_name_id
+			USING ERRCODE = 'check_violation';
+	END IF;
+
+	PERFORM 1
+		FROM defs.technology_dependencies d1
+			INNER JOIN defs.techdep_cache n
+				ON n.technology_name_id = d1.technology_name_id_depends
+		WHERE d1.technology_name_id = NEW.technology_name_id
+			AND n.tdcache_reverse
+			AND n.technology_name_id_copyof = NEW.technology_name_id_depends;
+	IF FOUND THEN
+		RAISE EXCEPTION '% depends on a dependency of %' ,
+				NEW.technology_name_id , NEW.technology_name_id_depends
+			USING ERRCODE = 'check_violation';
+	END IF;
+
+	PERFORM 1 FROM defs.techdep_cache
+		WHERE technology_name_id = NEW.technology_name_id
+			AND technology_name_id_copyof = NEW.technology_name_id_depends
+			AND NOT tdcache_reverse;
+	IF FOUND THEN
+		RAISE EXCEPTION '% is already a dependency of %' ,
+				NEW.technology_name_id_depends , NEW.technology_name_id
+			USING ERRCODE = 'check_violation';
+	END IF;
+
+	RETURN NEW;
+END;
+$tgf_techdeps_bi$;
+
+
+CREATE TRIGGER tg_techdeps_bi
+	BEFORE INSERT ON defs.technology_dependencies
+		FOR EACH ROW EXECUTE PROCEDURE defs.tgf_techdeps_bi( );
+
+REVOKE EXECUTE
+	ON FUNCTION defs.tgf_techdeps_bi( )
+	FROM PUBLIC;
+
+
+/*
+ * Trigger function that updates the dependency cache
+ * ---------------------------------------------------
+ * 
+ * After a new, valid dependency has been added, trees for both forward and
+ * reverse depdencies must be copied.
+ */
+DROP FUNCTION IF EXISTS defs.tgf_techdeps_ai( ) CASCADE;
+CREATE FUNCTION defs.tgf_techdeps_ai( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $tgf_techdeps_ai$
+BEGIN
+
+	PERFORM defs.tdcache_set_child( FALSE ,
+		NEW.technology_name_id_depends , NEW.technology_name_id ,
+		NEW.techdep_id );
+
+	PERFORM defs.tdcache_set_child( TRUE ,
+		NEW.technology_name_id , NEW.technology_name_id_depends ,
+		NEW.techdep_id );
+
+	RETURN NEW;
+END;
+$tgf_techdeps_ai$;
+
+CREATE TRIGGER tg_techdeps_ai
+	AFTER INSERT ON defs.technology_dependencies
+		FOR EACH ROW EXECUTE PROCEDURE defs.tgf_techdeps_ai( );
+
+REVOKE EXECUTE
+	ON FUNCTION defs.tgf_techdeps_ai( )
+	FROM PUBLIC;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/010-technologies-cache-entries.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/010-technologies-cache-entries.sql
new file mode 100644
index 0000000..13dd5fb
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/010-technologies-cache-entries.sql
@@ -0,0 +1,49 @@
+/*
+ * Make sure that inserting new technologies create corresponding dependency
+ * cache entries, and that these entries are deleted along with the
+ * technologies.
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	-- Make the columns we don't use in the technology definition table NULL-able
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+
+	-- Create a string to use as the technology's name
+	SELECT _create_test_strings( 1 , 'tech' );
+	
+	-- Insert the new technology
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 2 );
+
+	SELECT diag_test_name( 'defs.technologies - Inserting creates cache entries' );
+	SELECT set_eq( $$
+		SELECT tdcache_reverse , ( tdcache_id_parent IS NULL ) AS no_parent ,
+					tdcache_depth , technology_name_id_copyof ,
+					( tdcache_id_copyof IS NULL ) AS not_a_copy ,
+					( techdep_id IS NULL ) AS not_a_dependency
+				FROM defs.techdep_cache
+				WHERE technology_name_id = _get_string( 'tech1' );
+	$$ , $$ VALUES (
+			FALSE , TRUE , 0 , _get_string( 'tech1' ) , TRUE , TRUE
+		) , (
+			TRUE , TRUE , 0 , _get_string( 'tech1' ) , TRUE , TRUE
+	) $$ );
+
+	SELECT diag_test_name( 'defs.technologies - Cache entries are deleted along with technologies' );
+	DELETE FROM defs.technologies WHERE technology_name_id = _get_string( 'tech1' );
+	SELECT is_empty( $$
+		SELECT * FROM defs.techdep_cache
+				WHERE technology_name_id = _get_string( 'tech1' );
+	$$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/020-techdeps-cache-entries.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/020-techdeps-cache-entries.sql
new file mode 100644
index 0000000..af056ca
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/020-techdeps-cache-entries.sql
@@ -0,0 +1,115 @@
+/*
+ * Make sure that inserting technology dependencies results in new entries in
+ * the cache, and that the amount of entries in the cache grows according to
+ * predictions.
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	-- Make the columns we don't use in the technology definition table NULL-able
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+
+	-- Create a string to use as the technologies' names
+	SELECT _create_test_strings( 4 , 'tech' );
+	
+	-- Insert the technologies
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) , ( _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech3' ) ) , ( _get_string( 'tech4' ) );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 3 );
+
+	INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+		VALUES ( _get_string( 'tech2' ) , _get_string( 'tech1' ) );
+	SELECT diag_test_name( 'defs.technology_dependencies - Dependencies create cache entries' );
+	SELECT set_eq( $$
+		SELECT technology_name_id , tdcache_reverse ,
+				( tdcache_id_parent IS NULL ) AS no_parent ,
+				tdcache_depth , technology_name_id_copyof ,
+				( tdcache_id_copyof IS NULL ) AS not_a_copy ,
+				( techdep_id IS NULL ) AS not_a_dependency
+			FROM defs.techdep_cache
+			WHERE technology_name_id IN ( _get_string( 'tech1' ) , _get_string( 'tech2' ) );
+	$$ , $$ VALUES (
+			_get_string( 'tech1' ) , FALSE , TRUE , 0 , _get_string( 'tech1' ) , TRUE , TRUE
+		) , (
+			_get_string( 'tech1' ) , TRUE , TRUE , 0 , _get_string( 'tech1' ) , TRUE , TRUE
+		) , (
+			_get_string( 'tech1' ) , TRUE , FALSE , 1 , _get_string( 'tech2' ) , FALSE , FALSE
+		) , (
+			_get_string( 'tech2' ) , TRUE , TRUE , 0 , _get_string( 'tech2' ) , TRUE , TRUE
+		) , (
+			_get_string( 'tech2' ) , FALSE , TRUE , 0 , _get_string( 'tech2' ) , TRUE , TRUE
+		) , (
+			_get_string( 'tech2' ) , FALSE , FALSE , 1 , _get_string( 'tech1' ) , FALSE , FALSE
+	) $$ );
+
+
+	DELETE FROM defs.technology_dependencies WHERE technology_name_id = _get_string( 'tech2' );
+	SELECT diag_test_name( 'defs.technologies - Cache entries are deleted along with dependencies' );
+	SELECT is_empty( $$
+		SELECT * FROM defs.techdep_cache
+			WHERE technology_name_id IN ( _get_string( 'tech1' ) , _get_string( 'tech2' ) )
+				AND tdcache_depth > 0;
+	$$ );
+
+	
+	/* Now insert the following dependencies:
+	 *	tech4 -> {tech3 , tech2}
+	 *	tech3 -> {tech1}
+	 *	tech2 -> {tech1}
+	 */
+	INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+		VALUES ( _get_string( 'tech4' ) , _get_string( 'tech3' ) ) ,
+				( _get_string( 'tech4' ) , _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech3' ) , _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech2' ) , _get_string( 'tech1' ) );
+
+	/* Then check the amount of cache entries, as described below:
+	 *
+	 *	technology		reverse			entries / copies
+	 * ------------------------------------------------
+	 *	tech1			yes				5 / 4
+	 *	tech1			no				1 / 0
+	 *	tech2			yes				2 / 1
+	 *	tech2			no				2 / 1
+	 *	tech3			yes				2 / 1
+	 *	tech3			no				2 / 1
+	 *	tech4			yes				1 / 0
+	 *	tech4			no				5 / 4
+	 */
+	SELECT set_eq( $$
+		SELECT technology_name_id , tdcache_reverse ,
+				COUNT(*) AS total , COUNT( tdcache_id_copyof ) AS copies
+			FROM defs.techdep_cache
+			WHERE technology_name_id IN (
+					_get_string( 'tech1' ) , _get_string( 'tech2' ) ,
+					_get_string( 'tech3' ) , _get_string( 'tech4' )
+				)
+			GROUP BY technology_name_id , tdcache_reverse;
+	$$ , $$ VALUES (
+			_get_string( 'tech1' ) , TRUE , 5 , 4
+		) , (
+			_get_string( 'tech1' ) , FALSE , 1 , 0
+		) , (
+			_get_string( 'tech2' ) , TRUE , 2 , 1
+		) , (
+			_get_string( 'tech2' ) , FALSE , 2 , 1
+		) , (
+			_get_string( 'tech3' ) , TRUE , 2 , 1
+		) , (
+			_get_string( 'tech3' ) , FALSE , 2 , 1
+		) , (
+			_get_string( 'tech4' ) , TRUE , 1 , 0
+		) , (
+			_get_string( 'tech4' ) , FALSE , 5 , 4
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/030-techdeps-cycles.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/030-techdeps-cycles.sql
new file mode 100644
index 0000000..7f0917f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/030-techdeps-cycles.sql
@@ -0,0 +1,49 @@
+/*
+ * Make sure that it is not possible to create cycles in dependencies.
+ */
+BEGIN;
+	\i utils/strings.sql
+	-- Make the columns we don't use in the technology definition table NULL-able
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+
+	-- Create strings to use as the technologies' names
+	SELECT _create_test_strings( 3 , 'tech' );
+	
+	-- Insert the technologies
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech3' ) );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 3 );
+	
+	SELECT diag_test_name( 'defs.technology_dependencies - Dependency from A to A is rejected' );
+	SELECT throws_ok( $$
+		INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+			VALUES ( _get_string( 'tech1' ) , _get_string( 'tech1' ) );
+	$$ , 23514 );
+
+	SELECT diag_test_name( 'defs.technology_dependencies - Direct dependency from A to B to A is rejected' );
+	INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+		VALUES ( _get_string( 'tech2' ) , _get_string( 'tech1' ) );
+	SELECT throws_ok( $$
+		INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+			VALUES ( _get_string( 'tech1' ) , _get_string( 'tech2' ) );
+	$$ , 23514 );
+
+	SELECT diag_test_name( 'defs.technology_dependencies - Indirect dependency from A to B to A is rejected' );
+	INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+		VALUES ( _get_string( 'tech3' ) , _get_string( 'tech2' ) );
+	SELECT throws_ok( $$
+		INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+			VALUES ( _get_string( 'tech1' ) , _get_string( 'tech3' ) );
+	$$ , 23514 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/040-techdeps-redundancy.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/040-techdeps-redundancy.sql
new file mode 100644
index 0000000..c065f02
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/040-techdeps-redundancy.sql
@@ -0,0 +1,79 @@
+/*
+ * Make sure that it is not possible to create redundant dependencies
+ */
+BEGIN;
+	\i utils/strings.sql
+	-- Make the columns we don't use in the technology definition table NULL-able
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+
+	-- Create strings to use as the technologies' names
+	SELECT _create_test_strings( 3 , 'tech' );
+	
+	-- Insert the technologies
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech3' ) );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 5 );
+
+	/*
+	 * Assuming we have tech2 -> {tech1} and tech3 -> {tech1}, it shouldn't be
+	 * possible to add dependencies between tech2 and tech3.
+	 */
+	INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+		VALUES ( _get_string( 'tech2' ) , _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech3' ) , _get_string( 'tech1' ) ); 
+	SELECT diag_test_name( 'defs.technology_dependencies - B -> A and C -> A, adding B -> C is redundant' );
+	SELECT throws_ok( $$
+		INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+			VALUES ( _get_string( 'tech2' ) , _get_string( 'tech3' ) );
+	$$ , 23514 );
+	SELECT diag_test_name( 'defs.technology_dependencies - B -> A and C -> A, adding C -> B is redundant' );
+	SELECT throws_ok( $$
+		INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+			VALUES ( _get_string( 'tech3' ) , _get_string( 'tech2' ) );
+	$$ , 23514 );
+	DELETE FROM defs.technology_dependencies;
+
+	/*
+	 * Assuming we have tech3 -> {tech2,tech1}, trying to add a dependency
+	 * between tech2 and tech1 would cause a redundancy.
+	 */
+	INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+		VALUES ( _get_string( 'tech3' ) , _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech3' ) , _get_string( 'tech2' ) ); 
+	SELECT diag_test_name( 'defs.technology_dependencies - C -> A and C -> B, adding A -> B is redundant' );
+	SELECT throws_ok( $$
+		INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+			VALUES ( _get_string( 'tech1' ) , _get_string( 'tech2' ) );
+	$$ , 23514 );
+	SELECT diag_test_name( 'defs.technology_dependencies - C -> A and C -> B, adding B -> A is redundant' );
+	SELECT throws_ok( $$
+		INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+			VALUES ( _get_string( 'tech2' ) , _get_string( 'tech1' ) );
+	$$ , 23514 );
+	DELETE FROM defs.technology_dependencies;
+
+	/*
+	 * Assuming we have tech3 -> {tech2} and tech2 -> {tech1}, trying to add a
+	 * dependency from tech3 to tech1 would cause a redundancy.
+	 */
+	INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+		VALUES ( _get_string( 'tech3' ) , _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech2' ) , _get_string( 'tech1' ) ); 
+	SELECT diag_test_name( 'defs.technology_dependencies - C -> B and B -> A, adding C -> A is redundant' );
+	SELECT throws_ok( $$
+		INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+			VALUES ( _get_string( 'tech3' ) , _get_string( 'tech1' ) );
+	$$ , 23514 );
+	DELETE FROM defs.technology_dependencies;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/010-tdcache-copy-tree.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/010-tdcache-copy-tree.sql
new file mode 100644
index 0000000..75b1d8c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/010-tdcache-copy-tree.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.tdcache_copy_tree( )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.tdcache_copy_tree( ) - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT defs.tdcache_copy_tree( FALSE , 1 , 2 , 3 , 4 , 5 );
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/020-tdcache-set-child.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/020-tdcache-set-child.sql
new file mode 100644
index 0000000..c9b9ca9
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/020-tdcache-set-child.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.tdcache_set_child( )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.tdcache_set_child( ) - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT defs.tdcache_set_child( FALSE , 1 , 2 , 3 );
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/030-tgf-technologies-ai.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/030-tgf-technologies-ai.sql
new file mode 100644
index 0000000..6232dfc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/030-tgf-technologies-ai.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.tgf_technologies_ai( )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.tgf_technologies_ai( ) - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT defs.tgf_technologies_ai( );
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/040-tgf-techdeps-bi.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/040-tgf-techdeps-bi.sql
new file mode 100644
index 0000000..90a7a72
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/040-tgf-techdeps-bi.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.tgf_techdeps_bi( )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.tgf_techdeps_bi( ) - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT defs.tgf_techdeps_bi( );
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/050-tgf-techdeps-ai.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/050-tgf-techdeps-ai.sql
new file mode 100644
index 0000000..ec4bc01
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/050-tgf-techdeps-ai.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.tgf_techdeps_ai( )
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.tgf_techdeps_ai( ) - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT defs.tgf_techdeps_ai( );
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From 1ae9a15f6ef1d14c0a126347612e5ab6e6cbcbb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 16 Feb 2012 18:32:44 +0100
Subject: [PATCH 56/94] Improvements to developer SQL tests runner

* The script will no longer fail when there are no tests matching the
requested name in either admin/ or user/

* The script will ignore non-SQL files when running with --run-name
---
 legacyworlds/dev-tools/run-database-tests.sh | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/legacyworlds/dev-tools/run-database-tests.sh b/legacyworlds/dev-tools/run-database-tests.sh
index cfaf812..b1956f8 100755
--- a/legacyworlds/dev-tools/run-database-tests.sh
+++ b/legacyworlds/dev-tools/run-database-tests.sh
@@ -50,10 +50,14 @@ EOF
 	if [ "x$run_name" = "x" ]; then
 		run_name='*.sql'
 	else
-		run_name='*'"$run_name"'*'
+		run_name='*'"$run_name"'*.sql'
+	fi
+	if ! [ -z "`find admin/ -type f -name "$run_name"`" ]; then
+		pg_prove -d $test_db `find admin/ -type f -name "$run_name" | sort` || exit 1
+	fi
+	if ! [ -z "`find user/ -type f -name "$run_name"`" ]; then
+		pg_prove -U $test_user -d $test_db `find user/ -type f -name "$run_name" | sort` || exit 1
 	fi
-	pg_prove -d $test_db `find admin/ -type f -name "$run_name" | sort` || exit 1
-	pg_prove -U $test_user -d $test_db `find user/ -type f -name "$run_name" | sort` || exit 1
 )
 result=$?
 

From 3b2ec4bb640e46d0e56e72f93fe2f9ef584012e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Fri, 17 Feb 2012 07:46:51 +0100
Subject: [PATCH 57/94] Empire technologies table

* Added table to store empire technologies. The table will store
technologies in all states.

* Note: this table is called "emp.technologies_v2" for now. It will need
to be renamed when the old tech system is gone.

* 030-data/110-empires.sql needs to be executed to create the new table
and a type it uses.
---
 .../parts/030-data/110-empires.sql            | 80 +++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
index f6015dc..4480ff9 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
@@ -67,6 +67,86 @@ ALTER TABLE emp.resources
 		FOREIGN KEY ( resource_name_id ) REFERENCES defs.resources;
 
 
+
+/*
+ * States for empire technologies
+ * -------------------------------
+ *
+ * This enumerated type represents the 3 possible state for an empire
+ * technology: being researched, pending implementation, or implemented.
+ */
+
+DROP TYPE IF EXISTS emp.technology_state CASCADE;
+CREATE TYPE emp.technology_state
+	AS ENUM(
+		/* Research in progress */
+		'RESEARCH' ,
+		/* Implementation pending */
+		'PENDING' ,
+		/* Implemented technology */
+		'KNOWN'
+	);
+
+
+/*
+ * Empire technologies
+ * --------------------
+ * 
+ * This table stores technologies that are being researched, need to be or
+ * have been implemented by an empire. Technologies that are being researched
+ * include additional information that represent the research progress and
+ * priority.
+ * 
+ * FIXME: this table must be renamed to emp.technologies after the old
+ * research system has been removed 
+ */
+
+CREATE TABLE emp.technologies_v2(
+	/* Identifier of the empire */
+	empire_id				INT NOT NULL ,
+	
+	/* Identifier of the technology */
+	technology_name_id		INT NOT NULL ,
+	
+	/* Current state of the technology */
+	emptech_state			emp.technology_state NOT NULL
+								DEFAULT 'RESEARCH' ,
+
+	/* Accumulated research points, or NULL if research is over */
+	emptech_points			DOUBLE PRECISION
+								DEFAULT 0.0 ,
+
+	/* Research priority, or NULL if research is over */
+	emptech_priority		INT DEFAULT 2
+								CHECK( emptech_priority BETWEEN 0 AND 4 ) ,
+
+	/* Primary key is the empire / technology pair */
+	PRIMARY KEY( empire_id , technology_name_id ) ,
+	
+	/* Make sure there are both research points and a priority during research
+	 * and neither when the technology is pending implementation or
+	 * implemented.
+	 */
+	CHECK( CASE emptech_state
+		WHEN 'RESEARCH' THEN
+			( emptech_points IS NOT NULL AND emptech_priority IS NOT NULL )
+		ELSE
+			( emptech_points IS NULL AND emptech_priority IS NULL )
+	END )
+);
+
+CREATE INDEX idx_emptech_technology
+	ON emp.technologies_v2 ( technology_name_id );
+
+ALTER TABLE emp.technologies_v2
+	ADD CONSTRAINT fk_emptech_empire
+		FOREIGN KEY ( empire_id ) REFERENCES emp.empires ( name_id )
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_emptech_technology
+		FOREIGN KEY ( technology_name_id ) REFERENCES defs.technologies;
+
+
+
 --
 -- Empire technologies
 --

From af57e7d3b5484164b27e145adcf988504cc35628 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 27 Feb 2012 14:14:00 +0100
Subject: [PATCH 58/94] Technology definition functions

* Added stored procedures which manipulate technology definitions
themselves (defs.uoc_technology) or their dependencies (defs.techdep_add
and defs.techdep_remove)
---
 .../db-structure/parts/030-data/080-techs.sql |   4 +-
 .../parts/040-functions/030-tech.sql          | 321 ++++++++++++++++++
 .../030-tech/010-uoc-technology-create.sql    |  86 +++++
 .../030-tech/020-uoc-technology-update.sql    |  82 +++++
 .../030-tech/030-uoc-technology-scale.sql     |  58 ++++
 .../030-tech/040-techdep-add.sql              |  65 ++++
 .../030-tech/050-techdep-remove.sql           |  51 +++
 .../030-tech/010-uoc-technology.sql           |  13 +
 .../030-tech/020-techdep-add.sql              |  13 +
 .../030-tech/030-techdep-remove.sql           |  13 +
 10 files changed, 704 insertions(+), 2 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/010-uoc-technology-create.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/020-uoc-technology-update.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/030-uoc-technology-scale.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/040-techdep-add.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/050-techdep-remove.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/010-uoc-technology.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/020-techdep-add.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/030-techdep-remove.sql

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
index 289c7b2..de20289 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
@@ -32,11 +32,11 @@ CREATE TABLE defs.technologies(
 	technology_description_id	INT NOT NULL ,
 	
 	/* Monetary price of the technology - FIXME: will be removed later */
-	technology_price			INT NOT NULL
+	technology_price			BIGINT NOT NULL
 									CHECK( technology_price > 0 ) ,
 
 	/* Cost of the technology in terms of research points */
-	technology_points			INT NOT NULL
+	technology_points			BIGINT NOT NULL
 									CHECK( technology_points > 0 )
 );
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
index eb2c74a..5b2e90b 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
@@ -7,6 +7,327 @@
 -- --------------------------------------------------------
 
 
+/*
+ * Return type for technology management functions
+ * ------------------------------------------------
+ * 
+ * This enumerated type is used for the return values of all technology
+ * management functions. This includes defs.uoc_technology() of course,
+ * but also defs.techdep_add() and defs.techdep_remove().
+ */
+DROP TYPE IF EXISTS defs.technology_update_result CASCADE;
+CREATE TYPE defs.technology_update_result
+	AS ENUM(
+		/* The technology definition or dependency was created */
+		'CREATED' ,
+
+		/* The technology definition was updated */
+		'UPDATED' ,
+		
+		/* The dependency was deleted */
+		'DELETED' ,
+		
+		/* The specified dependency does not exist */
+		'MISSING' ,
+
+		/* One (or more) of the numeric parameters is invalid */
+		'BAD_VALUE' ,
+
+		/* The name, description, discovery or category string identifiers
+		 * were not valid string identifiers.
+		 */
+		'BAD_STRINGS' ,
+
+		/* The specified description and/or discovery string was in use by
+		 * another technology.
+		 */
+		'DUP_STRING' ,
+		
+		/* The dependency would cause a cycle */
+		'CYCLE' ,
+		
+		/* The dependency would be redundant */
+		'REDUNDANT'
+	);
+
+
+/*
+ * Update or create a technology definition
+ * -----------------------------------------
+ *
+ * This stored procedure can be used to update existing technology definitions
+ * or create new records. It will not affect technology dependencies: no
+ * depedencies will be added when creating a new technology, and existing
+ * dependencies will be conserved when updating.
+ * 
+ * If a technology already exists, check for empires researching that
+ * technology, and scale their current progress accordingly.
+ * 
+ * Parameters:
+ *		_name			Text identifier of the name string
+ *		_category		Text identifier of the category string
+ *		_discovery		Text identifier of the discovery string
+ *		_description	Text identifier of the description string
+ *		_price			Monetary cost to implement the technology
+ *		_points			Amount of points required to research teh technology
+ *
+ * Returns:
+ *		?				One of the following return codes: CREATED, UPDATED,
+ *							BAD_VALUE, BAD_STRINGS, DUP_STRING
+ */
+DROP FUNCTION IF EXISTS defs.uoc_technology(
+		TEXT , TEXT , TEXT , TEXT , BIGINT , BIGINT ) CASCADE;
+CREATE FUNCTION defs.uoc_technology(
+			_name			TEXT ,
+			_category		TEXT ,
+			_discovery		TEXT ,
+			_description	TEXT ,
+			_price			BIGINT ,
+			_points			BIGINT )
+		RETURNS defs.technology_update_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $uoc_technology$
+
+DECLARE
+	_name_id	INT;
+	_disc_id	INT;
+	_desc_id	INT;
+	_cat_id		INT;
+	_old_points	BIGINT;
+	_multi		DOUBLE PRECISION;
+
+BEGIN
+	-- Get all string identifiers
+	SELECT INTO _name_id id FROM defs.strings WHERE name = _name;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+	SELECT INTO _desc_id id FROM defs.strings WHERE name = _description;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+	SELECT INTO _disc_id id FROM defs.strings WHERE name = _discovery;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+	SELECT INTO _cat_id id FROM defs.strings WHERE name = _category;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+
+	-- Try inserting the record
+	BEGIN
+		INSERT INTO defs.technologies (
+			technology_name_id , technology_category_id ,
+			technology_discovery_id , technology_description_id ,
+			technology_price , technology_points
+		) VALUES (
+			_name_id , _cat_id ,
+			_disc_id , _desc_id ,
+			_price , _points
+		);
+		RETURN 'CREATED';
+	EXCEPTION
+		WHEN unique_violation THEN
+			-- Continue, we can't determine which error this
+			-- was about at this point.
+	END;
+	
+	-- Lock existing definition and empire research records
+	PERFORM 1
+		FROM defs.technologies _def
+			LEFT OUTER JOIN (
+					SELECT technology_name_id
+						FROM emp.technologies_v2 _tech
+						WHERE technology_name_id = _name_id
+								AND emptech_state = 'RESEARCH'
+						FOR UPDATE OF _tech
+				) _emps USING ( technology_name_id )
+		WHERE technology_name_id = _name_id
+		FOR UPDATE OF _def;
+	IF NOT FOUND THEN
+		RETURN 'DUP_STRING';
+	END IF;
+	
+	-- Get old value for research points
+	SELECT INTO _old_points technology_points
+		FROM defs.technologies
+		WHERE technology_name_id = _name_id;
+
+	-- Update the record
+	BEGIN
+		UPDATE defs.technologies
+			SET technology_category_id = _cat_id ,
+				technology_discovery_id = _disc_id ,
+				technology_description_id = _desc_id ,
+				technology_price = _price ,
+				technology_points = _points
+			WHERE technology_name_id = _name_id;
+	EXCEPTION
+		WHEN unique_violation THEN
+			RETURN 'DUP_STRING';
+	END;
+	
+	-- Update empire research if necessary
+	IF _old_points <> _points THEN
+		_multi := _points::DOUBLE PRECISION / _old_points::DOUBLE PRECISION;
+		UPDATE emp.technologies_v2
+			SET emptech_points = emptech_points * _multi
+			WHERE technology_name_id = _name_id
+				AND emptech_points IS NOT NULL;
+	END IF;
+
+	RETURN 'UPDATED';
+
+EXCEPTION
+	WHEN check_violation THEN
+		RETURN 'BAD_VALUE';
+
+END;
+$uoc_technology$;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.uoc_technology(
+		TEXT , TEXT , TEXT , TEXT , BIGINT , BIGINT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION defs.uoc_technology(
+		TEXT , TEXT , TEXT , TEXT , BIGINT , BIGINT )
+	TO :dbuser;
+
+
+
+/*
+ * Add a technology dependency
+ * ----------------------------
+ * 
+ * This stored procedure attempts to create a dependency between two
+ * technologies by looking them up by name then inserting the pair in the
+ * dependency table.
+ *
+ * Parameters:
+ *		_dependent		The name of the dependent technology
+ *		_dependency		The name of the dependency
+ *
+ * Returns:
+ * 		?				One of the following return codes: CREATED,
+ *							BAD_STRINGS, CYCLE, REDUNDANT
+ */
+DROP FUNCTION IF EXISTS defs.techdep_add( TEXT , TEXT );
+CREATE FUNCTION defs.techdep_add( _dependent TEXT , _dependency TEXT )
+		RETURNS defs.technology_update_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $techdep_add$
+
+DECLARE
+	_tech1_id	INT;
+	_tech2_id	INT;
+
+BEGIN
+	
+	SELECT INTO _tech1_id , _tech2_id _str1.id , _str2.id
+		FROM defs.strings _str1
+			CROSS JOIN defs.strings _str2
+		WHERE _str1.name = _dependent
+			AND _str2.name = _dependency;
+	IF NOT FOUND THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+	
+	INSERT INTO defs.technology_dependencies (
+			technology_name_id , technology_name_id_depends
+		) VALUES ( _tech1_id , _tech2_id );
+	RETURN 'CREATED';
+
+EXCEPTION
+
+	WHEN foreign_key_violation THEN
+		RETURN 'BAD_STRINGS';
+
+	WHEN unique_violation THEN
+		RETURN 'REDUNDANT';
+
+	WHEN check_violation THEN
+		IF SQLERRM LIKE '%Cycle detected%' THEN
+			RETURN 'CYCLE';
+		END IF;
+		RETURN 'REDUNDANT';
+
+END;
+$techdep_add$;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.techdep_add( TEXT , TEXT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION defs.techdep_add( TEXT , TEXT )
+	TO :dbuser;
+
+
+
+/*
+ * Remove a technology dependency
+ * -------------------------------
+ * 
+ * This stored procedure removes a dependency from a technology to another.
+ * 
+ * Parameters:
+ *		_dependent		The name of the dependent technology
+ *		_dependency		The name of the dependency
+ *
+ * Returns:
+ * 		?				One of the following return codes: DELETED, MISSING
+ */
+DROP FUNCTION IF EXISTS defs.techdep_remove( TEXT , TEXT );
+CREATE FUNCTION defs.techdep_remove( _dependent TEXT , _dependency TEXT )
+		RETURNS defs.technology_update_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $techdep_remove$
+
+DECLARE
+	_dep_id		INT;
+
+BEGIN
+	
+	SELECT INTO _dep_id techdep_id
+		FROM defs.technology_dependencies _td
+			INNER JOIN defs.strings _str1
+				ON _str1.id = _td.technology_name_id
+			INNER JOIN defs.strings _str2
+				ON _str2.id = _td.technology_name_id_depends
+		WHERE _str1.name = _dependent
+			AND _str2.name = _dependency
+		FOR UPDATE OF _td;
+	IF NOT FOUND THEN
+		RETURN 'MISSING';
+	END IF;
+
+	DELETE FROM defs.technology_dependencies
+		WHERE techdep_id = _dep_id;
+	RETURN 'DELETED';
+
+END;
+$techdep_remove$;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.techdep_remove( TEXT , TEXT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION defs.techdep_remove( TEXT , TEXT )
+	TO :dbuser;
+
+
+
+-- ********************************************************
+-- OLD CODE BELOW
+-- ********************************************************
+
 --
 -- "Basic" buildables view (buildables that do not depend on any technology)
 --
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/010-uoc-technology-create.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/010-uoc-technology-create.sql
new file mode 100644
index 0000000..3ac248f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/010-uoc-technology-create.sql
@@ -0,0 +1,86 @@
+/*
+ * Test the defs.uoc_technology() function for new technologies
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	/*
+	 * We need a few test strings to play with. Each technology definition
+	 * uses four strings (name, category, discovery and description) so we'll
+	 * create 8.
+	 */
+	SELECT _create_test_strings( 8 , 's' );
+
+	/*
+	 * Manually insert an entry into the technologies table, using strings
+	 * 5-8 for the various fields.
+	 */
+	INSERT INTO defs.technologies (
+		technology_name_id , technology_category_id ,
+		technology_discovery_id , technology_description_id ,
+		technology_price , technology_points
+	) VALUES (
+		_get_string( 's5' ) , _get_string( 's6' ) ,
+		_get_string( 's7' ) , _get_string( 's8' ) ,
+		1 , 1
+	);
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 10 );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Invalid name string' );
+	SELECT is( defs.uoc_technology(
+			'does not exist' , 's2' , 's3' , 's4' , 1 , 1
+		)::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Invalid category string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 'does not exist' , 's3' , 's4' , 1 , 1
+		)::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Invalid discovery string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 'does not exist' , 's4' , 1 , 1
+		)::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Invalid description string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 'does not exist' , 1 , 1
+		)::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Invalid price' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 's4' , 0 , 1
+		)::TEXT , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Invalid research points' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 's4' , 1 , 0
+		)::TEXT , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Duplicate description string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's7' , 's4' , 1 , 1
+		)::TEXT , 'DUP_STRING' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Duplicate discovery string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 's8' , 1 , 1
+		)::TEXT , 'DUP_STRING' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Success' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 's4' , 2 , 3
+		)::TEXT , 'CREATED' );
+	SELECT diag_test_name( 'defs.uoc_technology() - Creation - Row exists after success' );
+	SELECT set_eq( $$
+		SELECT * FROM defs.technologies
+			WHERE technology_name_id = _get_string( 's1' )
+	$$ , $$ VALUES(
+			_get_string( 's1' ) , _get_string( 's2' ) ,
+			_get_string( 's3' ) , _get_string( 's4' ) ,
+			2 , 3
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/020-uoc-technology-update.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/020-uoc-technology-update.sql
new file mode 100644
index 0000000..383e453
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/020-uoc-technology-update.sql
@@ -0,0 +1,82 @@
+/*
+ * Test the defs.uoc_technology() function for existing technologies
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	/*
+	 * We need a few test strings to play with. Each technology definition
+	 * uses four strings (name, category, discovery and description) so we'll
+	 * create 8.
+	 */
+	SELECT _create_test_strings( 8 , 's' );
+
+	/* Insert two entries. */
+	INSERT INTO defs.technologies (
+		technology_name_id , technology_category_id ,
+		technology_discovery_id , technology_description_id ,
+		technology_price , technology_points
+	) VALUES (
+		_get_string( 's1' ) , _get_string( 's2' ) ,
+		_get_string( 's3' ) , _get_string( 's4' ) ,
+		1 , 1
+	) , (
+		_get_string( 's5' ) , _get_string( 's6' ) ,
+		_get_string( 's7' ) , _get_string( 's8' ) ,
+		1 , 1
+	);
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 9 );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Invalid category string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 'does not exist' , 's3' , 's4' , 1 , 1
+		)::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Invalid discovery string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 'does not exist' , 's4' , 1 , 1
+		)::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Invalid description string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 'does not exist' , 1 , 1
+		)::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Invalid price' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 's4' , 0 , 1
+		)::TEXT , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Invalid research points' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 's4' , 1 , 0
+		)::TEXT , 'BAD_VALUE' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Duplicate description string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's7' , 's4' , 1 , 1
+		)::TEXT , 'DUP_STRING' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Duplicate discovery string' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's2' , 's3' , 's8' , 1 , 1
+		)::TEXT , 'DUP_STRING' );
+
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Success' );
+	SELECT is( defs.uoc_technology(
+			's1' , 's4' , 's2' , 's3' , 2 , 3
+		)::TEXT , 'UPDATED' );
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Row contents after success' );
+	SELECT set_eq( $$
+		SELECT * FROM defs.technologies
+			WHERE technology_name_id = _get_string( 's1' )
+	$$ , $$ VALUES(
+			_get_string( 's1' ) , _get_string( 's4' ) ,
+			_get_string( 's2' ) , _get_string( 's3' ) ,
+			2 , 3
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/030-uoc-technology-scale.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/030-uoc-technology-scale.sql
new file mode 100644
index 0000000..595bb7f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/030-uoc-technology-scale.sql
@@ -0,0 +1,58 @@
+/*
+ * Test the defs.uoc_technology() function when the points for a technology
+ * are updated and empires are researching it.
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	/*
+	 * We need a few test strings to play with. Each technology definition
+	 * uses four strings (name, category, discovery and description) so we'll
+	 * create 4.
+	 */
+	SELECT _create_test_strings( 4 , 's' );
+
+	/* Insert one entry. */
+	INSERT INTO defs.technologies (
+		technology_name_id , technology_category_id ,
+		technology_discovery_id , technology_description_id ,
+		technology_price , technology_points
+	) VALUES (
+		_get_string( 's1' ) , _get_string( 's2' ) ,
+		_get_string( 's3' ) , _get_string( 's4' ) ,
+		1 , 100
+	);
+	
+	/* Remove foreign key to empires on empire technologies */
+	ALTER TABLE emp.technologies_v2
+		DROP CONSTRAINT fk_emptech_empire;
+
+	/* Insert records for the new technology, with different states */
+	INSERT INTO emp.technologies_v2 (
+		empire_id , technology_name_id , emptech_state ,
+		emptech_points , emptech_priority
+	) VALUES (
+		1 , _get_string( 's1' ) , 'RESEARCH' , 50 , 2
+	) , (
+		2 , _get_string( 's1' ) , 'RESEARCH' , 0 , 2
+	);
+	
+	/* Now change the technology so it requires 1000 points */
+	SELECT defs.uoc_technology( 's1' , 's2' , 's3' , 's4' , 1 , 1000 );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT no_plan( );
+	
+	SELECT diag_test_name( 'defs.uoc_technology() - Update - Scaling of in-progress research' );
+	SELECT set_eq( $$
+		SELECT empire_id , ROUND( emptech_points )::INT
+			FROM emp.technologies_v2
+			WHERE emptech_state = 'RESEARCH'
+	$$ , $$ VALUES(
+		1 , 500
+	) , (
+		2 , 0
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/040-techdep-add.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/040-techdep-add.sql
new file mode 100644
index 0000000..c5236f7
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/040-techdep-add.sql
@@ -0,0 +1,65 @@
+/*
+ * Test the defs.techdep_add() function
+ */
+BEGIN;
+	\i utils/strings.sql
+	-- Make the columns we don't use in the technology definition table NULL-able
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+
+	-- Create strings to use as the technologies' names
+	SELECT _create_test_strings( 5 , 'tech' );
+	
+	-- Insert the technologies
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech3' ) ) ,
+				( _get_string( 'tech4' ) );
+
+	-- Add a few dependencies
+	INSERT INTO defs.technology_dependencies(
+			technology_name_id , technology_name_id_depends
+		) VALUES ( _get_string( 'tech3' ) , _get_string( 'tech2' ) ) , 
+			( _get_string( 'tech2' ) , _get_string( 'tech1' ) );
+
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 8 );
+	
+	SELECT diag_test_name( 'defs.techdep_add() - Bad dependent technology name' );
+	SELECT is( defs.techdep_add( 'does not exist' , 'tech2' )::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.techdep_add() - Bad dependency name' );
+	SELECT is( defs.techdep_add( 'tech1' , 'does not exist' )::TEXT , 'BAD_STRINGS' );
+	
+	SELECT diag_test_name( 'defs.techdep_add() - Valid name that is not a technology' );
+	SELECT is( defs.techdep_add( 'tech5' , 'tech2' )::TEXT , 'BAD_STRINGS' );
+
+	SELECT diag_test_name( 'defs.techdep_add() - Duplicate dependency' );
+	SELECT is( defs.techdep_add( 'tech3' , 'tech2' )::TEXT , 'REDUNDANT' );
+
+	SELECT diag_test_name( 'defs.techdep_add() - Cyclic dependency' );
+	SELECT is( defs.techdep_add( 'tech2' , 'tech3' )::TEXT , 'CYCLE' );
+
+	SELECT diag_test_name( 'defs.techdep_add() - Redundant dependency' );
+	SELECT is( defs.techdep_add( 'tech3' , 'tech1' )::TEXT , 'REDUNDANT' );
+
+	SELECT diag_test_name( 'defs.techdep_add() - Success - Return value' );
+	SELECT is( defs.techdep_add( 'tech4' , 'tech1' )::TEXT , 'CREATED' );
+
+	SELECT diag_test_name( 'defs.techdep_add() - Success - Table entries' );
+	SELECT set_eq( $$
+		SELECT technology_name_id_depends
+			FROM defs.technology_dependencies
+			WHERE technology_name_id = _get_string( 'tech4' );
+	$$ , $$ VALUES(
+		_get_string( 'tech1' )
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/050-techdep-remove.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/050-techdep-remove.sql
new file mode 100644
index 0000000..b7da5be
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/050-techdep-remove.sql
@@ -0,0 +1,51 @@
+/*
+ * Test the defs.techdep_remove() function
+ */
+BEGIN;
+	\i utils/strings.sql
+	-- Make the columns we don't use in the technology definition table NULL-able
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+
+	-- Create strings to use as the technologies' names
+	SELECT _create_test_strings( 2 , 'tech' );
+	
+	-- Insert the technologies
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech2' ) );
+
+	-- Add a dependency from tech2 to tech1
+	INSERT INTO defs.technology_dependencies(
+			technology_name_id , technology_name_id_depends
+		) VALUES ( _get_string( 'tech2' ) , _get_string( 'tech1' ) );
+
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 5 );
+	
+	SELECT diag_test_name( 'defs.techdep_remove() - Bad dependent technology name' );
+	SELECT is( defs.techdep_remove( 'does not exist' , 'tech1' )::TEXT , 'MISSING' );
+
+	SELECT diag_test_name( 'defs.techdep_remove() - Bad dependency name' );
+	SELECT is( defs.techdep_remove( 'tech2' , 'does not exist' )::TEXT , 'MISSING' );
+
+	SELECT diag_test_name( 'defs.techdep_remove() - Correct name but no dependency' );
+	SELECT is( defs.techdep_remove( 'tech1' , 'tech2' )::TEXT , 'MISSING' );
+	
+	SELECT diag_test_name( 'defs.techdep_remove() - Success - Return value' );
+	SELECT is( defs.techdep_remove( 'tech2' , 'tech1' )::TEXT , 'DELETED' );
+	
+	SELECT diag_test_name( 'defs.techdep_remove() - Success - Table contents' );
+	SELECT is_empty($$
+		SELECT * FROM defs.technology_dependencies
+			WHERE technology_name_id = _get_string( 'tech2' )
+				AND technology_name_id_depends = _get_string( 'tech1' );
+	$$);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/010-uoc-technology.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/010-uoc-technology.sql
new file mode 100644
index 0000000..aa26d1b
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/010-uoc-technology.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on defs.uoc_technology()
+ */
+BEGIN;
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.uoc_technology() - EXECUTE privilege' );
+	SELECT lives_ok( $$
+		SELECT defs.uoc_technology( '' , '' , '' , '' , 1 , 2 );
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/020-techdep-add.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/020-techdep-add.sql
new file mode 100644
index 0000000..f516d51
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/020-techdep-add.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on defs.techdep_add()
+ */
+BEGIN;
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.techdep_add() - EXECUTE privilege' );
+	SELECT lives_ok( $$
+		SELECT defs.techdep_add( '' , '' );
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/030-techdep-remove.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/030-techdep-remove.sql
new file mode 100644
index 0000000..a94ba9f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/030-techdep-remove.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on defs.techdep_remove()
+ */
+BEGIN;
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.techdep_remove() - EXECUTE privilege' );
+	SELECT lives_ok( $$
+		SELECT defs.techdep_remove( '' , '' );
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From 26fe4b926dadd125c471c0afb5c644af027add02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 27 Feb 2012 18:18:35 +0100
Subject: [PATCH 59/94] Fixed package for resources data tests

* The tests for resource definition import classes were not in the right
package. They have been moved.
---
 .../lw/cli/xmlimport/{ => data}/resources/BaseTest.java         | 2 +-
 .../cli/xmlimport/{ => data}/resources/TestBasicResource.java   | 2 +-
 .../cli/xmlimport/{ => data}/resources/TestNaturalResource.java | 2 +-
 .../xmlimport/{ => data}/resources/TestResourceParameter.java   | 2 +-
 .../lw/cli/xmlimport/{ => data}/resources/TestResources.java    | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)
 rename legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/{ => data}/resources/BaseTest.java (99%)
 rename legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/{ => data}/resources/TestBasicResource.java (99%)
 rename legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/{ => data}/resources/TestNaturalResource.java (99%)
 rename legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/{ => data}/resources/TestResourceParameter.java (99%)
 rename legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/{ => data}/resources/TestResources.java (98%)

diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/BaseTest.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/BaseTest.java
similarity index 99%
rename from legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/BaseTest.java
rename to legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/BaseTest.java
index 74a88d2..d578d51 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/BaseTest.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/BaseTest.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.cli.xmlimport.resources;
+package com.deepclone.lw.cli.xmlimport.data.resources;
 
 
 import com.deepclone.lw.cli.xmlimport.data.ImportableData;
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestBasicResource.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestBasicResource.java
similarity index 99%
rename from legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestBasicResource.java
rename to legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestBasicResource.java
index a6bb6b3..fd5f8cf 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestBasicResource.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestBasicResource.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.cli.xmlimport.resources;
+package com.deepclone.lw.cli.xmlimport.data.resources;
 
 
 import static org.junit.Assert.assertEquals;
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestNaturalResource.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestNaturalResource.java
similarity index 99%
rename from legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestNaturalResource.java
rename to legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestNaturalResource.java
index e46ed9c..63a55ae 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestNaturalResource.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestNaturalResource.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.cli.xmlimport.resources;
+package com.deepclone.lw.cli.xmlimport.data.resources;
 
 
 import static org.junit.Assert.assertEquals;
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResourceParameter.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestResourceParameter.java
similarity index 99%
rename from legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResourceParameter.java
rename to legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestResourceParameter.java
index 7ff4d6d..dd82eee 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResourceParameter.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestResourceParameter.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.cli.xmlimport.resources;
+package com.deepclone.lw.cli.xmlimport.data.resources;
 
 
 import static org.junit.Assert.assertEquals;
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResources.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestResources.java
similarity index 98%
rename from legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResources.java
rename to legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestResources.java
index 3a75fc9..dc730a4 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/resources/TestResources.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/TestResources.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.cli.xmlimport.resources;
+package com.deepclone.lw.cli.xmlimport.data.resources;
 
 
 import static org.junit.Assert.assertEquals;

From c5464212bcda68c5a13597220c8597cc3715e5c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 27 Feb 2012 14:27:33 +0100
Subject: [PATCH 60/94] Deleted old "test" buildables and technologies

* These 2 definition files were remnants of B6M1's early tests. They did
not belong in the repo any more.
---
 .../data/buildables-test.xml                  | 40 -------------------
 legacyworlds-server-main/data/techs-test.xml  | 23 -----------
 2 files changed, 63 deletions(-)
 delete mode 100644 legacyworlds-server-main/data/buildables-test.xml
 delete mode 100644 legacyworlds-server-main/data/techs-test.xml

diff --git a/legacyworlds-server-main/data/buildables-test.xml b/legacyworlds-server-main/data/buildables-test.xml
deleted file mode 100644
index 69bc47a..0000000
--- a/legacyworlds-server-main/data/buildables-test.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<buildables xmlns="http://www.deepclone.com/lw/b6/m1/buildables" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/buildables buildables.xsd">
-
-	<building name="milFactory" description="milFactoryDescription" type="WORK" output="5" workers="200">
-		<cost build="100" upkeep="10" work="4800" />
-	</building>
-	<building name="turret" description="turretDescription" type="DEF" output="10" workers="5">
-		<cost build="40" upkeep="2" work="600" />
-	</building>
-	<building name="indFactory" description="indFactoryDescription" type="CASH" output="4" workers="500">
-		<cost build="500" upkeep="20" work="28800" />
-		<tech name="civTech" level="1" />
-	</building>
-	<building name="reanimationCentre" description="reanimationCentreDescription" type="POP" output="1" workers="300">
-		<cost build="4000" upkeep="200" work="57600" />
-		<tech name="civTech" level="2" />
-	</building>
-	<building name="superTurret" description="superTurretDescription" type="DEF" output="500" workers="1">
-		<cost build="4000" upkeep="10" work="20000" />
-		<tech name="civTech" level="3" />
-	</building>
-
-	<ship name="fighter" description="fighterDescription" time="3" power="10">
-		<cost build="100" upkeep="20" work="100" />
-	</ship>
-	<ship name="cruiser" description="cruiserDescription" time="5" power="100">
-		<cost build="500" upkeep="80" work="500" />
-		<tech name="milTech" level="1" />
-	</ship>
-	<ship name="bCruiser" description="bCruiserDescription" time="4" power="335">
-		<cost build="2500" upkeep="320" work="2500" />
-		<tech name="milTech" level="2" />
-	</ship>
-	<ship name="dreadnought" description="dreadnoughtDescription" time="6" power="5000">
-		<cost build="12500" upkeep="1280" work="12500" />
-		<tech name="milTech" level="3" />
-	</ship>
-
-</buildables>
\ No newline at end of file
diff --git a/legacyworlds-server-main/data/techs-test.xml b/legacyworlds-server-main/data/techs-test.xml
deleted file mode 100644
index f2b8f44..0000000
--- a/legacyworlds-server-main/data/techs-test.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<technologies xmlns="http://www.deepclone.com/lw/b6/m1/techs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/techs techs.xsd">
-
-	<tech-line name="milTech" description="milTechDescription">
-		<level name="cruisersTech" description="cruisersTechDescription"
-			points="5000" cost="10000" />
-		<level name="bCruisersTech" description="bCruisersTechDescription"
-			points="20000" cost="30000" />
-		<level name="dreadnoughtsTech" description="dreadnoughtsTechDescription"
-			points="48000" cost="100000" />
-	</tech-line>
-
-	<tech-line name="civTech" description="civTechDescription">
-		<level name="indFactTech" description="indFactTechDescription"
-			points="2000" cost="5000" />
-		<level name="reanimationTech" description="reanimationTechDescription"
-			points="10000" cost="25000" />
-		<level name="superTurretTech" description="superTurretTechDescription"
-			points="30000" cost="125000" />
-	</tech-line>
-
-</technologies>
\ No newline at end of file

From 1f3c7a920257ad940201fb53ebbb6c15363f0df1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 27 Feb 2012 20:04:02 +0100
Subject: [PATCH 61/94] Technology definitions loader

* Added "dummy" data file for technologies (for now it simply copies the
old, line-based technologies) and corresponding XML schema

* Added missing SQL stored procedure to clear all dependencies and
reverse dependencies from a technology

* Added import classes, loader and import tool for the technology graph

* Added tech graph import tool to post-build tests
---
 build/post-build.d/20-import-tools.sh         |   1 +
 .../parts/040-functions/030-tech.sql          |  41 +++
 .../030-tech/060-techdep-clear.sql            |  58 +++
 .../030-tech/040-techdep-clear.sql            |  13 +
 legacyworlds-server-main/data/tech-graph.xml  |  42 +++
 legacyworlds-server-main/data/tech-graph.xsd  |  35 ++
 .../com/deepclone/lw/cli/ImportTechGraph.java | 326 +++++++++++++++++
 .../lw/cli/xmlimport/TechnologyLoader.java    | 116 ++++++
 .../data/resources/BasicResource.java         |   4 +-
 .../xmlimport/data/techs/Technologies.java    |  98 ++++++
 .../cli/xmlimport/data/techs/Technology.java  | 183 ++++++++++
 .../techs/TechnologyDefinitionResult.java     |  47 +++
 .../technology-loader/bad-contents.xml        |   6 +
 .../TestFiles/technology-loader/bad-data.xml  |   8 +
 .../TestFiles/technology-loader/bad-xml.xml   |   2 +
 .../TestFiles/technology-loader/good-data.xml |   9 +
 .../lw/cli/xmlimport/TestI18NLoader.java      |  25 +-
 .../lw/cli/xmlimport/TestResourceLoader.java  |  24 +-
 .../cli/xmlimport/TestTechnologyLoader.java   | 132 +++++++
 .../xmlimport/data/resources/BaseTest.java    |   2 +-
 .../lw/cli/xmlimport/data/techs/BaseTest.java | 175 ++++++++++
 .../data/techs/TestTechnologies.java          |  96 +++++
 .../xmlimport/data/techs/TestTechnology.java  | 329 ++++++++++++++++++
 legacyworlds/doc/local-deployment.txt         |   2 +
 24 files changed, 1731 insertions(+), 43 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/060-techdep-clear.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/040-techdep-clear.sql
 create mode 100644 legacyworlds-server-main/data/tech-graph.xml
 create mode 100644 legacyworlds-server-main/data/tech-graph.xsd
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechGraph.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/TechnologyLoader.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/Technologies.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/Technology.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/TechnologyDefinitionResult.java
 create mode 100644 legacyworlds-server-tests/TestFiles/technology-loader/bad-contents.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/technology-loader/bad-data.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/technology-loader/bad-xml.xml
 create mode 100644 legacyworlds-server-tests/TestFiles/technology-loader/good-data.xml
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestTechnologyLoader.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/BaseTest.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/TestTechnologies.java
 create mode 100644 legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/TestTechnology.java

diff --git a/build/post-build.d/20-import-tools.sh b/build/post-build.d/20-import-tools.sh
index 2b20f04..de69c46 100644
--- a/build/post-build.d/20-import-tools.sh
+++ b/build/post-build.d/20-import-tools.sh
@@ -33,6 +33,7 @@ EOF
 java legacyworlds-server-main-*.jar --run-tool ImportText data/i18n-text.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportResources data/resources.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportTechs data/techs.xml || exit 1
+java legacyworlds-server-main-*.jar --run-tool ImportTechGraph data/tech-graph.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportBuildables data/buildables.xml || exit 1
 
 java legacyworlds-server-main-*.jar &
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
index 5b2e90b..2a1f4be 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
@@ -324,6 +324,47 @@ GRANT EXECUTE
 
 
 
+/*
+ * Remove all dependencies for a technology
+ * -----------------------------------------
+ * 
+ * This stored procedure removes all dependencies and reverse dependencies for
+ * some technology.
+ * 
+ * Parameters:
+ *		_technology		The name of the technology
+ */
+DROP FUNCTION IF EXISTS defs.techdep_clear( TEXT );
+CREATE FUNCTION defs.techdep_clear( _technology TEXT )
+	RETURNS VOID
+	LANGUAGE SQL
+	STRICT VOLATILE
+	SECURITY DEFINER
+AS $techdep_clear$
+
+	DELETE FROM defs.technology_dependencies
+		WHERE technology_name_id = (
+				SELECT id FROM defs.strings
+					WHERE name = $1
+			);
+
+	DELETE FROM defs.technology_dependencies
+		WHERE technology_name_id_depends = (
+				SELECT id FROM defs.strings
+					WHERE name = $1
+			);
+
+$techdep_clear$;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.techdep_clear( TEXT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION defs.techdep_clear( TEXT )
+	TO :dbuser;
+
+
+
 -- ********************************************************
 -- OLD CODE BELOW
 -- ********************************************************
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/060-techdep-clear.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/060-techdep-clear.sql
new file mode 100644
index 0000000..ef2dacc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/060-techdep-clear.sql
@@ -0,0 +1,58 @@
+/*
+ * Test the defs.techdep_clear() function
+ */
+BEGIN;
+	\i utils/strings.sql
+	-- Make the columns we don't use in the technology definition table NULL-able
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+
+	-- Create strings to use as the technologies' names
+	SELECT _create_test_strings( 4 , 'tech' );
+	
+	-- Insert the technologies
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech3' ) ) ,
+				( _get_string( 'tech4' ) );
+
+	-- Add a chain of dependencies
+	INSERT INTO defs.technology_dependencies(
+			technology_name_id , technology_name_id_depends
+		) VALUES ( _get_string( 'tech2' ) , _get_string( 'tech1' ) ) ,
+			 ( _get_string( 'tech3' ) , _get_string( 'tech2' ) ) ,
+			 ( _get_string( 'tech4' ) , _get_string( 'tech3' ) );
+
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'defs.techdep_clear() - No failure on invalid technology name' );
+	SELECT lives_ok( $$
+		SELECT defs.techdep_clear( 'does not exist' )
+	$$ );
+
+	SELECT diag_test_name( 'defs.techdep_clear() - Successful call' );
+	SELECT lives_ok( $$
+		SELECT defs.techdep_clear( 'tech2' )
+	$$ );
+	
+	SELECT diag_test_name( 'defs.techdep_clear() - Cleared technology has no dependencies' );
+	SELECT is_empty( $$
+		SELECT * FROM defs.technology_dependencies
+			WHERE technology_name_id = _get_string( 'tech2' );
+	$$ );
+	
+	SELECT diag_test_name( 'defs.techdep_clear() - Cleared technology has no reverse dependencies' );
+	SELECT is_empty( $$
+		SELECT * FROM defs.technology_dependencies
+			WHERE technology_name_id_depends = _get_string( 'tech2' );
+	$$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/040-techdep-clear.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/040-techdep-clear.sql
new file mode 100644
index 0000000..fb9f70b
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/030-tech/040-techdep-clear.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on defs.techdep_remove()
+ */
+BEGIN;
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.techdep_clear() - EXECUTE privilege' );
+	SELECT lives_ok( $$
+		SELECT defs.techdep_clear( '' );
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-main/data/tech-graph.xml b/legacyworlds-server-main/data/tech-graph.xml
new file mode 100644
index 0000000..4f54016
--- /dev/null
+++ b/legacyworlds-server-main/data/tech-graph.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-tech-graph xmlns="http://www.deepclone.com/lw/b6/m2/tech-graph"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/tech-graph tech-graph.xsd">
+
+	<!-- Military technologies (cruisers, battle cruisers, dreadnoughts) -->
+
+	<technology name="cruisersTech" category="milTech"
+		description="cruisersTechDescription" discovery="cruisersTechDescription"
+		points="25000" cost="10000" />
+
+	<technology name="bCruisersTech" category="milTech"
+		description="bCruisersTechDescription" discovery="bCruisersTechDescription"
+		points="900000" cost="400000">
+		<depends-on>cruisersTech</depends-on>
+	</technology>
+
+	<technology name="dreadnoughtsTech" category="milTech"
+		description="dreadnoughtsTechDescription" discovery="dreadnoughtsTechDescription"
+		points="2250000" cost="1012500">
+		<depends-on>bCruisersTech</depends-on>
+	</technology>
+
+	<!-- Civilian technologies (industrial factories, corpse re-animation, bio-turrets -->
+
+	<technology name="indFactTech" category="civTech"
+		description="indFactTechDescription" discovery="indFactTechDescription"
+		points="10000" cost="5000" />
+
+	<technology name="reanimationTech" category="civTech"
+		description="reanimationTechDescription" discovery="reanimationTechDescription"
+		points="562500" cost="281250">
+		<depends-on>indFactTech</depends-on>
+	</technology>
+
+	<technology name="superTurretTech" category="civTech"
+		description="superTurretTechDescription" discovery="superTurretTechDescription"
+		points="1350000" cost="607500">
+		<depends-on>reanimationTech</depends-on>
+	</technology>
+
+</lw-tech-graph>
\ No newline at end of file
diff --git a/legacyworlds-server-main/data/tech-graph.xsd b/legacyworlds-server-main/data/tech-graph.xsd
new file mode 100644
index 0000000..4c3b555
--- /dev/null
+++ b/legacyworlds-server-main/data/tech-graph.xsd
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns="http://www.deepclone.com/lw/b6/m2/tech-graph"
+	targetNamespace="http://www.deepclone.com/lw/b6/m2/tech-graph"
+	xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
+	attributeFormDefault="unqualified">
+
+	<xs:element name="lw-tech-graph">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="technology" type="technology-description"
+					minOccurs="1" maxOccurs="unbounded">
+				</xs:element>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:simpleType name="positive-long">
+		<xs:restriction base="xs:long">
+			<xs:minExclusive value="0"></xs:minExclusive>
+		</xs:restriction>
+	</xs:simpleType>
+
+	<xs:complexType name="technology-description">
+		<xs:sequence>
+			<xs:element name="depends-on" type="xs:token" minOccurs="0"
+				maxOccurs="unbounded" />
+		</xs:sequence>
+		<xs:attribute name="name" use="required" type="xs:token" />
+		<xs:attribute name="category" use="required" type="xs:token" />
+		<xs:attribute name="description" use="required" type="xs:token" />
+		<xs:attribute name="discovery" use="required" type="xs:token" />
+		<xs:attribute name="cost" use="required" type="positive-long" />
+		<xs:attribute name="points" use="required" type="positive-long" />
+	</xs:complexType>
+</xs:schema>
\ No newline at end of file
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechGraph.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechGraph.java
new file mode 100644
index 0000000..c912c05
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechGraph.java
@@ -0,0 +1,326 @@
+package com.deepclone.lw.cli;
+
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.log4j.Logger;
+import org.postgresql.util.PGobject;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.FileSystemXmlApplicationContext;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.cli.xmlimport.TechnologyLoader;
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.techs.Technologies;
+import com.deepclone.lw.cli.xmlimport.data.techs.Technology;
+import com.deepclone.lw.cli.xmlimport.data.techs.TechnologyDefinitionResult;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Technology graph import tool
+ * 
+ * <p>
+ * This class defines the body of the technology graph import tool. It loads the data file specified
+ * on the command line, validates it and then loads the technology definitions and dependencies into
+ * the database.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class ImportTechGraph
+		extends CLITool
+{
+	/** Logging system */
+	private final Logger logger = Logger.getLogger( ImportTechGraph.class );
+
+	/** File to read the definitions from */
+	private File file;
+
+	/** Spring transaction template */
+	private TransactionTemplate tTemplate;
+
+	/** Technology creation or update stored procedure */
+	private StoredProc fUocTechnology;
+
+	/** Stored procedure that removes all dependencies from a technology */
+	private StoredProc fTechdepClear;
+
+	/** Stored procedure that adds a dependency to a technology */
+	private StoredProc fTechdepAdd;
+
+
+	/**
+	 * Create the Spring context
+	 * 
+	 * <p>
+	 * Load the definition of the data source as a Spring context, then adds the transaction
+	 * management component.
+	 * 
+	 * @return the Spring application context
+	 */
+	private ClassPathXmlApplicationContext createContext( )
+	{
+		FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( new String[] {
+			this.getDataSource( )
+		} );
+		ctx.refresh( );
+
+		return new ClassPathXmlApplicationContext( new String[] {
+			"configuration/transactions.xml"
+		} , true , ctx );
+	}
+
+
+	/**
+	 * Create database access templates
+	 * 
+	 * <p>
+	 * Initialise the transaction template and the stored procedure wrappers.
+	 * 
+	 * @param ctx
+	 *            the Spring application context
+	 */
+	private void createTemplates( ApplicationContext ctx )
+	{
+		DataSource dSource = ctx.getBean( DataSource.class );
+		PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
+
+		this.fUocTechnology = new StoredProc( dSource , "defs" , "uoc_technology" )
+				.addParameter( "_name" , java.sql.Types.VARCHAR ).addParameter( "_category" , java.sql.Types.VARCHAR )
+				.addParameter( "_discovery" , java.sql.Types.VARCHAR )
+				.addParameter( "_description" , java.sql.Types.VARCHAR )
+				.addParameter( "_price" , java.sql.Types.BIGINT ).addParameter( "_points" , java.sql.Types.BIGINT )
+				.addOutput( "_result" , java.sql.Types.OTHER );
+
+		this.fTechdepClear = new StoredProc( dSource , "defs" , "techdep_clear" ).addParameter( "_tech" ,
+				java.sql.Types.VARCHAR );
+
+		this.fTechdepAdd = new StoredProc( dSource , "defs" , "techdep_add" )
+				.addParameter( "_dependent" , java.sql.Types.VARCHAR )
+				.addParameter( "_dependency" , java.sql.Types.VARCHAR ).addOutput( "_result" , java.sql.Types.OTHER );
+
+		this.tTemplate = new TransactionTemplate( tManager );
+	}
+
+
+	/**
+	 * Import all technology definitions
+	 * 
+	 * <p>
+	 * Import all technology definitions from the top-level {@link Technologies}, creating a map of
+	 * dependencies. Once all definitions have been imported, add all dependencies.
+	 * 
+	 * @param data
+	 *            the top level {@link Technologies} data instance
+	 * 
+	 * @throws DataImportException
+	 *             when some technology definition or dependency cannot be imported
+	 */
+	private void importTechnologies( Technologies data )
+			throws DataImportException
+	{
+		Map< String , List< String > > dependencies = new HashMap< String , List< String > >( );
+		for ( Technology technology : data ) {
+			this.importTechnologyDefinition( technology );
+			dependencies.put( technology.getName( ) , technology.getDependencies( ) );
+		}
+
+		for ( Map.Entry< String , List< String > > techDeps : dependencies.entrySet( ) ) {
+			for ( String dependency : techDeps.getValue( ) ) {
+				this.importDependency( techDeps.getKey( ) , dependency );
+			}
+		}
+	}
+
+
+	/**
+	 * Import a technology definition
+	 * 
+	 * <p>
+	 * Import the definition, and if there was already a definition by that name, clear its
+	 * dependencies.
+	 * 
+	 * @param technology
+	 *            the definition to import
+	 * @throws DataImportException
+	 *             when the update or creation stored procedure returns anything other than
+	 *             <code>CREATED</code> or <code>UPDATED</code>
+	 */
+	private void importTechnologyDefinition( Technology technology )
+			throws DataImportException
+	{
+		this.logger.info( "Importing technology '" + technology.getName( ) + "'" );
+
+		TechnologyDefinitionResult result = this.getResultOf( this.fUocTechnology.execute( technology.getName( ) ,
+				technology.getCategory( ) , technology.getDiscovery( ) , technology.getDescription( ) ,
+				technology.getCost( ) , technology.getPoints( ) ) );
+		if ( result == TechnologyDefinitionResult.UPDATED ) {
+			this.fTechdepClear.execute( technology.getName( ) );
+		} else if ( result != TechnologyDefinitionResult.CREATED ) {
+			throw new DataImportException( "Error while importing technology '" + technology.getName( ) + "': "
+					+ result );
+		}
+	}
+
+
+	/**
+	 * Import a dependency between two technologies
+	 * 
+	 * <p>
+	 * Attempt to add a dependency from a technology to another.
+	 * 
+	 * @param dependent
+	 *            the dependent technology
+	 * @param dependency
+	 *            the technology to depend on
+	 * 
+	 * @throws DataImportException
+	 *             if dependency creation fails
+	 */
+	private void importDependency( String dependent , String dependency )
+			throws DataImportException
+	{
+		this.logger.info( "Importing dependency from " + dependent + " to " + dependency );
+
+		TechnologyDefinitionResult result = this.getResultOf( this.fTechdepAdd.execute( dependent , dependency ) );
+		switch ( result ) {
+			case CREATED:
+				return;
+			case REDUNDANT:
+				throw new DataImportException( "( " + dependent + " -> " + dependency + " ) is redundant" );
+			case BAD_STRINGS:
+				throw new DataImportException( "( " + dependent + " -> " + dependency + " ): undefined technology" );
+			case CYCLE:
+				throw new DataImportException( "( " + dependent + " -> " + dependency + " ) would create a cycle" );
+			default:
+				throw new DataImportException( "( " + dependent + " -> " + dependency + " ): unexpected error code "
+						+ result );
+		}
+	}
+
+
+	/**
+	 * Helper method for stored procedure results
+	 * 
+	 * <p>
+	 * This method converts the map returned by one of the definition stored procedures into a
+	 * {@link TechnologyDefinitionResult} value.
+	 * 
+	 * @param spResult
+	 *            the return value of {@link StoredProc#execute(Object...)}
+	 * 
+	 * @return the converted value of the _result field
+	 */
+	private TechnologyDefinitionResult getResultOf( Map< String , Object > spResult )
+	{
+		return TechnologyDefinitionResult.valueOf( ( (PGobject) spResult.get( "_result" ) ).getValue( ) );
+	}
+
+
+	/**
+	 * Run the technology definitions import tool
+	 * 
+	 * <p>
+	 * Loads the data file, the connects to the database and creates or updates all definitions.
+	 */
+	@Override
+	public void run( )
+	{
+		Technologies data;
+		try {
+			data = new TechnologyLoader( this.file ).load( );
+		} catch ( DataImportException e ) {
+			this.logger.error( "Error while loading technology definitions" , e );
+			return;
+		}
+
+		AbstractApplicationContext ctx = this.createContext( );
+		this.createTemplates( ctx );
+		this.executeImportTransaction( data );
+		ToolBase.destroyContext( ctx );
+	}
+
+
+	/**
+	 * Execute the technology definitions importation transaction
+	 * 
+	 * <p>
+	 * Run a transaction and execute the importation code inside it. Roll back if anything goes
+	 * wrong.
+	 * 
+	 * @param data
+	 *            the {@link Technologies} definitions instance
+	 */
+	private void executeImportTransaction( final Technologies data )
+	{
+		boolean rv = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
+			@Override
+			public Boolean doInTransaction( TransactionStatus status )
+			{
+				boolean rv = ImportTechGraph.this.doTransaction( data );
+				if ( !rv ) {
+					status.setRollbackOnly( );
+				}
+				return rv;
+			}
+		} );
+		if ( rv ) {
+			this.logger.info( "Technology import successful" );
+		}
+	}
+
+
+	/**
+	 * Import transaction body
+	 * 
+	 * <p>
+	 * Import all definitions and handle exceptions.
+	 * 
+	 * @param data
+	 *            the {@link Technologies} definitions instance
+	 * @return
+	 */
+	private boolean doTransaction( Technologies data )
+	{
+		try {
+			this.importTechnologies( data );
+			return true;
+		} catch ( RuntimeException e ) {
+			this.logger.error( "Caught runtime exception" , e );
+		} catch ( DataImportException e ) {
+			this.logger.error( e.getMessage( ) );
+		}
+		return false;
+	}
+
+
+	/**
+	 * Obtain the name of the definitions file
+	 * 
+	 * <p>
+	 * Check the command line options, setting the definitions file accordingly.
+	 */
+	@Override
+	public boolean setOptions( String... options )
+	{
+		if ( options.length != 1 ) {
+			return false;
+		}
+		this.file = new File( options[ 0 ] );
+		if ( ! ( this.file.isFile( ) && this.file.canRead( ) ) ) {
+			return false;
+		}
+		return true;
+	}
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/TechnologyLoader.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/TechnologyLoader.java
new file mode 100644
index 0000000..275a7cf
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/TechnologyLoader.java
@@ -0,0 +1,116 @@
+package com.deepclone.lw.cli.xmlimport;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.techs.Technologies;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.XStreamException;
+
+
+
+/**
+ * Technology Loader
+ * 
+ * <p>
+ * This class can be used to load all technology definitions. It extracts them from the XML file and
+ * verifies them.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TechnologyLoader
+{
+	/** The file to read the XML from */
+	private final File file;
+
+
+	/**
+	 * Initialise the loader
+	 * 
+	 * @param file
+	 *            the XML file that contains the definitions
+	 */
+	public TechnologyLoader( File file )
+	{
+		this.file = file.getAbsoluteFile( );
+	}
+
+
+	/**
+	 * Initialise the necessary XStream instance
+	 * 
+	 * <p>
+	 * Initialise the XStream instance by processing the annotations of all the technology
+	 * importable data classes.
+	 * 
+	 * @return the XStream instance to use when loading the data
+	 */
+	private XStream initXStream( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( Technologies.class );
+		return xstream;
+	}
+
+
+	/**
+	 * Load the technology definitions
+	 * 
+	 * <p>
+	 * Load the XML file and process the definitions using XStream.
+	 * 
+	 * @return the top-level importable data instance
+	 * 
+	 * @throws DataImportException
+	 *             if reading from the file or parsing its contents fail
+	 */
+	private Technologies loadXMLFile( )
+			throws DataImportException
+	{
+		FileInputStream fis;
+		try {
+			fis = new FileInputStream( this.file );
+		} catch ( FileNotFoundException e ) {
+			throw new DataImportException( "Unable to load technology definitions" , e );
+		}
+
+		try {
+			try {
+				XStream xstream = this.initXStream( );
+				return (Technologies) xstream.fromXML( fis );
+			} finally {
+				fis.close( );
+			}
+		} catch ( IOException e ) {
+			throw new DataImportException( "Input error while loading technology definitions" , e );
+		} catch ( XStreamException e ) {
+			throw new DataImportException( "XML error while loading technology definitions" , e );
+		}
+	}
+
+
+	/**
+	 * Load and process technology definitions
+	 * 
+	 * <p>
+	 * Attempt to load all technology definitions, ensure they are valid, then set the original
+	 * file's path.
+	 * 
+	 * @return the top-level importable data instance
+	 * 
+	 * @throws DataImportException
+	 *             if loading or verifying the data fails
+	 */
+	public Technologies load( )
+			throws DataImportException
+	{
+		Technologies techs = this.loadXMLFile( );
+		techs.verifyData( );
+		techs.setReadFrom( this.file );
+		return techs;
+	}
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/BasicResource.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/BasicResource.java
index 551ff47..86a8442 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/BasicResource.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/resources/BasicResource.java
@@ -64,9 +64,9 @@ public class BasicResource
 			throw new DataImportException( "Missing name string" );
 		}
 		if ( this.description == null || "".equals( this.description.trim( ) ) ) {
-			throw new DataImportException( "Missing name string" );
+			throw new DataImportException( "Missing description string" );
 		}
-		if ( this.category != null && "".equals( this.description.trim( ) ) ) {
+		if ( this.category != null && "".equals( this.category.trim( ) ) ) {
 			throw new DataImportException( "Category invalid" );
 		}
 		if ( this.weight == null ) {
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/Technologies.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/Technologies.java
new file mode 100644
index 0000000..842f64c
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/Technologies.java
@@ -0,0 +1,98 @@
+package com.deepclone.lw.cli.xmlimport.data.techs;
+
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+/**
+ * Technology definitions data
+ * 
+ * <p>
+ * This class represents the contents of a technology definition XML file. It contains a list of
+ * technology definitions.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "lw-tech-graph" )
+public class Technologies
+		extends ImportableData
+		implements Iterable< Technology >
+{
+	/** All present technology definitions */
+	@XStreamImplicit( itemFieldName = "technology" )
+	private final List< Technology > technologies = new LinkedList< Technology >( );
+
+
+	/**
+	 * Check the technology data
+	 * 
+	 * <p>
+	 * Check whether there is a list of technologies, then check each definition to make sure that
+	 * it is valid and that its name is unique.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		if ( this.technologies == null ) {
+			throw new DataImportException( "No technology definitions" );
+		}
+
+		HashSet< String > names = new HashSet< String >( );
+		for ( Technology tech : this.technologies ) {
+			tech.verifyData( );
+			this.checkUniqueItem( names , tech.getName( ) );
+		}
+	}
+
+
+	/**
+	 * Checks that the name of a technology is unique
+	 * 
+	 * <p>
+	 * This helper method is used by {@link #verifyData()} to make sure technology names are unique.
+	 * 
+	 * @param existing
+	 *            the existing set of names
+	 * @param value
+	 *            the name to check
+	 * 
+	 * @throws DataImportException
+	 *             if the name is already present in the set of existing names
+	 */
+	public void checkUniqueItem( HashSet< String > existing , String value )
+			throws DataImportException
+	{
+		if ( existing.contains( value ) ) {
+			throw new DataImportException( "Duplicate technology name '" + value + "'" );
+		}
+		existing.add( value );
+	}
+
+
+	/**
+	 * Technology definition iterator
+	 * 
+	 * <p>
+	 * Grant access to the list of technologies in read-only mode.
+	 * 
+	 * @return a read-only iterator on the list of technologies.
+	 */
+	@Override
+	public Iterator< Technology > iterator( )
+	{
+		return Collections.unmodifiableList( this.technologies ).iterator( );
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/Technology.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/Technology.java
new file mode 100644
index 0000000..49b6368
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/Technology.java
@@ -0,0 +1,183 @@
+package com.deepclone.lw.cli.xmlimport.data.techs;
+
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+/**
+ * A technology definition
+ * 
+ * <p>
+ * This class represents a technology definition that can be imported from the data file. It
+ * contains all required technology attributes, as well as a list of strings for the dependencies.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+public class Technology
+		extends ImportableData
+{
+
+	/** Identifier of the name string */
+	@XStreamAsAttribute
+	private String name;
+
+	/** Identifier of the category string */
+	@XStreamAsAttribute
+	private String category;
+
+	/** Identifier of the description string */
+	@XStreamAsAttribute
+	private String description;
+
+	/** Identifier of the string to display on technology discovery */
+	@XStreamAsAttribute
+	private String discovery;
+
+	/** Monetary cost of the technology */
+	@XStreamAsAttribute
+	private Long cost;
+
+	/** Required amount of research points */
+	@XStreamAsAttribute
+	private Long points;
+
+	/** List of technology name string identifiers describing the dependencies */
+	@XStreamImplicit( itemFieldName = "depends-on" )
+	private final List< String > dependencies = new LinkedList< String >( );
+
+
+	/**
+	 * Technology definition verifier
+	 * 
+	 * <p>
+	 * Make sure that all technology definition fields are both present and valid. If dependencies
+	 * exist, also verify the names' validity.
+	 */
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		if ( this.name == null || "".equals( this.name.trim( ) ) ) {
+			throw new DataImportException( "Missing name string" );
+		}
+		if ( this.category == null || "".equals( this.category.trim( ) ) ) {
+			throw new DataImportException( "Missing category string in " + this.name );
+		}
+		if ( this.description == null || "".equals( this.description.trim( ) ) ) {
+			throw new DataImportException( "Missing description string in " + this.name );
+		}
+		if ( this.discovery == null || "".equals( this.discovery.trim( ) ) ) {
+			throw new DataImportException( "Missing discovery string in " + this.name );
+		}
+
+		if ( this.points == null ) {
+			throw new DataImportException( "Missing research points in " + this.name );
+		} else if ( this.points <= 0 ) {
+			throw new DataImportException( "Invalid research points in " + this.name );
+		}
+
+		if ( this.cost == null ) {
+			throw new DataImportException( "Missing cost in " + this.name );
+		} else if ( this.cost <= 0 ) {
+			throw new DataImportException( "Invalid cost in " + this.name );
+		}
+
+		if ( this.dependencies != null ) {
+			for ( String dep : this.dependencies ) {
+				if ( dep == null || "".equals( dep.trim( ) ) ) {
+					throw new DataImportException( "Empty dependency name in " + this.name );
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * Gets the identifier of the name string.
+	 * 
+	 * @return the identifier of the name string
+	 */
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	/**
+	 * Gets the identifier of the category string.
+	 * 
+	 * @return the identifier of the category string
+	 */
+	public String getCategory( )
+	{
+		return this.category;
+	}
+
+
+	/**
+	 * Gets the identifier of the description string.
+	 * 
+	 * @return the identifier of the description string
+	 */
+	public String getDescription( )
+	{
+		return this.description;
+	}
+
+
+	/**
+	 * Gets the identifier of the string to display on technology discovery.
+	 * 
+	 * @return the identifier of the string to display on technology discovery
+	 */
+	public String getDiscovery( )
+	{
+		return this.discovery;
+	}
+
+
+	/**
+	 * Gets the monetary cost of the technology.
+	 * 
+	 * @return the monetary cost of the technology
+	 */
+	public Long getCost( )
+	{
+		return this.cost;
+	}
+
+
+	/**
+	 * Gets the required amount of research points.
+	 * 
+	 * @return the required amount of research points
+	 */
+	public Long getPoints( )
+	{
+		return this.points;
+	}
+
+
+	/**
+	 * Gets the list of technology name string identifiers describing the dependencies.
+	 * 
+	 * @return the list of technology name string identifiers describing the dependencies
+	 */
+	public List< String > getDependencies( )
+	{
+		if ( this.dependencies == null ) {
+			return Collections.emptyList( );
+		}
+		return Collections.unmodifiableList( this.dependencies );
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/TechnologyDefinitionResult.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/TechnologyDefinitionResult.java
new file mode 100644
index 0000000..e7491bb
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/techs/TechnologyDefinitionResult.java
@@ -0,0 +1,47 @@
+package com.deepclone.lw.cli.xmlimport.data.techs;
+
+
+/**
+ * Return codes for technology definition manipulation stored procedures
+ * 
+ * <p>
+ * This enumeration represents the various values which can be returned by the stored procedures
+ * which manipulate technology definitions and technology dependencies.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public enum TechnologyDefinitionResult {
+
+	/** The technology definition or dependency was created */
+	CREATED ,
+
+	/** The technology definition was updated */
+	UPDATED ,
+
+	/** The dependency was deleted */
+	DELETED ,
+
+	/** The specified dependency does not exist */
+	MISSING ,
+
+	/** One (or more) of the numeric parameters is invalid */
+	BAD_VALUE ,
+
+	/**
+	 * The name, description, discovery or category string identifiers were not valid string
+	 * identifiers.
+	 */
+	BAD_STRINGS ,
+
+	/**
+	 * The specified description and/or discovery string was in use by another technology.
+	 */
+	DUP_STRING ,
+
+	/** The dependency would cause a cycle */
+	CYCLE ,
+
+	/** The dependency would be redundant */
+	REDUNDANT
+
+}
diff --git a/legacyworlds-server-tests/TestFiles/technology-loader/bad-contents.xml b/legacyworlds-server-tests/TestFiles/technology-loader/bad-contents.xml
new file mode 100644
index 0000000..272dba0
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/technology-loader/bad-contents.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-tech-graph>
+
+	<does-not-exist />
+
+</lw-tech-graph>
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/technology-loader/bad-data.xml b/legacyworlds-server-tests/TestFiles/technology-loader/bad-data.xml
new file mode 100644
index 0000000..584e660
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/technology-loader/bad-data.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-tech-graph xmlns="http://www.deepclone.com/lw/b6/m2/tech-graph"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/tech-graph tech-graph.xsd">
+
+	<technology />
+
+</lw-tech-graph>
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/technology-loader/bad-xml.xml b/legacyworlds-server-tests/TestFiles/technology-loader/bad-xml.xml
new file mode 100644
index 0000000..ee1d0ed
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/technology-loader/bad-xml.xml
@@ -0,0 +1,2 @@
+This is not an XML file, obviously.
+We'll make that even more confusing: <<<<<< & >>!!!
\ No newline at end of file
diff --git a/legacyworlds-server-tests/TestFiles/technology-loader/good-data.xml b/legacyworlds-server-tests/TestFiles/technology-loader/good-data.xml
new file mode 100644
index 0000000..0cf7e8e
--- /dev/null
+++ b/legacyworlds-server-tests/TestFiles/technology-loader/good-data.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-tech-graph xmlns="http://www.deepclone.com/lw/b6/m2/tech-graph"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/tech-graph tech-graph.xsd">
+
+	<technology name="test" category="test" discovery="test"
+		description="test" cost="12" points="34" />
+
+</lw-tech-graph>
\ No newline at end of file
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestI18NLoader.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestI18NLoader.java
index 0b2ac67..a44feed 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestI18NLoader.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestI18NLoader.java
@@ -1,7 +1,11 @@
 package com.deepclone.lw.cli.xmlimport;
 
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.io.IOException;
@@ -11,7 +15,6 @@ import org.junit.Test;
 import com.deepclone.lw.cli.xmlimport.data.DataImportException;
 import com.deepclone.lw.cli.xmlimport.data.i18n.I18NText;
 import com.deepclone.lw.cli.xmlimport.data.i18n.LanguageDefinition;
-import com.deepclone.lw.cli.xmlimport.data.i18n.StringDefinition;
 import com.thoughtworks.xstream.converters.ConversionException;
 import com.thoughtworks.xstream.io.StreamException;
 
@@ -129,22 +132,8 @@ public class TestI18NLoader
 		assertNotNull( text );
 
 		int lCount = 0;
-		for ( LanguageDefinition ld : text ) {
-			assertEquals( "test" , ld.getId( ) );
-			assertEquals( "test" , ld.getName( ) );
-
-			int tCount = 0;
-			for ( StringDefinition sd : ld ) {
-				assertEquals( "test" , sd.getId( ) );
-				try {
-					assertEquals( "test" , sd.getString( ).trim( ) );
-				} catch ( DataImportException e ) {
-					fail( "could not load string: " + e.getMessage( ) );
-				}
-				tCount++;
-			}
-
-			assertEquals( 1 , tCount );
+		for ( @SuppressWarnings( "unused" )
+		LanguageDefinition ld : text ) {
 			lCount++;
 		}
 		assertEquals( 1 , lCount );
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestResourceLoader.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestResourceLoader.java
index d49210d..def4308 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestResourceLoader.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestResourceLoader.java
@@ -14,7 +14,6 @@ import org.junit.Test;
 
 import com.deepclone.lw.cli.xmlimport.data.DataImportException;
 import com.deepclone.lw.cli.xmlimport.data.resources.BasicResource;
-import com.deepclone.lw.cli.xmlimport.data.resources.NaturalResource;
 import com.deepclone.lw.cli.xmlimport.data.resources.Resources;
 import com.thoughtworks.xstream.converters.ConversionException;
 import com.thoughtworks.xstream.io.StreamException;
@@ -127,27 +126,8 @@ public class TestResourceLoader
 		// Not sure if this is the best way to code for the two different resource types...
 		int rCount = 0;
 
-		for ( BasicResource br : resources ) {
-			if ( rCount == 0 ) {
-				assertEquals( "money" , br.getName( ) );
-				assertEquals( "moneyDescription" , br.getDescription( ) );
-				assertEquals( new Integer( 0 ) , br.getWeight( ) );
-				assertEquals( null , br.getCategory( ) );
-			} else if ( rCount == 1 ) {
-				// This isn't retarded is it?
-				NaturalResource nr = (NaturalResource) br;
-				assertEquals( "titanium" , nr.getName( ) );
-				assertEquals( "titaniumDescription" , nr.getDescription( ) );
-				assertEquals( new Integer( 1 ) , nr.getWeight( ) );
-				assertEquals( "minerals" , nr.getCategory( ) );
-				assertEquals( new Double( 0.8 ) , nr.getPresenceProbability( ) );
-				assertEquals( new Double( 5000 ) , nr.getQuantity( ).getAverage( ) );
-				assertEquals( new Double( 1500 ) , nr.getQuantity( ).getDeviation( ) );
-				assertEquals( new Double( 0.1 ) , nr.getDifficulty( ).getAverage( ) );
-				assertEquals( new Double( 0.05 ) , nr.getDifficulty( ).getDeviation( ) );
-				assertEquals( new Double( 0.4 ) , nr.getRecovery( ).getAverage( ) );
-				assertEquals( new Double( 0.05 ) , nr.getRecovery( ).getDeviation( ) );
-			}
+		for ( @SuppressWarnings( "unused" )
+		BasicResource br : resources ) {
 			rCount++;
 		}
 		assertEquals( 2 , rCount );
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestTechnologyLoader.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestTechnologyLoader.java
new file mode 100644
index 0000000..ee24fc4
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/TestTechnologyLoader.java
@@ -0,0 +1,132 @@
+package com.deepclone.lw.cli.xmlimport;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.techs.Technologies;
+import com.deepclone.lw.cli.xmlimport.data.techs.Technology;
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.io.StreamException;
+
+
+
+/**
+ * Unit tests for the {@link TechnologyLoader} class.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestTechnologyLoader
+{
+	/**
+	 * Try initialising the loader with a <code>null</code> file instance.
+	 * 
+	 * @throws NullPointerException
+	 *             when the constructor is executed
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testNullFile( )
+			throws NullPointerException
+	{
+		new TechnologyLoader( null );
+	}
+
+
+	/** Try loading a file that does not exist */
+	@Test
+	public void testMissingFile( )
+	{
+		TechnologyLoader loader = new TechnologyLoader( new File( "does-not-exist" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a" + e.getCause( ).getClass( ).getName( ) , e.getCause( ) instanceof IOException );
+			return;
+		}
+		fail( "no exception after trying to load a missing file" );
+	}
+
+
+	/** Try loading a file that contains something that is not XML. */
+	@Test
+	public void testBadXML( )
+	{
+		TechnologyLoader loader = new TechnologyLoader( new File( "TestFiles/technology-loader/bad-xml.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a " + e.getCause( ).getClass( ).getName( ) , e.getCause( ) instanceof StreamException );
+			return;
+		}
+		fail( "no exception after loading stuff that isn't XML" );
+	}
+
+
+	/**
+	 * Test loading a file that contains XML but which cannot be deserialised to a
+	 * {@link Technologies} instance.
+	 */
+	@Test
+	public void testBadContents( )
+	{
+		TechnologyLoader loader = new TechnologyLoader( new File( "TestFiles/technology-loader/bad-contents.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertTrue( "cause is a " + e.getCause( ).getClass( ).getName( ) ,
+					e.getCause( ) instanceof ConversionException );
+			return;
+		}
+		fail( "no exception after loading bad XML" );
+	}
+
+
+	/**
+	 * Try loading a file that contains valid XML for a {@link Technologies} instance with semantic
+	 * errors.
+	 */
+	@Test
+	public void testBadData( )
+	{
+		TechnologyLoader loader = new TechnologyLoader( new File( "TestFiles/technology-loader/bad-data.xml" ) );
+		try {
+			loader.load( );
+		} catch ( DataImportException e ) {
+			assertNull( e.getCause( ) );
+			return;
+		}
+		fail( "no exception after loading bad data" );
+	}
+
+
+	/** Try loading valid data, make sure that it contains one record */
+	@Test
+	public void testGoodData( )
+	{
+		TechnologyLoader loader = new TechnologyLoader( new File( "TestFiles/technology-loader/good-data.xml" ) );
+		Technologies technologies;
+		try {
+			technologies = loader.load( );
+		} catch ( DataImportException e ) {
+			fail( "could not load valid file" );
+			return;
+		}
+		assertNotNull( technologies );
+
+		int rCount = 0;
+		for ( @SuppressWarnings( "unused" )
+		Technology tech : technologies ) {
+			rCount++;
+		}
+		assertEquals( 1 , rCount );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/BaseTest.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/BaseTest.java
index d578d51..9f2eb11 100644
--- a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/BaseTest.java
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/resources/BaseTest.java
@@ -22,7 +22,7 @@ import com.thoughtworks.xstream.XStreamException;
  * 
  */
 
-abstract public class BaseTest
+abstract class BaseTest
 {
 	/**
 	 * Escape &amp;, &lt; and &gt; in XML strings
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/BaseTest.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/BaseTest.java
new file mode 100644
index 0000000..2d70331
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/BaseTest.java
@@ -0,0 +1,175 @@
+package com.deepclone.lw.cli.xmlimport.data.techs;
+
+
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.XStreamException;
+
+
+
+/**
+ * Base class for testing technology importation
+ * 
+ * <p>
+ * This class is used as a parent for tests of the technology import structures. It includes the
+ * code used to actually create these technology definitions, as this can normally only be done
+ * through XStream.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+
+abstract class BaseTest
+{
+	/**
+	 * Escape &amp;, &lt; and &gt; in XML strings
+	 * 
+	 * @param string
+	 *            the string to escape
+	 * 
+	 * @return the escaped string
+	 */
+	private String xmlString( String string )
+	{
+		return string.replace( "&" , "&amp;" ).replace( "<" , "&lt;" ).replace( ">" , "&gt;" );
+	}
+
+
+	/**
+	 * Escape &amp;, &lt; and &gt;, ' and " in XML strings
+	 * 
+	 * @param string
+	 *            the string to escape
+	 * 
+	 * @return the escaped string
+	 */
+	private String quoteString( String string )
+	{
+		return "\"" + this.xmlString( string ).replace( "\"" , "&quot;" ).replace( "'" , "&apos;" ) + "\"";
+	}
+
+
+	/**
+	 * Create the XML code for a technology definition
+	 * 
+	 * <p>
+	 * This method generates the XML code for a technology definition, which can then be imported
+	 * using XStream into a {@link Technology} instance. It is capable of generating instances with
+	 * various fields set to <code>null</code>.
+	 * 
+	 * @param name
+	 *            identifier of the technology's name string
+	 * @param category
+	 *            identifier of the technology's category string
+	 * @param discovery
+	 *            identifier of the technology's discovery string
+	 * @param description
+	 *            identifier of the technology's description string
+	 * @param cost
+	 *            monetary cost of implementing the technology
+	 * @param points
+	 *            amount of research points required
+	 * @param dependencies
+	 *            dependencies as an array of strings
+	 * @return
+	 */
+	protected String createTechnology( String name , String category , String discovery , String description ,
+			Long cost , Long points , String... dependencies )
+	{
+		StringBuilder builder = new StringBuilder( "<technology" );
+
+		if ( name != null ) {
+			builder.append( " name=" ).append( this.quoteString( name ) );
+		}
+		if ( category != null ) {
+			builder.append( " category=" ).append( this.quoteString( category ) );
+		}
+		if ( discovery != null ) {
+			builder.append( " discovery=" ).append( this.quoteString( discovery ) );
+		}
+		if ( description != null ) {
+			builder.append( " description=" ).append( this.quoteString( description ) );
+		}
+		if ( cost != null ) {
+			builder.append( " cost=\"" ).append( cost ).append( "\"" );
+		}
+		if ( points != null ) {
+			builder.append( " points=\"" ).append( points ).append( "\"" );
+		}
+
+		if ( dependencies.length == 0 ) {
+			builder.append( " />" );
+		} else {
+			builder.append( ">" );
+			for ( String dep : dependencies ) {
+				builder.append( "<depends-on>" );
+				if ( dep != null ) {
+					builder.append( this.xmlString( dep ) );
+				}
+				builder.append( "</depends-on>" );
+			}
+			builder.append( "</technology>" );
+		}
+
+		return builder.toString( );
+	}
+
+
+	/**
+	 * Creates the XML code for a top-level technology definition element
+	 * 
+	 * @param resources
+	 *            XML definitions of technologies
+	 * 
+	 * @return the top-level element's XML code
+	 */
+	protected String createTopLevel( String... technologies )
+	{
+		StringBuilder str = new StringBuilder( );
+		str.append( "<lw-tech-graph>" );
+		for ( String technology : technologies ) {
+			str.append( technology );
+		}
+		str.append( "</lw-tech-graph>" );
+		return str.toString( );
+	}
+
+
+	/**
+	 * Create the necessary XStream instance
+	 * 
+	 * <p>
+	 * Initialise an XStream instance and set it up so it can process technology data definitions.
+	 * This includes annotation processing, of course, but also generating an alias allowing the
+	 * &lt;technology&gt; tag to be recognised on its own.
+	 */
+	private XStream createXStreamInstance( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( Technologies.class );
+		xstream.alias( "technology" , Technology.class );
+		return xstream;
+	}
+
+
+	/**
+	 * Create a resource object from its XML code
+	 * 
+	 * @param xml
+	 *            the XML code
+	 * @param cls
+	 *            the class of the object
+	 * 
+	 * @return the object that was created from the code
+	 * 
+	 * @throws ClassCastException
+	 *             if the code corresponds to some other type of object
+	 * @throws XStreamException
+	 *             if some error occurred while deserialising the object
+	 */
+	protected < T extends ImportableData > T createObject( String xml , Class< T > cls )
+			throws ClassCastException , XStreamException
+	{
+		return cls.cast( this.createXStreamInstance( ).fromXML( xml ) );
+	}
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/TestTechnologies.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/TestTechnologies.java
new file mode 100644
index 0000000..bd5c6ac
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/TestTechnologies.java
@@ -0,0 +1,96 @@
+package com.deepclone.lw.cli.xmlimport.data.techs;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+
+
+
+/**
+ * Unit tests for the {@link Technologies} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestTechnologies
+		extends BaseTest
+{
+
+	/**
+	 * Test loading an empty list of technologies
+	 * 
+	 * <p>
+	 * {@link Technologies#iterator()} will throw {@link NullPointerException} when there are no
+	 * definitions. Use that to determine if it is empty.
+	 */
+	@Test( expected = NullPointerException.class )
+	public void testEmpty( )
+	{
+		String defs = this.createTopLevel( );
+		Technologies techs = this.createObject( defs , Technologies.class );
+		techs.iterator( );
+	}
+
+
+	/**
+	 * Test loading a list of technologies
+	 */
+	@Test
+	public void testTechs( )
+	{
+		String tDef = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 34L );
+		String defs = this.createTopLevel( tDef );
+		Technologies techs = this.createObject( defs , Technologies.class );
+
+		int i = 0;
+		for ( Technology technology : techs ) {
+			assertNotNull( technology );
+			assertEquals( 0 , i );
+			i++;
+		}
+	}
+
+
+	/**
+	 * Test verifying an empty list of technologies
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyEmpty( )
+			throws DataImportException
+	{
+		String defs = this.createTopLevel( );
+		Technologies techs = this.createObject( defs , Technologies.class );
+		techs.verifyData( );
+	}
+
+
+	/**
+	 * Test verifying a list of technologies with an invalid technology in it
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyInvalid( )
+			throws DataImportException
+	{
+		String tDef = this.createTechnology( null , "category" , "discovery" , "description" , 12L , 34L );
+		String defs = this.createTopLevel( tDef );
+		Technologies techs = this.createObject( defs , Technologies.class );
+		techs.verifyData( );
+	}
+
+
+	/**
+	 * Test verifying a valid list of technologies
+	 */
+	@Test
+	public void testVerifyOk( )
+			throws DataImportException
+	{
+		String tDef = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 34L );
+		String defs = this.createTopLevel( tDef );
+		Technologies techs = this.createObject( defs , Technologies.class );
+		techs.verifyData( );
+	}
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/TestTechnology.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/TestTechnology.java
new file mode 100644
index 0000000..4b76abc
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/cli/xmlimport/data/techs/TestTechnology.java
@@ -0,0 +1,329 @@
+package com.deepclone.lw.cli.xmlimport.data.techs;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+
+
+
+/**
+ * Test the {@link Technology} class
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class TestTechnology
+		extends BaseTest
+{
+
+	/**
+	 * Test loading a definition with no dependencies
+	 */
+	@Test
+	public void testLoadNoDeps( )
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+
+		assertNotNull( technology );
+		assertEquals( "name" , technology.getName( ) );
+		assertEquals( "category" , technology.getCategory( ) );
+		assertEquals( "discovery" , technology.getDiscovery( ) );
+		assertEquals( "description" , technology.getDescription( ) );
+		assertEquals( new Long( 12 ) , technology.getCost( ) );
+		assertEquals( new Long( 34 ) , technology.getPoints( ) );
+
+		assertNotNull( technology.getDependencies( ) );
+		assertTrue( technology.getDependencies( ).isEmpty( ) );
+	}
+
+
+	/**
+	 * Test loading a definition with dependencies
+	 */
+	@Test
+	public void testLoadDeps( )
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 34L ,
+				"dep1" , "dep2" );
+		Technology technology = this.createObject( definition , Technology.class );
+
+		assertNotNull( technology );
+		assertEquals( "name" , technology.getName( ) );
+		assertEquals( "category" , technology.getCategory( ) );
+		assertEquals( "discovery" , technology.getDiscovery( ) );
+		assertEquals( "description" , technology.getDescription( ) );
+		assertEquals( new Long( 12 ) , technology.getCost( ) );
+		assertEquals( new Long( 34 ) , technology.getPoints( ) );
+
+		assertNotNull( technology.getDependencies( ) );
+		assertEquals( 2 , technology.getDependencies( ).size( ) );
+		assertEquals( "dep1" , technology.getDependencies( ).get( 0 ) );
+		assertEquals( "dep2" , technology.getDependencies( ).get( 1 ) );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the name is null
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyNameNull( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( null , "category" , "discovery" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the name is empty
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyNameEmpty( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "" , "category" , "discovery" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the name consists of spaces only
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyNameSpaces( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "    " , "category" , "discovery" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the category is null
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyCategoryNull( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , null , "discovery" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the category is empty
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyCategoryEmpty( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "" , "discovery" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the category consists of spaces only
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyCategorySpaces( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "    " , "discovery" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the discovery string is null
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyDiscoveryNull( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , null , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the discovery string is empty
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyDiscoveryEmpty( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the discovery string consists of spaces only
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyDiscoverySpaces( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "    " , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the description is null
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyDescriptionNull( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , null , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the description is empty
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyDescriptionEmpty( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the description consists of spaces only
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyDescriptionSpaces( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "  " , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the cost is null
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyCostNull( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , null , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the cost is 0
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyCostZero( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 0L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the amount of points is null
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyPointsNull( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , null );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when the amount of points is 0
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyPointsZero( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 0L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} on a valid definition with no dependencies
+	 */
+	@Test
+	public void testVerifyValidNoDeps( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 34L );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when a dependency is empty
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyDepsEmpty( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 34L , "" );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} when a dependency consists of spaces only
+	 */
+	@Test( expected = DataImportException.class )
+	public void testVerifyDepsSpaces( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 34L ,
+				"   " );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+
+	/**
+	 * Test {@link Technology#verifyData()} on a valid definition with dependencies
+	 */
+	@Test
+	public void testVerifyValidDeps( )
+			throws DataImportException
+	{
+		String definition = this.createTechnology( "name" , "category" , "discovery" , "description" , 12L , 34L ,
+				"dep1" , "dep2" );
+		Technology technology = this.createObject( definition , Technology.class );
+		technology.verifyData( );
+	}
+
+}
diff --git a/legacyworlds/doc/local-deployment.txt b/legacyworlds/doc/local-deployment.txt
index 24669c3..b31c0ff 100644
--- a/legacyworlds/doc/local-deployment.txt
+++ b/legacyworlds/doc/local-deployment.txt
@@ -41,6 +41,8 @@ from the root of the server's distribution:
 		--run-tool ImportText data/i18n-text.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \
 		--run-tool ImportTechs data/techs.xml
+	java -jar legacyworlds-server-main-1.0.0-0.jar \
+		--run-tool ImportTechGraph data/tech-graph.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \
 		--run-tool ImportResources data/resources.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \

From 8c0b4abd1e946843c3a19435dabb17215c55142a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 28 Feb 2012 12:34:59 +0100
Subject: [PATCH 62/94] Empire research initialisation

* When an empire is created, all technologies that have no dependencies
will be added as ongoing research with default priority and no points in
the empire research table.
---
 .../parts/040-functions/040-empire.sql        |  8 +++++
 .../040-empire/010-create-empire.sql          | 33 ++++++++++++++++++-
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index a49c0d6..1c1defa 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -45,6 +45,14 @@ BEGIN
 	INSERT INTO emp.resources ( empire_id , resource_name_id )
 		SELECT _name_id , resource_name_id FROM defs.resources;
 
+	-- Insert technologies that have no dependencies as research in progress
+	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+		SELECT _name_id , technology_name_id
+			FROM defs.technologies
+				LEFT OUTER JOIN defs.technology_dependencies
+					USING ( technology_name_id )
+			WHERE techdep_id IS NULL;
+
 	-- Update resource mining quantities
 	UPDATE verse.planet_resources
 		SET pres_income = emp.mining_compute_extraction( _update_row )
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
index 5aa570b..c079d75 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
@@ -27,6 +27,28 @@ BEGIN;
 			100 , 0.2 , 0.5
 		);
 
+	/*
+	 * We also need 3 technologies (tech1, tech2 and tech3), and a chain of
+	 * dependencies between them (i.e. tech3 -> tech2 -> tech1). Disabling
+	 * unused fields in defs.technologies makes things easier.
+	 */
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+	SELECT _create_test_strings( 3 , 'tech' );
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech3' ) );
+	INSERT INTO defs.technology_dependencies(
+			technology_name_id , technology_name_id_depends
+		) VALUES ( _get_string( 'tech2' ) , _get_string( 'tech1' ) ) ,
+			 ( _get_string( 'tech3' ) , _get_string( 'tech2' ) );
+
+
 	/* We replace the emp.mining_get_input() and emp.mining_compute_extraction()
 	 * functions with something we control fully.
 	 */
@@ -49,7 +71,7 @@ BEGIN;
 	$$;
 
 	/***** TESTS BEGIN HERE *****/
-	SELECT plan( 8 );
+	SELECT plan( 9 );
 	
 	SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) ,
 				_get_map_name( 'testPlanet1' ) ,
@@ -92,6 +114,15 @@ BEGIN;
 		FROM emp.resources
 		WHERE empire_id = _get_emp_name( 'testEmp1' );
 
+	SELECT diag_test_name( 'emp.create_empire() - Empire technologies have been initialised' );
+	SELECT set_eq( $$
+		SELECT technology_name_id , emptech_state::TEXT , emptech_points , emptech_priority
+			FROM emp.technologies_v2
+			WHERE empire_id = _get_emp_name( 'testEmp1' );
+	$$ , $$ VALUES(
+		_get_string( 'tech1' ) , 'RESEARCH' , 0.0 , 2
+	) $$ );
+
 	SELECT diag_test_name( 'emp.create_empire() - Resource mining has been updated' );
 	SELECT is( pres_income::DOUBLE PRECISION , 42::DOUBLE PRECISION )
 		FROM verse.planet_resources

From f4e38e49435d21f27545db28fa544af114cdd8d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 28 Feb 2012 16:15:02 +0100
Subject: [PATCH 63/94] Technology implementation

* A stored procedure which implements technologies has been added. It
will mark a pending technology as implemented and remove the
corresponding quantity of money from the empire, then add any newly
available research items to the empire's research.
---
 .../parts/040-functions/040-empire.sql        |  88 +++++++++++++-
 .../040-empire/015-technology-implement.sql   | 110 ++++++++++++++++++
 .../040-empire/015-technology-implement.sql   |  13 +++
 3 files changed, 210 insertions(+), 1 deletion(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/015-technology-implement.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/015-technology-implement.sql

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 1c1defa..bc4e640 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -68,6 +68,92 @@ REVOKE EXECUTE
 	FROM PUBLIC;
 
 
+/*
+ * Implements a technology
+ * ------------------------
+ * 
+ * This stored procedure is called when an empire attempts to implement a
+ * technology. It will check the empire's resources and the technology itself,
+ * then mark it as implemented if necessary. It will also add new research
+ * entries if necessary.
+ * 
+ * Parameters:
+ *		_empire			The empire's identifier
+ *		_technology		The string identifier for the technology to implement
+ */
+DROP FUNCTION emp.technology_implement( INT , TEXT );
+CREATE FUNCTION emp.technology_implement( _empire INT , _technology TEXT )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $technology_implement$
+
+DECLARE
+	_impl_data	RECORD;
+
+BEGIN
+
+	-- Access and lock the records
+	SELECT INTO _impl_data
+			technology_name_id , technology_price
+		FROM emp.empires _emp
+			INNER JOIN emp.technologies_v2 _tech
+				ON _tech.empire_id = _emp.name_id
+			INNER JOIN defs.technologies _def
+				USING ( technology_name_id )
+			INNER JOIN defs.strings _name
+				ON _def.technology_name_id = _name.id
+		WHERE _emp.name_id = _empire
+			AND _name.name = _technology
+			AND _tech.emptech_state = 'PENDING'
+			AND _emp.cash >= _def.technology_price
+		FOR UPDATE OF _emp , _tech
+		FOR SHARE OF _def;
+	IF NOT FOUND THEN
+		RETURN FALSE;
+	END IF;
+
+	-- Implement the technology
+	UPDATE emp.empires
+		SET cash = cash - _impl_data.technology_price
+		WHERE name_id = _empire;
+	UPDATE emp.technologies_v2
+		SET emptech_state = 'KNOWN'
+		WHERE empire_id = _empire
+			AND technology_name_id = _impl_data.technology_name_id;
+
+	-- Insert new research
+	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+		SELECT _empire , _valid.technology_name_id
+			FROM  ( SELECT _tech.technology_name_id ,
+							( COUNT(*) = COUNT(_emptech.emptech_state) ) AS emptech_has_dependencies
+						FROM defs.technologies _tech
+							INNER JOIN defs.technology_dependencies _deps
+									USING ( technology_name_id )
+							LEFT OUTER JOIN emp.technologies_v2 _emptech
+									ON _emptech.technology_name_id = _deps.technology_name_id_depends
+										AND _emptech.emptech_state = 'KNOWN'
+										AND _emptech.empire_id = _empire
+						GROUP BY _tech.technology_name_id ) _valid
+				LEFT OUTER JOIN emp.technologies_v2 _emptech
+					ON _emptech.empire_id = _empire
+						AND _emptech.technology_name_id = _valid.technology_name_id
+			WHERE _emptech.empire_id IS NULL AND _valid.emptech_has_dependencies;
+
+	RETURN TRUE;
+END;
+$technology_implement$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.technology_implement( INT , TEXT )
+	FROM PUBLIC;
+
+GRANT EXECUTE
+	ON FUNCTION emp.technology_implement( INT , TEXT )
+	TO :dbuser;
+
+
 
 --
 -- Returns a planet owner's empire size
@@ -107,7 +193,7 @@ GRANT EXECUTE ON FUNCTION emp.get_current( INT ) TO :dbuser;
 
 
 --
--- Implements a technology
+-- Implements a technology (OLD VERSION)
 --
 
 CREATE OR REPLACE FUNCTION emp.implement_tech( e_id INT , l_id INT )
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/015-technology-implement.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/015-technology-implement.sql
new file mode 100644
index 0000000..8479524
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/015-technology-implement.sql
@@ -0,0 +1,110 @@
+/*
+ * Unit tests for emp.technology_implement()
+ */
+BEGIN;
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+
+	/*
+	 * Create empires
+	 */
+	SELECT _create_emp_names( 4 , 'emp' );
+	INSERT INTO emp.empires ( name_id , cash )
+		SELECT id , 200 FROM naming.empire_names;
+
+	/*
+	 * We also need 3 technologies (tech1, tech2 and tech3), and some
+	 * dependencies between them: tech3 -> {tech2, tech1}). Disabling
+	 * unused fields in defs.technologies makes things easier.
+	 */
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+	SELECT _create_test_strings( 3 , 'tech' );
+	INSERT INTO defs.technologies ( technology_name_id , technology_price )
+		VALUES ( _get_string( 'tech1' ) , 200 ) ,
+				( _get_string( 'tech2' ) , 200 ) ,
+				( _get_string( 'tech3' ) , 300 );
+	INSERT INTO defs.technology_dependencies(
+			technology_name_id , technology_name_id_depends
+		) VALUES ( _get_string( 'tech3' ) , _get_string( 'tech1' ) ) ,
+			 ( _get_string( 'tech3' ) , _get_string( 'tech2' ) );
+
+	/* Empire "emp1" has only in-progress research. */
+	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+		VALUES( _get_emp_name( 'emp1' ) , _get_string( 'tech1' ) );
+
+	/* Empire "emp2" has a pending technology. */
+	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
+		VALUES( _get_emp_name( 'emp2' ) , _get_string( 'tech1' ) , 'PENDING' , NULL , NULL );
+
+	/* Empire "emp3" has implemented 'tech1' and has 'tech2' as pending. */
+	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
+		VALUES( _get_emp_name( 'emp3' ) , _get_string( 'tech1' ) , 'KNOWN' , NULL , NULL ) ,
+			( _get_emp_name( 'emp3' ) , _get_string( 'tech2' ) , 'PENDING' , NULL , NULL );
+
+	/* Empire "emp4" has implemented 'tech1' and 'tech2' and has 'tech3' as pending. */
+	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
+		VALUES( _get_emp_name( 'emp4' ) , _get_string( 'tech1' ) , 'KNOWN' , NULL , NULL ) ,
+			( _get_emp_name( 'emp4' ) , _get_string( 'tech2' ) , 'KNOWN' , NULL , NULL ) ,
+			( _get_emp_name( 'emp4' ) , _get_string( 'tech3' ) , 'PENDING' , NULL , NULL );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 10 );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on in-progress research' );
+	SELECT ok( NOT emp.technology_implement( _get_emp_name( 'emp1' ) , 'tech1' ) );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on unknown technology' );
+	SELECT ok( NOT emp.technology_implement( _get_emp_name( 'emp1' ) , 'tech2' ) );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on implemented technology' );
+	SELECT ok( NOT emp.technology_implement( _get_emp_name( 'emp3' ) , 'tech1' ) );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology - No new research - Return value' );
+	SELECT ok( emp.technology_implement( _get_emp_name( 'emp2' ) , 'tech1' ) );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology - No new research - Table contents' );
+	SELECT set_eq( $$
+		SELECT technology_name_id , emptech_state::TEXT
+			FROM emp.technologies_v2
+			WHERE empire_id = _get_emp_name( 'emp2' )
+	$$ , $$ VALUES(
+		_get_string( 'tech1' ) , 'KNOWN'
+	) $$ );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology - No new research - Empire cash' );
+	SELECT is( cash , 0.0::REAL ) FROM emp.empires
+		WHERE name_id = _get_emp_name( 'emp2' );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology - New research - Return value' );
+	SELECT ok( emp.technology_implement( _get_emp_name( 'emp3' ) , 'tech2' ) );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology - New research - Table contents' );
+	SELECT set_eq( $$
+		SELECT technology_name_id , emptech_state::TEXT
+			FROM emp.technologies_v2
+			WHERE empire_id = _get_emp_name( 'emp3' )
+	$$ , $$ VALUES(
+		_get_string( 'tech1' ) , 'KNOWN'
+	) , (
+		_get_string( 'tech2' ) , 'KNOWN'
+	) , (
+		_get_string( 'tech3' ) , 'RESEARCH'
+	) $$ );
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology - New research - Empire cash' );
+	SELECT is( cash , 0.0::REAL ) FROM emp.empires
+		WHERE name_id = _get_emp_name( 'emp3' );
+
+
+	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology when empire cash is too low' );
+	SELECT ok( NOT emp.technology_implement( _get_emp_name( 'emp4' ) , 'tech3' ) );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/015-technology-implement.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/015-technology-implement.sql
new file mode 100644
index 0000000..57097ce
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/015-technology-implement.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on emp.technology_implement()
+ */
+BEGIN;
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.technology_implement() - EXECUTE privilege' );
+	SELECT lives_ok( $$
+		SELECT emp.technology_implement( 1 , '' );
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From e01eab9c09bf2c409c798c2eb4d26ab7c926f4be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Wed, 29 Feb 2012 11:50:04 +0100
Subject: [PATCH 64/94] Technology dependencies view

* Added SQL view that lists dependencies of technologies as
comma-separated lists of identifiers
---
 .../026-technology-dependencies.sql           | 25 +++++++++-
 .../050-technology-dependencies-view.sql      | 49 +++++++++++++++++++
 .../060-technology-dependencies-view.sql      | 15 ++++++
 3 files changed, 88 insertions(+), 1 deletion(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/050-technology-dependencies-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/060-technology-dependencies-view.sql

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/026-technology-dependencies.sql b/legacyworlds-server-data/db-structure/parts/040-functions/026-technology-dependencies.sql
index ff75daa..0a1fc41 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/026-technology-dependencies.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/026-technology-dependencies.sql
@@ -311,4 +311,27 @@ CREATE TRIGGER tg_techdeps_ai
 
 REVOKE EXECUTE
 	ON FUNCTION defs.tgf_techdeps_ai( )
-	FROM PUBLIC;
\ No newline at end of file
+	FROM PUBLIC;
+
+
+/*
+ * Technology dependencies view
+ * -----------------------------
+ * 
+ * This view generates a parseable list of dependencies per technology.
+ * 
+ * Columns:
+ *		technology_name_id		The technology's name
+ *		technology_dependencies	A list of comma-separated technology name
+ *									identifiers
+ */
+DROP VIEW IF EXISTS defs.technology_dependencies_view CASCADE;
+CREATE VIEW defs.technology_dependencies_view
+	AS SELECT technology_name_id ,
+			array_to_string( array_agg( _name_str.name ) , ',' ) AS technology_dependencies
+		FROM defs.technologies _tech
+			LEFT OUTER JOIN defs.technology_dependencies
+				USING ( technology_name_id )
+			LEFT OUTER JOIN defs.strings _name_str
+				ON _name_str.id = technology_name_id_depends
+		GROUP BY technology_name_id;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/050-technology-dependencies-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/050-technology-dependencies-view.sql
new file mode 100644
index 0000000..879da1f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/026-technology-dependencies/050-technology-dependencies-view.sql
@@ -0,0 +1,49 @@
+/*
+ * Unit tests for defs.technology_dependencies_view
+ */
+BEGIN;
+	\i utils/strings.sql
+	-- Make the columns we don't use in the technology definition table NULL-able
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+
+	-- Create strings to use as the technologies' names
+	SELECT _create_test_strings( 4 , 'tech' );
+	
+	-- Insert the technologies
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) ,
+				( _get_string( 'tech2' ) ) ,
+				( _get_string( 'tech3' ) ) ,
+				( _get_string( 'tech4' ) );
+
+	-- Insert dependencies: tech3 -> {tech1,tech2} , tech4 -> {tech3}
+	INSERT INTO defs.technology_dependencies ( technology_name_id , technology_name_id_depends )
+		VALUES ( _get_string( 'tech3' ) , _get_string( 'tech1' ) ) ,
+			( _get_string( 'tech3' ) , _get_string( 'tech2' ) ) ,
+			( _get_string( 'tech4' ) , _get_string( 'tech3' ) );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 3 );
+
+	SELECT diag_test_name( 'defs.technology_dependencies_view - Technologies with no dependencies' );
+	SELECT is( technology_dependencies , '' )
+		FROM defs.technology_dependencies_view
+		WHERE technology_name_id = _get_string( 'tech1' );
+
+	SELECT diag_test_name( 'defs.technology_dependencies_view - Technologies with a single dependency' );
+	SELECT is( technology_dependencies , 'tech3' )
+		FROM defs.technology_dependencies_view
+		WHERE technology_name_id = _get_string( 'tech4' );
+
+	SELECT diag_test_name( 'defs.technology_dependencies_view - Technologies with multiple dependencies' );
+	SELECT ok( technology_dependencies = 'tech1,tech2' OR technology_dependencies = 'tech2,tech1' )
+		FROM defs.technology_dependencies_view
+		WHERE technology_name_id = _get_string( 'tech3' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/060-technology-dependencies-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/060-technology-dependencies-view.sql
new file mode 100644
index 0000000..6a016de
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/026-technology-dependencies/060-technology-dependencies-view.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on defs.technology_dependencies_view
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'defs.technology_dependencies_view - No SELECT privilege' );
+	SELECT throws_ok( $$
+		SELECT * FROM defs.technology_dependencies_view;
+	$$ , 42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From b15acadc1bc31f8b5d2183174868a709e208e13d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 1 Mar 2012 09:50:20 +0100
Subject: [PATCH 65/94] Empire technology views

* Added new research-related constants

* Added set of views and functions to list empires' technologies. This
includes a view which determines the visibility of an in-progress
research's details, and a main list view.
---
 .../lw/beans/sys/ConstantsRegistrarBean.java  |  15 ++
 .../parts/040-functions/040-empire.sql        | 164 ++++++++++++++++++
 .../016-technology-make-identifier.sql        |  17 ++
 .../040-technology-visibility-view.sql        | 111 ++++++++++++
 .../040-empire/050-technologies-view.sql      | 125 +++++++++++++
 .../016-technology-make-identifier.sql        |  14 ++
 .../040-technology-visibility-view.sql        |  15 ++
 .../040-empire/050-technologies-view.sql      |  15 ++
 8 files changed, 476 insertions(+)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/016-technology-make-identifier.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/040-technology-visibility-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/050-technologies-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/016-technology-make-identifier.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/040-technology-visibility-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/050-technologies-view.sql

diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 23bdad8..5fd56cc 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -125,6 +125,21 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( wcNames[ 6 ] , cat , cDesc , 0.50 , 0.01 , true ) );
 		cDesc = "Proportion of queue investments that is recovered when flushing the queue.";
 		defs.add( new ConstantDefinition( wcNames[ 7 ] , cat , cDesc , 0.1 , 0.01 , 1.0 ) );
+		
+		// Research
+		String[] rcNames = {
+				"basePoints" ,  "visibility.points" , "visibility.ratio"
+		};
+		for ( int i = 0 ; i < wcNames.length ; i++ ) {
+			rcNames[ i ] = "game.research." + rcNames[ i ];
+		}
+		cat = "Research & technologies";
+		cDesc = "Research points per population unit.";
+		defs.add( new ConstantDefinition( rcNames[ 0 ] , cat , cDesc , 0.50 , 0.01 , true ) );
+		cDesc = "Points above which a technology becomes visible.";
+		defs.add( new ConstantDefinition( rcNames[ 1 ] , cat , cDesc , 2500.0 , 0.01 , true ) );
+		cDesc = "Completion ratio above which a technology becomes visible.";
+		defs.add( new ConstantDefinition( rcNames[ 2 ] , cat , cDesc , 0.10 , 0.01 , 0.99 ) );
 
 		// Vacation mode
 		cDesc = "Initial vacation credits.";
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index bc4e640..a1383ed 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -154,6 +154,46 @@ GRANT EXECUTE
 	TO :dbuser;
 
 
+/*
+ * Compute a technology identifier
+ * --------------------------------
+ * 
+ * This function returns the identifier of a technology as seen from the
+ * player's side. The identifier is either the string identifier for the
+ * technology's name, or a MD5 hash including both the empire's identifier
+ * and the string identifier for "unknown" technologies.
+ * 
+ * Parameters:
+ *		_empire		The empire's identifier
+ *		_technology	The technology's string identifier
+ *		_visible	TRUE if the technology is supposed to be visible, FALSE
+ *						otherwise
+ *
+ * Returns:
+ *		?			The technology's client-side identifier
+ */
+DROP FUNCTION IF EXISTS emp.technology_make_identifier( INT , TEXT , BOOLEAN );
+CREATE FUNCTION emp.technology_make_identifier(
+				_empire INT , _technology TEXT , _visible BOOLEAN )
+	RETURNS TEXT
+	LANGUAGE SQL
+	STRICT IMMUTABLE
+	SECURITY DEFINER
+AS $technology_make_identifier$
+
+	SELECT ( CASE
+		WHEN $3 THEN
+			$2
+		ELSE
+			md5( $1::TEXT || ' (making hash less obvious) ' || $2 )
+	END );
+
+$technology_make_identifier$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.technology_make_identifier( INT , TEXT , BOOLEAN )
+	FROM PUBLIC;
+
 
 --
 -- Returns a planet owner's empire size
@@ -878,3 +918,127 @@ CREATE VIEW emp.resources_view
 GRANT SELECT
 	ON emp.resources_view
 	TO :dbuser;
+
+
+
+/*
+ * Technology visibility view
+ * ---------------------------
+ * 
+ * This view can be used to determine whether entries from empires' research
+ * and technologies table are fully visible or only displayed as "unknown
+ * technologies".
+ * 
+ * Columns:
+ *		empire_id			The empire's identifier
+ *		technology_name_id	The technology's identifier
+ *		emptech_visible		TRUE if the technology's details are visible,
+ *								FALSE if they should be hidden
+ */
+DROP VIEW IF EXISTS emp.technology_visibility_view CASCADE;
+CREATE VIEW emp.technology_visibility_view
+	AS SELECT empire_id , technology_name_id ,
+			( emptech_state <> 'RESEARCH'
+				OR emptech_points >= sys.get_constant( 'game.research.visibility.points' )
+				OR emptech_points / technology_points::DOUBLE PRECISION >= sys.get_constant( 'game.research.visibility.ratio' )
+			) AS emptech_visible
+		FROM emp.technologies_v2
+			INNER JOIN defs.technologies
+				USING ( technology_name_id );
+
+
+/*
+ * Empire research and technologies
+ * ---------------------------------
+ * 
+ * This view lists empires' research and technologies, along with their
+ * current state.
+ * 
+ * Columns:
+ *		empire_id			The empire's identifier
+ *		emptech_id			An identifier for the technology, which is either
+ *								the string identifier of the technology's name
+ *								or a MD5 hash if the technology is not
+ *								supposed to be visible
+ *		emptech_state		The state of the technology, straight from the
+ *								empire technology table
+ *		emptech_visible		Whether the technology is supposed to be visible
+ *								or not
+ *		technology_category	The string identifier of the technology's category
+ *		technology_name		The string identifier of the technology's name,
+ *								or NULL if the technology is not supposed to
+ *								be visible
+ *		technology_description	The string identifier of the technology's name,
+ *									or NULL if the technology is not supposed
+ *									to be visible
+ *		emptech_points		The amount of points accumulated while researching
+ *								the technology, or NULL if the technology is
+ *								not supposed to be visible
+ *		emptech_priority	The current research priority, or NULL if the
+ *								technology is no longer being researched
+ *		emptech_ratio		The percentage of points accumulated while
+ *								researching the technology, or NULL if the
+ *								technology is no longer being researched
+ *		technology_price	The monetary price of the technology, or NULL if
+ *								the technology is not supposed to be visible
+ *		technology_dependencies	The technology's dependencies from the
+ *									dependencies view
+ */
+DROP VIEW IF EXISTS emp.technologies_v2_view CASCADE;
+CREATE VIEW emp.technologies_v2_view
+	AS SELECT empire_id ,
+			emp.technology_make_identifier( empire_id , _name_str.name , emptech_visible ) AS emptech_id ,
+			emptech_state ,
+			emptech_visible ,
+			_cat_str.name AS technology_category ,
+			( CASE
+				WHEN emptech_visible THEN
+					_name_str.name
+				ELSE
+					NULL::TEXT
+			END ) AS technology_name ,
+			( CASE
+				WHEN emptech_visible THEN
+					_descr_str.name
+				ELSE
+					NULL::TEXT
+			END ) AS technology_description ,
+			( CASE
+				WHEN emptech_state <> 'RESEARCH' then
+					technology_points
+				WHEN emptech_visible THEN
+					FLOOR( emptech_points )::BIGINT
+				ELSE
+					NULL::BIGINT
+			END ) AS emptech_points ,
+			emptech_priority ,
+			( CASE
+				WHEN emptech_state = 'RESEARCH' THEN
+					FLOOR( 100.0 * emptech_points / technology_points::DOUBLE PRECISION )::INT
+				ELSE
+					NULL::INT
+			END ) AS emptech_ratio ,
+			( CASE
+				WHEN emptech_visible THEN
+					technology_price
+				ELSE
+					NULL::INT
+			END ) AS technology_price ,
+			technology_dependencies
+		FROM emp.technologies_v2
+			INNER JOIN emp.technology_visibility_view
+				USING ( technology_name_id , empire_id )
+			INNER JOIN defs.technologies _tech
+				USING ( technology_name_id )
+			INNER JOIN defs.technology_dependencies_view
+				USING ( technology_name_id )
+			INNER JOIN defs.strings _name_str
+				ON _name_str.id = _tech.technology_name_id
+			INNER JOIN defs.strings _cat_str
+				ON _cat_str.id = _tech.technology_category_id
+			INNER JOIN defs.strings _descr_str
+				ON _descr_str.id = _tech.technology_description_id;
+
+GRANT SELECT
+	ON emp.technologies_v2_view
+	TO :dbuser;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/016-technology-make-identifier.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/016-technology-make-identifier.sql
new file mode 100644
index 0000000..0ea818a
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/016-technology-make-identifier.sql
@@ -0,0 +1,17 @@
+/*
+ * Unit tests for emp.technology_make_identifier()
+ */
+BEGIN;
+	SELECT no_plan( );
+	
+	SELECT diag_test_name( 'emp.technology_make_identifier() - Identifier of visible technologies' );
+	SELECT is( emp.technology_make_identifier( 1 , 'test-string' , TRUE ) , 'test-string' );
+
+	SELECT diag_test_name( 'emp.technology_make_identifier() - Identifier of unknown technologies - 32 characters' );
+	SELECT is( length( emp.technology_make_identifier( 1 , 'test-string' , FALSE ) ) , 32 );
+
+	SELECT diag_test_name( 'emp.technology_make_identifier() - Identifier of unknown technologies - Contains hex value' );
+	SELECT ok( emp.technology_make_identifier( 1 , 'test-string' , FALSE ) !~ '[^a-f0-9]' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/040-technology-visibility-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/040-technology-visibility-view.sql
new file mode 100644
index 0000000..d5b22ac
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/040-technology-visibility-view.sql
@@ -0,0 +1,111 @@
+/*
+ * Unit tests for emp.technology_visibility_view
+ */
+BEGIN;
+	\i utils/strings.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+
+	/* Create a single empire */
+	SELECT _create_emp_names( 1 , 'emp' );
+	INSERT INTO emp.empires ( name_id , cash )
+		VALUES ( _get_emp_name( 'emp1' ) , 200 );
+
+	/*
+	 * Set visibility thresholds to either 50% or 100 points.
+	 */
+	SELECT sys.uoc_constant( 'game.research.visibility.points' , '(test)' , 'Test' , 100.0 );
+	SELECT sys.uoc_constant( 'game.research.visibility.ratio' , '(test)' , 'Test' , 0.5 );
+	
+	/* Create 6 technology definitions that will be used to test various
+	 * states: pending, known, 4 variants of research in progress (points
+	 * and ratio below thresholds, points below threshold, ratio below
+	 * threshold, both points and ratio above thresholds).
+	 * 
+	 * Disable all unused fields from the definition table.
+	 */
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL;
+	SELECT _create_test_strings( 6 , 'tech' );
+	INSERT INTO defs.technologies( technology_name_id , technology_points )
+		VALUES ( _get_string( 'tech1' ) , 100 ) ,
+			( _get_string( 'tech2' ) , 100 ) ,
+			( _get_string( 'tech3' ) , 100 ) ,
+			( _get_string( 'tech4' ) , 100 ) ,
+			( _get_string( 'tech5' ) , 1000 ) ,
+			( _get_string( 'tech6' ) , 1000 );
+
+	/* Insert empire state */
+	INSERT INTO emp.technologies_v2 (
+			empire_id , technology_name_id ,
+			emptech_state , emptech_points , emptech_priority
+		) VALUES (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech1' ) ,
+			'KNOWN' , NULL , NULL
+		) , (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech2' ) ,
+			'PENDING' , NULL , NULL
+		) , (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech3' ) ,
+			'RESEARCH' , 10.0 , 2
+		) , (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech4' ) ,
+			'RESEARCH' , 51.0 , 2
+		) , (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech5' ) ,
+			'RESEARCH' , 101.0 , 2
+		) , (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech6' ) ,
+			'RESEARCH' , 501.0 , 2
+		);
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 7 );
+
+	SELECT diag_test_name( 'emp.technology_visibility_view - All technologies are listed' );
+	SELECT is( COUNT( * )::INT , 6 )
+		FROM emp.technology_visibility_view
+		WHERE empire_id = _get_emp_name( 'emp1' );
+
+	SELECT diag_test_name( 'emp.technology_visibility_view - Known technologies are visible' );
+	SELECT ok( emptech_visible )
+		FROM emp.technology_visibility_view
+		WHERE technology_name_id = _get_string( 'tech1' )
+			AND empire_id = _get_emp_name( 'emp1' );
+
+	SELECT diag_test_name( 'emp.technology_visibility_view - Pending technologies are visible' );
+	SELECT ok( emptech_visible )
+		FROM emp.technology_visibility_view
+		WHERE technology_name_id = _get_string( 'tech2' )
+			AND empire_id = _get_emp_name( 'emp1' );
+
+	SELECT diag_test_name( 'emp.technology_visibility_view - In-progress technologies with both points and ratios below thresholds are not visible' );
+	SELECT ok( NOT emptech_visible )
+		FROM emp.technology_visibility_view
+		WHERE technology_name_id = _get_string( 'tech3' )
+			AND empire_id = _get_emp_name( 'emp1' );
+
+	SELECT diag_test_name( 'emp.technology_visibility_view - In-progress technologies with points below threshold are visible' );
+	SELECT ok( emptech_visible )
+		FROM emp.technology_visibility_view
+		WHERE technology_name_id = _get_string( 'tech4' )
+			AND empire_id = _get_emp_name( 'emp1' );
+
+	SELECT diag_test_name( 'emp.technology_visibility_view - In-progress technologies with ratio below threshold are visible' );
+	SELECT ok( emptech_visible )
+		FROM emp.technology_visibility_view
+		WHERE technology_name_id = _get_string( 'tech5' )
+			AND empire_id = _get_emp_name( 'emp1' );
+
+	SELECT diag_test_name( 'emp.technology_visibility_view - In-progress technologies with both points and ratios above threshold are visible' );
+	SELECT ok( emptech_visible )
+		FROM emp.technology_visibility_view
+		WHERE technology_name_id = _get_string( 'tech6' )
+			AND empire_id = _get_emp_name( 'emp1' );
+
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/050-technologies-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/050-technologies-view.sql
new file mode 100644
index 0000000..f2dd122
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/050-technologies-view.sql
@@ -0,0 +1,125 @@
+/*
+ * Unit tests for emp.technologies_v2_view
+ */
+BEGIN;
+	\i utils/strings.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+
+	/* Create three empires (easier to use as keys) */
+	SELECT _create_emp_names( 3 , 'emp' );
+	INSERT INTO emp.empires ( name_id , cash )
+		SELECT id , 200.0 FROM naming.empire_names;
+
+	/* Create a technology after disabling unused fields */
+	ALTER TABLE defs.technologies
+		ALTER technology_discovery_id DROP NOT NULL;
+	SELECT _create_test_strings( 1 , 'tech' );
+	SELECT _create_test_strings( 1 , 'techCategory' );
+	SELECT _create_test_strings( 1 , 'techDescription' );
+	INSERT INTO defs.technologies (
+			technology_name_id , technology_category_id , technology_description_id ,
+			technology_price , technology_points
+		) VALUES (
+			_get_string( 'tech1' ) , _get_string( 'techCategory1' ) , _get_string( 'techDescription1' ) ,
+			123 , 456
+		);
+
+	/* Replace identifier function with something easier to check */
+	CREATE OR REPLACE FUNCTION emp.technology_make_identifier(
+				_empire INT , _technology TEXT , _visible BOOLEAN )
+		RETURNS TEXT
+		LANGUAGE SQL
+		STRICT IMMUTABLE
+		SECURITY DEFINER
+	AS $technology_make_identifier$
+		SELECT $1::TEXT || ',' || $2 || ',' || $3::TEXT;
+	$technology_make_identifier$;
+
+	/* Replace both the visibility and dependencies views with plain SELECT's
+	 * from tables.
+	 */
+	CREATE TABLE _fake_visibility(
+		empire_id			INT ,
+		technology_name_id	INT ,
+		emptech_visible		BOOLEAN
+	);
+	CREATE OR REPLACE VIEW emp.technology_visibility_view
+		AS SELECT * FROM _fake_visibility;
+	CREATE TABLE _fake_deps(
+		technology_name_id		INT ,
+		technology_dependencies	TEXT
+	);
+	CREATE OR REPLACE VIEW defs.technology_dependencies_view
+		AS SELECT * FROM _fake_deps;
+
+	/* Insert empire states and data for fake views */
+	INSERT INTO emp.technologies_v2 (
+			empire_id , technology_name_id ,
+			emptech_state , emptech_points , emptech_priority
+		) VALUES (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech1' ) ,
+			'KNOWN' , NULL , NULL
+		) , (
+			_get_emp_name( 'emp2' ) , _get_string( 'tech1' ) ,
+			'RESEARCH' , 228.9 , 0
+		) , (
+			_get_emp_name( 'emp3' ) , _get_string( 'tech1' ) ,
+			'RESEARCH' , 114 , 1
+		);
+	INSERT INTO _fake_visibility VALUES(
+		_get_emp_name( 'emp1' ) , _get_string( 'tech1' ) , TRUE 
+	) , (
+		_get_emp_name( 'emp2' ) , _get_string( 'tech1' ) , TRUE 
+	) , (
+		_get_emp_name( 'emp3' ) , _get_string( 'tech1' ) , FALSE 
+	);
+	INSERT INTO _fake_deps VALUES( _get_string( 'tech1' ) , 'deps are here' );
+	
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 3 );
+	
+	SELECT diag_test_name( 'emp.technologies_v2_view - Known technology' );
+	SELECT set_eq( $$
+		SELECT emptech_id , emptech_state::TEXT , emptech_visible ,
+				technology_category , technology_name , technology_description ,
+				emptech_points , emptech_priority IS NULL AS ep_null ,
+				emptech_ratio IS NULL AS er_null ,
+				technology_price , technology_dependencies
+			FROM emp.technologies_v2_view
+			WHERE empire_id = _get_emp_name( 'emp1' )
+	$$ , $$ VALUES(
+		_get_emp_name( 'emp1' ) || ',tech1,true' , 'KNOWN' , TRUE ,
+		'techCategory1' , 'tech1' , 'techDescription1' ,
+		456 ,  TRUE , TRUE , 123 , 'deps are here'
+	) $$ );
+	
+	SELECT diag_test_name( 'emp.technologies_v2_view - In-progress, visible technology' );
+	SELECT set_eq( $$
+		SELECT emptech_id , emptech_state::TEXT , emptech_visible ,
+				technology_category , technology_name , technology_description ,
+				emptech_points , emptech_priority , emptech_ratio ,
+				technology_price , technology_dependencies
+			FROM emp.technologies_v2_view
+			WHERE empire_id = _get_emp_name( 'emp2' )
+	$$ , $$ VALUES(
+		_get_emp_name( 'emp2' ) || ',tech1,true' , 'RESEARCH' , TRUE ,
+		'techCategory1' , 'tech1' , 'techDescription1' ,
+		228 ,  0 , 50 , 123 , 'deps are here'
+	) $$ );
+
+	SELECT diag_test_name( 'emp.technologies_v2_view - In-progress, unknown technology' );
+	SELECT set_eq( $$
+		SELECT emptech_id , emptech_state::TEXT , emptech_visible ,
+				technology_category , technology_name IS NULL AS n1 , technology_description IS NULL AS n2 ,
+				emptech_points IS NULL AS n3 , emptech_priority , emptech_ratio ,
+				technology_price IS NULL AS n4, technology_dependencies
+			FROM emp.technologies_v2_view
+			WHERE empire_id = _get_emp_name( 'emp3' )
+	$$ , $$ VALUES(
+		_get_emp_name( 'emp3' ) || ',tech1,false' , 'RESEARCH' , FALSE ,
+		'techCategory1' , TRUE, TRUE ,
+		TRUE , 1 , 25 , TRUE , 'deps are here'
+	) $$ );
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/016-technology-make-identifier.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/016-technology-make-identifier.sql
new file mode 100644
index 0000000..4f33b2e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/016-technology-make-identifier.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on emp.technology_make_identifier()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.technology_make_identifier() - No EXECUTE privilege' );
+	SELECT throws_ok( $$
+		SELECT emp.technology_make_identifier( 1 , '' , FALSE );
+	$$ , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/040-technology-visibility-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/040-technology-visibility-view.sql
new file mode 100644
index 0000000..5c2062c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/040-technology-visibility-view.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on emp.technology_visibility_view
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.technology_visibility_view - No SELECT privilege' );
+	SELECT throws_ok( $$
+		SELECT * FROM emp.technology_visibility_view;
+	$$ , 42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/050-technologies-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/050-technologies-view.sql
new file mode 100644
index 0000000..a869fd4
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/050-technologies-view.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on emp.technologies_v2_view
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.technologies_v2_view - SELECT privilege' );
+	SELECT lives_ok( $$
+		SELECT * FROM emp.technologies_v2_view;
+	$$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From 1dcde71dff0201f86eb1c01e3e9b1fef4319985e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 1 Mar 2012 10:52:52 +0100
Subject: [PATCH 66/94] Research priorities

* Added stored procedures meant to update research priorities.
---
 .../parts/040-functions/040-empire.sql        | 158 +++++++++++++++++-
 .../040-empire/017-resprio-update-start.sql   | 105 ++++++++++++
 .../040-empire/018-resprio-update-set.sql     |  37 ++++
 .../040-empire/019-resprio-update-apply.sql   |  82 +++++++++
 .../040-empire/017-resprio-update-start.sql   |  13 ++
 .../040-empire/018-resprio-update-set.sql     |  15 ++
 .../040-empire/019-resprio-update-apply.sql   |  15 ++
 7 files changed, 424 insertions(+), 1 deletion(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/017-resprio-update-start.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/018-resprio-update-set.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/019-resprio-update-apply.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/017-resprio-update-start.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/018-resprio-update-set.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/019-resprio-update-apply.sql

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index a1383ed..18cbe08 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -195,6 +195,162 @@ REVOKE EXECUTE
 	FROM PUBLIC;
 
 
+/*
+ * Initialise a research priorities update
+ * ----------------------------------------
+ * 
+ * This stored procedure prepares a temporary table which is used to update
+ * an empire's research priorities.
+ *
+ * Parameters:
+ *		_empire		The empire's identifier
+ *
+ * Returns:
+ *		?			TRUE if the empire exists, is not on vacation mode and has
+ *						in-progress research, FALSE otherwise.
+ */
+DROP FUNCTION IF EXISTS emp.resprio_update_start( INT );
+CREATE FUNCTION emp.resprio_update_start( _empire INT )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $resprio_update_start$
+BEGIN
+
+	-- Create temporary table
+	CREATE TEMPORARY TABLE rprio_update(
+		_empire_id			INT ,
+		_technology_name_id	INT ,
+		_emptech_id			TEXT ,
+		_emptech_priority	INT
+	) ON COMMIT DROP;
+
+	-- Lock records and fill table
+	INSERT INTO rprio_update (
+			_empire_id , _technology_name_id , _emptech_id , _emptech_priority
+		) SELECT _emp.name_id , _tech.technology_name_id ,
+				emp.technology_make_identifier( empire_id , _str.name , emptech_visible ) ,
+				_etech.emptech_priority
+			FROM emp.empires _emp
+				INNER JOIN emp.technologies_v2 _etech
+					ON _etech.empire_id = _emp.name_id
+						AND _etech.emptech_state = 'RESEARCH'
+				INNER JOIN defs.technologies _tech
+					USING ( technology_name_id )
+				INNER JOIN emp.technology_visibility_view _vis
+					USING ( empire_id , technology_name_id )
+				INNER JOIN defs.strings _str
+					ON _str.id = _tech.technology_name_id
+				INNER JOIN naming.empire_names _ename
+					ON _ename.id = _emp.name_id
+				LEFT OUTER JOIN users.vacations _vac
+					ON _vac.account_id = _ename.owner_id
+						AND _vac.status = 'PROCESSED'
+			WHERE _emp.name_id = _empire AND _vac.account_id IS NULL
+			FOR UPDATE OF _emp , _etech
+			FOR SHARE OF _tech , _str , _ename;
+
+	RETURN FOUND;
+END;
+$resprio_update_start$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.resprio_update_start( INT )
+	FROM PUBLIC;
+
+GRANT EXECUTE
+	ON FUNCTION emp.resprio_update_start( INT )
+	TO :dbuser;
+
+
+/*
+ * Set the priority of some research
+ * ----------------------------------
+ * 
+ * This stored procedure updates the priority of some in-progress empire
+ * research. It will only function correctly if emp.resprio_update_start() was
+ * already executed.
+ * 
+ * Parameters:
+ *		_technology		The client-side identifier of the technology, as
+ *							returned by emp.technology_make_identifier()
+ *		_priority		The priority to assign to the technology
+ *
+ * Returns:
+ *		?				TRUE if the technology was found, FALSE if it wasn't.
+ */
+DROP FUNCTION IF EXISTS emp.resprio_update_set( TEXT , INT );
+CREATE FUNCTION emp.resprio_update_set( _technology TEXT , _priority INT )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $resprio_update_set$
+BEGIN
+	
+	UPDATE rprio_update
+		SET _emptech_priority = _priority
+		WHERE _emptech_id = _technology;
+	RETURN FOUND;
+
+END;
+$resprio_update_set$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.resprio_update_set( TEXT , INT )
+	FROM PUBLIC;
+
+GRANT EXECUTE
+	ON FUNCTION emp.resprio_update_set( TEXT , INT )
+	TO :dbuser;
+
+
+/*
+ * Apply an update to research priorities
+ * ---------------------------------------
+ * 
+ * This stored procedure applies changes listed in the temporary research
+ * priority table by updating the actual table with the new values.
+ * 
+ * Returns:
+ *		?		TRUE if the update was valid, FALSE if one of the updated
+ *					values was incorrect
+ */
+DROP FUNCTION IF EXISTS emp.resprio_update_apply( );
+CREATE FUNCTION emp.resprio_update_apply( )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $resprio_update_apply$
+BEGIN
+	
+	UPDATE emp.technologies_v2
+		SET emptech_priority = _emptech_priority
+		FROM rprio_update
+		WHERE _empire_id = empire_id
+			AND _technology_name_id = technology_name_id;
+	RETURN TRUE;
+
+EXCEPTION
+
+	WHEN check_violation THEN
+		RETURN FALSE;
+
+END;
+$resprio_update_apply$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.resprio_update_apply( )
+	FROM PUBLIC;
+
+GRANT EXECUTE
+	ON FUNCTION emp.resprio_update_apply( )
+	TO :dbuser;
+
+
+
 --
 -- Returns a planet owner's empire size
 --
@@ -1041,4 +1197,4 @@ CREATE VIEW emp.technologies_v2_view
 
 GRANT SELECT
 	ON emp.technologies_v2_view
-	TO :dbuser;
\ No newline at end of file
+	TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/017-resprio-update-start.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/017-resprio-update-start.sql
new file mode 100644
index 0000000..5355333
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/017-resprio-update-start.sql
@@ -0,0 +1,105 @@
+/*
+ * Unit tests for emp.resprio_update_start()
+ */
+BEGIN;
+	\i utils/strings.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+
+	/* Create two empires */
+	SELECT _create_emp_names( 2 , 'emp' );
+	INSERT INTO emp.empires ( name_id , cash )
+		SELECT id , 200.0 FROM naming.empire_names;
+
+	/* Create 3 technologies after disabling unused fields */
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+	SELECT _create_test_strings( 3 , 'tech' );
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) ) ,
+			( _get_string( 'tech2' ) ) ,
+			( _get_string( 'tech3' ) );
+
+	/* Replace identifier function with something easier to check */
+	CREATE OR REPLACE FUNCTION emp.technology_make_identifier(
+				_empire INT , _technology TEXT , _visible BOOLEAN )
+		RETURNS TEXT
+		LANGUAGE SQL
+		STRICT IMMUTABLE
+		SECURITY DEFINER
+	AS $technology_make_identifier$
+		SELECT $1::TEXT || ',' || $2 || ',' || $3::TEXT;
+	$technology_make_identifier$;
+
+	/* Replace the visibility view with plain SELECT's from a table.
+	 */
+	CREATE TABLE _fake_visibility(
+		empire_id			INT ,
+		technology_name_id	INT ,
+		emptech_visible		BOOLEAN
+	);
+	CREATE OR REPLACE VIEW emp.technology_visibility_view
+		AS SELECT * FROM _fake_visibility;
+
+	/* Insert empire state and data for fake views */
+	INSERT INTO emp.technologies_v2 (
+			empire_id , technology_name_id ,
+			emptech_state , emptech_points , emptech_priority
+		) VALUES (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech1' ) ,
+			'KNOWN' , NULL , NULL
+		) , (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech2' ) ,
+			'RESEARCH' , 123 , 0
+		) , (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech3' ) ,
+			'RESEARCH' , 123 , 1
+		);
+	INSERT INTO _fake_visibility VALUES(
+		_get_emp_name( 'emp1' ) , _get_string( 'tech1' ) , TRUE 
+	) , (
+		_get_emp_name( 'emp1' ) , _get_string( 'tech2' ) , TRUE 
+	) , (
+		_get_emp_name( 'emp1' ) , _get_string( 'tech3' ) , FALSE 
+	);
+	
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 9 );
+
+	SELECT diag_test_name( 'emp.resprio_update_start() - Invalid empire - Return value' );
+	SELECT ok( NOT emp.resprio_update_start( _get_bad_emp_name() ) );
+	SELECT diag_test_name( 'emp.resprio_update_start() - Invalid empire - Temporary table exists' );
+	SELECT has_table( 'rprio_update' );
+	SELECT diag_test_name( 'emp.resprio_update_start() - Invalid empire - Temporary table is empty' );
+	SELECT is_empty( $$ SELECT * FROM rprio_update $$ );
+	DROP TABLE IF EXISTS rprio_update;
+
+	SELECT diag_test_name( 'emp.resprio_update_start() - Empire with no research - Return value' );
+	SELECT ok( NOT emp.resprio_update_start( _get_emp_name( 'emp2' ) ) );
+	SELECT diag_test_name( 'emp.resprio_update_start() - Empire with no research - Temporary table exists' );
+	SELECT has_table( 'rprio_update' );
+	SELECT diag_test_name( 'emp.resprio_update_start() - Empire with no research - Temporary table is empty' );
+	SELECT is_empty( $$ SELECT * FROM rprio_update $$ );
+	DROP TABLE IF EXISTS rprio_update;
+
+	SELECT diag_test_name( 'emp.resprio_update_start() - Empire with in-progress research - Return value' );
+	SELECT ok( emp.resprio_update_start( _get_emp_name( 'emp1' ) ) );
+	SELECT diag_test_name( 'emp.resprio_update_start() - Empire with in-progress research - Temporary table exists' );
+	SELECT has_table( 'rprio_update' );
+	SELECT diag_test_name( 'emp.resprio_update_start() - Empire with in-progress research - Temporary table contents' );
+	SELECT set_eq( $$
+		SELECT _empire_id , _technology_name_id , _emptech_id , _emptech_priority
+			FROM rprio_update
+	$$ , $$ VALUES(
+		_get_emp_name( 'emp1' ) , _get_string( 'tech2' ) , _get_emp_name( 'emp1' ) || ',tech2,true' , 0
+	) , (
+		_get_emp_name( 'emp1' ) , _get_string( 'tech3' ) , _get_emp_name( 'emp1' ) || ',tech3,false' , 1
+	) $$ );
+	DROP TABLE IF EXISTS rprio_update;
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/018-resprio-update-set.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/018-resprio-update-set.sql
new file mode 100644
index 0000000..e317a1c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/018-resprio-update-set.sql
@@ -0,0 +1,37 @@
+/*
+ * Unit tests for emp.resprio_update_set( )
+ */
+BEGIN;
+	/* Create a fake temporary table and insert some values */
+	CREATE TEMPORARY TABLE rprio_update(
+		_empire_id			INT ,
+		_technology_name_id	INT ,
+		_emptech_id			TEXT ,
+		_emptech_priority	INT
+	) ON COMMIT DROP;
+	INSERT INTO rprio_update
+		VALUES ( 1 , 1 , 'test' , 2 );
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 4 );
+
+	SELECT diag_test_name( 'emp.resprio_update_set() - Using a bad identifier - Return value' );
+	SELECT ok( NOT emp.resprio_update_set( 'bad identifier' , 3 ) );
+	SELECT diag_test_name( 'emp.resprio_update_set() - Using a bad identifier - Table contents' );
+	SELECT set_eq( $$
+		SELECT * FROM rprio_update
+	$$ , $$ VALUES(
+		1 , 1 , 'test' , 2
+	) $$ );
+
+	SELECT diag_test_name( 'emp.resprio_update_set() - Using a valid identifier - Return value' );
+	SELECT ok( emp.resprio_update_set( 'test' , 3 ) );
+	SELECT diag_test_name( 'emp.resprio_update_set() - Using a bad identifier - Table contents' );
+	SELECT set_eq( $$
+		SELECT * FROM rprio_update
+	$$ , $$ VALUES(
+		1 , 1 , 'test' , 3
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/019-resprio-update-apply.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/019-resprio-update-apply.sql
new file mode 100644
index 0000000..d7ca259
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/019-resprio-update-apply.sql
@@ -0,0 +1,82 @@
+/*
+ * Unit tests for emp.resprio_update_apply()
+ */
+BEGIN;
+	\i utils/strings.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+
+	/* Create a pair of empires, a technology, and some empire
+	 * research & technology records.
+	 */
+	SELECT _create_emp_names( 2 , 'emp' );
+	INSERT INTO emp.empires ( name_id , cash )
+		SELECT id , 200.0 FROM naming.empire_names;
+
+	ALTER TABLE defs.technologies
+		ALTER technology_category_id DROP NOT NULL ,
+		ALTER technology_discovery_id DROP NOT NULL ,
+		ALTER technology_description_id DROP NOT NULL ,
+		ALTER technology_price DROP NOT NULL ,
+		ALTER technology_points DROP NOT NULL;
+	SELECT _create_test_strings( 1 , 'tech' );
+	INSERT INTO defs.technologies ( technology_name_id )
+		VALUES ( _get_string( 'tech1' ) );
+
+	INSERT INTO emp.technologies_v2(
+			empire_id , technology_name_id ,
+			emptech_state , emptech_points , emptech_priority
+		) VALUES (
+			_get_emp_name( 'emp1' ) , _get_string( 'tech1' ) ,
+			'RESEARCH' , 12 , 2 
+		) , (
+			_get_emp_name( 'emp2' ) , _get_string( 'tech1' ) ,
+			'RESEARCH' , 12 , 2 
+		);
+
+	/* Create a fake temporary table */
+	CREATE TEMPORARY TABLE rprio_update(
+		_empire_id			INT ,
+		_technology_name_id	INT ,
+		_emptech_id			TEXT ,
+		_emptech_priority	INT
+	) ON COMMIT DROP;
+
+	-- ***** TESTS BEGIN HERE *****
+	SELECT plan( 4 );
+
+	INSERT INTO rprio_update
+		VALUES ( _get_emp_name( 'emp1' ) , _get_string( 'tech1' ) , 'ignored' , 1 );
+	SELECT diag_test_name( 'emp.resprio_update_apply() - Applying a valid update - Return value' );
+	SELECT ok( emp.resprio_update_apply( ) );
+	SELECT diag_test_name( 'emp.resprio_update_apply() - Applying a valid update - Table contents' );
+	SELECT set_eq( $$
+		SELECT empire_id , emptech_priority
+			FROM emp.technologies_v2
+			WHERE technology_name_id = _get_string( 'tech1' );
+	$$ , $$ VALUES(
+		_get_emp_name( 'emp1' ) , 1
+	) , (
+		_get_emp_name( 'emp2' ) , 2
+	) $$ );
+	DELETE FROM rprio_update;
+	UPDATE emp.technologies_v2
+		SET emptech_priority = 2
+		WHERE technology_name_id = _get_string( 'tech1' );
+
+	INSERT INTO rprio_update
+		VALUES ( _get_emp_name( 'emp1' ) , _get_string( 'tech1' ) , 'ignored' , 15 );
+	SELECT diag_test_name( 'emp.resprio_update_apply() - Applying an invalid update - Return value' );
+	SELECT ok( NOT emp.resprio_update_apply( ) );
+	SELECT diag_test_name( 'emp.resprio_update_apply() - Applying an invalid update - Table contents' );
+	SELECT set_eq( $$
+		SELECT empire_id , emptech_priority
+			FROM emp.technologies_v2
+	$$ , $$ VALUES(
+		_get_emp_name( 'emp1' ) , 2
+	) , (
+		_get_emp_name( 'emp2' ) , 2
+	) $$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/017-resprio-update-start.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/017-resprio-update-start.sql
new file mode 100644
index 0000000..cc97c3f
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/017-resprio-update-start.sql
@@ -0,0 +1,13 @@
+/*
+ * Test privileges on emp.resprio_update_start()
+ */
+BEGIN;
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'emp.resprio_update_start() - EXECUTE privilege' );
+	SELECT lives_ok( $$
+		SELECT emp.resprio_update_start( 1 );
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/018-resprio-update-set.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/018-resprio-update-set.sql
new file mode 100644
index 0000000..c0de9d5
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/018-resprio-update-set.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on emp.resprio_update_set()
+ */
+BEGIN;
+	SELECT emp.resprio_update_start( 1 );
+
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.resprio_update_set() - EXECUTE privilege' );
+	SELECT lives_ok( $$
+		SELECT emp.resprio_update_set( '' , 1 );
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/019-resprio-update-apply.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/019-resprio-update-apply.sql
new file mode 100644
index 0000000..93684c5
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/019-resprio-update-apply.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on emp.resprio_update_apply()
+ */
+BEGIN;
+	SELECT emp.resprio_update_start( 1 );
+
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.resprio_update_apply() - EXECUTE privilege' );
+	SELECT lives_ok( $$
+		SELECT emp.resprio_update_apply( );
+	$$ );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file

From c9d8a077bdf0409d182a134d8670eccbecf8bf1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 1 Mar 2012 11:50:40 +0100
Subject: [PATCH 67/94] Moved empire research SQL to separate file

* Empire research & technology SQL code was getting dense, so it was
moved to a separate file.
---
 .../parts/040-functions/040-empire.sql        | 407 -----------------
 .../040-functions/045-empire-research.sql     | 416 ++++++++++++++++++
 .../010-technology-implement.sql}             |   0
 .../020-technology-make-identifier.sql}       |   0
 .../030-resprio-update-start.sql}             |   0
 .../040-resprio-update-set.sql}               |   0
 .../050-resprio-update-apply.sql}             |   0
 .../060-technology-visibility-view.sql}       |   0
 .../070-technologies-view.sql}                |   0
 .../010-technology-implement.sql}             |   0
 .../020-technology-make-identifier.sql}       |   0
 .../030-resprio-update-start.sql}             |   0
 .../040-resprio-update-set.sql}               |   0
 .../050-resprio-update-apply.sql}             |   0
 .../060-technology-visibility-view.sql}       |   0
 .../070-technologies-view.sql}                |   0
 16 files changed, 416 insertions(+), 407 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{040-empire/015-technology-implement.sql => 045-empire-research/010-technology-implement.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{040-empire/016-technology-make-identifier.sql => 045-empire-research/020-technology-make-identifier.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{040-empire/017-resprio-update-start.sql => 045-empire-research/030-resprio-update-start.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{040-empire/018-resprio-update-set.sql => 045-empire-research/040-resprio-update-set.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{040-empire/019-resprio-update-apply.sql => 045-empire-research/050-resprio-update-apply.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{040-empire/040-technology-visibility-view.sql => 045-empire-research/060-technology-visibility-view.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{040-empire/050-technologies-view.sql => 045-empire-research/070-technologies-view.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{040-empire/015-technology-implement.sql => 045-empire-research/010-technology-implement.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{040-empire/016-technology-make-identifier.sql => 045-empire-research/020-technology-make-identifier.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{040-empire/017-resprio-update-start.sql => 045-empire-research/030-resprio-update-start.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{040-empire/018-resprio-update-set.sql => 045-empire-research/040-resprio-update-set.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{040-empire/019-resprio-update-apply.sql => 045-empire-research/050-resprio-update-apply.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{040-empire/040-technology-visibility-view.sql => 045-empire-research/060-technology-visibility-view.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{040-empire/050-technologies-view.sql => 045-empire-research/070-technologies-view.sql} (100%)

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 18cbe08..563c356 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -68,289 +68,6 @@ REVOKE EXECUTE
 	FROM PUBLIC;
 
 
-/*
- * Implements a technology
- * ------------------------
- * 
- * This stored procedure is called when an empire attempts to implement a
- * technology. It will check the empire's resources and the technology itself,
- * then mark it as implemented if necessary. It will also add new research
- * entries if necessary.
- * 
- * Parameters:
- *		_empire			The empire's identifier
- *		_technology		The string identifier for the technology to implement
- */
-DROP FUNCTION emp.technology_implement( INT , TEXT );
-CREATE FUNCTION emp.technology_implement( _empire INT , _technology TEXT )
-		RETURNS BOOLEAN
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE
-		SECURITY DEFINER
-	AS $technology_implement$
-
-DECLARE
-	_impl_data	RECORD;
-
-BEGIN
-
-	-- Access and lock the records
-	SELECT INTO _impl_data
-			technology_name_id , technology_price
-		FROM emp.empires _emp
-			INNER JOIN emp.technologies_v2 _tech
-				ON _tech.empire_id = _emp.name_id
-			INNER JOIN defs.technologies _def
-				USING ( technology_name_id )
-			INNER JOIN defs.strings _name
-				ON _def.technology_name_id = _name.id
-		WHERE _emp.name_id = _empire
-			AND _name.name = _technology
-			AND _tech.emptech_state = 'PENDING'
-			AND _emp.cash >= _def.technology_price
-		FOR UPDATE OF _emp , _tech
-		FOR SHARE OF _def;
-	IF NOT FOUND THEN
-		RETURN FALSE;
-	END IF;
-
-	-- Implement the technology
-	UPDATE emp.empires
-		SET cash = cash - _impl_data.technology_price
-		WHERE name_id = _empire;
-	UPDATE emp.technologies_v2
-		SET emptech_state = 'KNOWN'
-		WHERE empire_id = _empire
-			AND technology_name_id = _impl_data.technology_name_id;
-
-	-- Insert new research
-	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
-		SELECT _empire , _valid.technology_name_id
-			FROM  ( SELECT _tech.technology_name_id ,
-							( COUNT(*) = COUNT(_emptech.emptech_state) ) AS emptech_has_dependencies
-						FROM defs.technologies _tech
-							INNER JOIN defs.technology_dependencies _deps
-									USING ( technology_name_id )
-							LEFT OUTER JOIN emp.technologies_v2 _emptech
-									ON _emptech.technology_name_id = _deps.technology_name_id_depends
-										AND _emptech.emptech_state = 'KNOWN'
-										AND _emptech.empire_id = _empire
-						GROUP BY _tech.technology_name_id ) _valid
-				LEFT OUTER JOIN emp.technologies_v2 _emptech
-					ON _emptech.empire_id = _empire
-						AND _emptech.technology_name_id = _valid.technology_name_id
-			WHERE _emptech.empire_id IS NULL AND _valid.emptech_has_dependencies;
-
-	RETURN TRUE;
-END;
-$technology_implement$;
-
-REVOKE EXECUTE
-	ON FUNCTION emp.technology_implement( INT , TEXT )
-	FROM PUBLIC;
-
-GRANT EXECUTE
-	ON FUNCTION emp.technology_implement( INT , TEXT )
-	TO :dbuser;
-
-
-/*
- * Compute a technology identifier
- * --------------------------------
- * 
- * This function returns the identifier of a technology as seen from the
- * player's side. The identifier is either the string identifier for the
- * technology's name, or a MD5 hash including both the empire's identifier
- * and the string identifier for "unknown" technologies.
- * 
- * Parameters:
- *		_empire		The empire's identifier
- *		_technology	The technology's string identifier
- *		_visible	TRUE if the technology is supposed to be visible, FALSE
- *						otherwise
- *
- * Returns:
- *		?			The technology's client-side identifier
- */
-DROP FUNCTION IF EXISTS emp.technology_make_identifier( INT , TEXT , BOOLEAN );
-CREATE FUNCTION emp.technology_make_identifier(
-				_empire INT , _technology TEXT , _visible BOOLEAN )
-	RETURNS TEXT
-	LANGUAGE SQL
-	STRICT IMMUTABLE
-	SECURITY DEFINER
-AS $technology_make_identifier$
-
-	SELECT ( CASE
-		WHEN $3 THEN
-			$2
-		ELSE
-			md5( $1::TEXT || ' (making hash less obvious) ' || $2 )
-	END );
-
-$technology_make_identifier$;
-
-REVOKE EXECUTE
-	ON FUNCTION emp.technology_make_identifier( INT , TEXT , BOOLEAN )
-	FROM PUBLIC;
-
-
-/*
- * Initialise a research priorities update
- * ----------------------------------------
- * 
- * This stored procedure prepares a temporary table which is used to update
- * an empire's research priorities.
- *
- * Parameters:
- *		_empire		The empire's identifier
- *
- * Returns:
- *		?			TRUE if the empire exists, is not on vacation mode and has
- *						in-progress research, FALSE otherwise.
- */
-DROP FUNCTION IF EXISTS emp.resprio_update_start( INT );
-CREATE FUNCTION emp.resprio_update_start( _empire INT )
-		RETURNS BOOLEAN
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE
-		SECURITY DEFINER
-	AS $resprio_update_start$
-BEGIN
-
-	-- Create temporary table
-	CREATE TEMPORARY TABLE rprio_update(
-		_empire_id			INT ,
-		_technology_name_id	INT ,
-		_emptech_id			TEXT ,
-		_emptech_priority	INT
-	) ON COMMIT DROP;
-
-	-- Lock records and fill table
-	INSERT INTO rprio_update (
-			_empire_id , _technology_name_id , _emptech_id , _emptech_priority
-		) SELECT _emp.name_id , _tech.technology_name_id ,
-				emp.technology_make_identifier( empire_id , _str.name , emptech_visible ) ,
-				_etech.emptech_priority
-			FROM emp.empires _emp
-				INNER JOIN emp.technologies_v2 _etech
-					ON _etech.empire_id = _emp.name_id
-						AND _etech.emptech_state = 'RESEARCH'
-				INNER JOIN defs.technologies _tech
-					USING ( technology_name_id )
-				INNER JOIN emp.technology_visibility_view _vis
-					USING ( empire_id , technology_name_id )
-				INNER JOIN defs.strings _str
-					ON _str.id = _tech.technology_name_id
-				INNER JOIN naming.empire_names _ename
-					ON _ename.id = _emp.name_id
-				LEFT OUTER JOIN users.vacations _vac
-					ON _vac.account_id = _ename.owner_id
-						AND _vac.status = 'PROCESSED'
-			WHERE _emp.name_id = _empire AND _vac.account_id IS NULL
-			FOR UPDATE OF _emp , _etech
-			FOR SHARE OF _tech , _str , _ename;
-
-	RETURN FOUND;
-END;
-$resprio_update_start$;
-
-REVOKE EXECUTE
-	ON FUNCTION emp.resprio_update_start( INT )
-	FROM PUBLIC;
-
-GRANT EXECUTE
-	ON FUNCTION emp.resprio_update_start( INT )
-	TO :dbuser;
-
-
-/*
- * Set the priority of some research
- * ----------------------------------
- * 
- * This stored procedure updates the priority of some in-progress empire
- * research. It will only function correctly if emp.resprio_update_start() was
- * already executed.
- * 
- * Parameters:
- *		_technology		The client-side identifier of the technology, as
- *							returned by emp.technology_make_identifier()
- *		_priority		The priority to assign to the technology
- *
- * Returns:
- *		?				TRUE if the technology was found, FALSE if it wasn't.
- */
-DROP FUNCTION IF EXISTS emp.resprio_update_set( TEXT , INT );
-CREATE FUNCTION emp.resprio_update_set( _technology TEXT , _priority INT )
-		RETURNS BOOLEAN
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE
-		SECURITY DEFINER
-	AS $resprio_update_set$
-BEGIN
-	
-	UPDATE rprio_update
-		SET _emptech_priority = _priority
-		WHERE _emptech_id = _technology;
-	RETURN FOUND;
-
-END;
-$resprio_update_set$;
-
-REVOKE EXECUTE
-	ON FUNCTION emp.resprio_update_set( TEXT , INT )
-	FROM PUBLIC;
-
-GRANT EXECUTE
-	ON FUNCTION emp.resprio_update_set( TEXT , INT )
-	TO :dbuser;
-
-
-/*
- * Apply an update to research priorities
- * ---------------------------------------
- * 
- * This stored procedure applies changes listed in the temporary research
- * priority table by updating the actual table with the new values.
- * 
- * Returns:
- *		?		TRUE if the update was valid, FALSE if one of the updated
- *					values was incorrect
- */
-DROP FUNCTION IF EXISTS emp.resprio_update_apply( );
-CREATE FUNCTION emp.resprio_update_apply( )
-		RETURNS BOOLEAN
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE
-		SECURITY DEFINER
-	AS $resprio_update_apply$
-BEGIN
-	
-	UPDATE emp.technologies_v2
-		SET emptech_priority = _emptech_priority
-		FROM rprio_update
-		WHERE _empire_id = empire_id
-			AND _technology_name_id = technology_name_id;
-	RETURN TRUE;
-
-EXCEPTION
-
-	WHEN check_violation THEN
-		RETURN FALSE;
-
-END;
-$resprio_update_apply$;
-
-REVOKE EXECUTE
-	ON FUNCTION emp.resprio_update_apply( )
-	FROM PUBLIC;
-
-GRANT EXECUTE
-	ON FUNCTION emp.resprio_update_apply( )
-	TO :dbuser;
-
-
-
 --
 -- Returns a planet owner's empire size
 --
@@ -1074,127 +791,3 @@ CREATE VIEW emp.resources_view
 GRANT SELECT
 	ON emp.resources_view
 	TO :dbuser;
-
-
-
-/*
- * Technology visibility view
- * ---------------------------
- * 
- * This view can be used to determine whether entries from empires' research
- * and technologies table are fully visible or only displayed as "unknown
- * technologies".
- * 
- * Columns:
- *		empire_id			The empire's identifier
- *		technology_name_id	The technology's identifier
- *		emptech_visible		TRUE if the technology's details are visible,
- *								FALSE if they should be hidden
- */
-DROP VIEW IF EXISTS emp.technology_visibility_view CASCADE;
-CREATE VIEW emp.technology_visibility_view
-	AS SELECT empire_id , technology_name_id ,
-			( emptech_state <> 'RESEARCH'
-				OR emptech_points >= sys.get_constant( 'game.research.visibility.points' )
-				OR emptech_points / technology_points::DOUBLE PRECISION >= sys.get_constant( 'game.research.visibility.ratio' )
-			) AS emptech_visible
-		FROM emp.technologies_v2
-			INNER JOIN defs.technologies
-				USING ( technology_name_id );
-
-
-/*
- * Empire research and technologies
- * ---------------------------------
- * 
- * This view lists empires' research and technologies, along with their
- * current state.
- * 
- * Columns:
- *		empire_id			The empire's identifier
- *		emptech_id			An identifier for the technology, which is either
- *								the string identifier of the technology's name
- *								or a MD5 hash if the technology is not
- *								supposed to be visible
- *		emptech_state		The state of the technology, straight from the
- *								empire technology table
- *		emptech_visible		Whether the technology is supposed to be visible
- *								or not
- *		technology_category	The string identifier of the technology's category
- *		technology_name		The string identifier of the technology's name,
- *								or NULL if the technology is not supposed to
- *								be visible
- *		technology_description	The string identifier of the technology's name,
- *									or NULL if the technology is not supposed
- *									to be visible
- *		emptech_points		The amount of points accumulated while researching
- *								the technology, or NULL if the technology is
- *								not supposed to be visible
- *		emptech_priority	The current research priority, or NULL if the
- *								technology is no longer being researched
- *		emptech_ratio		The percentage of points accumulated while
- *								researching the technology, or NULL if the
- *								technology is no longer being researched
- *		technology_price	The monetary price of the technology, or NULL if
- *								the technology is not supposed to be visible
- *		technology_dependencies	The technology's dependencies from the
- *									dependencies view
- */
-DROP VIEW IF EXISTS emp.technologies_v2_view CASCADE;
-CREATE VIEW emp.technologies_v2_view
-	AS SELECT empire_id ,
-			emp.technology_make_identifier( empire_id , _name_str.name , emptech_visible ) AS emptech_id ,
-			emptech_state ,
-			emptech_visible ,
-			_cat_str.name AS technology_category ,
-			( CASE
-				WHEN emptech_visible THEN
-					_name_str.name
-				ELSE
-					NULL::TEXT
-			END ) AS technology_name ,
-			( CASE
-				WHEN emptech_visible THEN
-					_descr_str.name
-				ELSE
-					NULL::TEXT
-			END ) AS technology_description ,
-			( CASE
-				WHEN emptech_state <> 'RESEARCH' then
-					technology_points
-				WHEN emptech_visible THEN
-					FLOOR( emptech_points )::BIGINT
-				ELSE
-					NULL::BIGINT
-			END ) AS emptech_points ,
-			emptech_priority ,
-			( CASE
-				WHEN emptech_state = 'RESEARCH' THEN
-					FLOOR( 100.0 * emptech_points / technology_points::DOUBLE PRECISION )::INT
-				ELSE
-					NULL::INT
-			END ) AS emptech_ratio ,
-			( CASE
-				WHEN emptech_visible THEN
-					technology_price
-				ELSE
-					NULL::INT
-			END ) AS technology_price ,
-			technology_dependencies
-		FROM emp.technologies_v2
-			INNER JOIN emp.technology_visibility_view
-				USING ( technology_name_id , empire_id )
-			INNER JOIN defs.technologies _tech
-				USING ( technology_name_id )
-			INNER JOIN defs.technology_dependencies_view
-				USING ( technology_name_id )
-			INNER JOIN defs.strings _name_str
-				ON _name_str.id = _tech.technology_name_id
-			INNER JOIN defs.strings _cat_str
-				ON _cat_str.id = _tech.technology_category_id
-			INNER JOIN defs.strings _descr_str
-				ON _descr_str.id = _tech.technology_description_id;
-
-GRANT SELECT
-	ON emp.technologies_v2_view
-	TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
new file mode 100644
index 0000000..0c32b71
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
@@ -0,0 +1,416 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Empire research functions and views
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+
+/*
+ * Implements a technology
+ * ------------------------
+ * 
+ * This stored procedure is called when an empire attempts to implement a
+ * technology. It will check the empire's resources and the technology itself,
+ * then mark it as implemented if necessary. It will also add new research
+ * entries if necessary.
+ * 
+ * Parameters:
+ *		_empire			The empire's identifier
+ *		_technology		The string identifier for the technology to implement
+ */
+DROP FUNCTION emp.technology_implement( INT , TEXT );
+CREATE FUNCTION emp.technology_implement( _empire INT , _technology TEXT )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $technology_implement$
+
+DECLARE
+	_impl_data	RECORD;
+
+BEGIN
+
+	-- Access and lock the records
+	SELECT INTO _impl_data
+			technology_name_id , technology_price
+		FROM emp.empires _emp
+			INNER JOIN emp.technologies_v2 _tech
+				ON _tech.empire_id = _emp.name_id
+			INNER JOIN defs.technologies _def
+				USING ( technology_name_id )
+			INNER JOIN defs.strings _name
+				ON _def.technology_name_id = _name.id
+		WHERE _emp.name_id = _empire
+			AND _name.name = _technology
+			AND _tech.emptech_state = 'PENDING'
+			AND _emp.cash >= _def.technology_price
+		FOR UPDATE OF _emp , _tech
+		FOR SHARE OF _def;
+	IF NOT FOUND THEN
+		RETURN FALSE;
+	END IF;
+
+	-- Implement the technology
+	UPDATE emp.empires
+		SET cash = cash - _impl_data.technology_price
+		WHERE name_id = _empire;
+	UPDATE emp.technologies_v2
+		SET emptech_state = 'KNOWN'
+		WHERE empire_id = _empire
+			AND technology_name_id = _impl_data.technology_name_id;
+
+	-- Insert new research
+	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+		SELECT _empire , _valid.technology_name_id
+			FROM  ( SELECT _tech.technology_name_id ,
+							( COUNT(*) = COUNT(_emptech.emptech_state) ) AS emptech_has_dependencies
+						FROM defs.technologies _tech
+							INNER JOIN defs.technology_dependencies _deps
+									USING ( technology_name_id )
+							LEFT OUTER JOIN emp.technologies_v2 _emptech
+									ON _emptech.technology_name_id = _deps.technology_name_id_depends
+										AND _emptech.emptech_state = 'KNOWN'
+										AND _emptech.empire_id = _empire
+						GROUP BY _tech.technology_name_id ) _valid
+				LEFT OUTER JOIN emp.technologies_v2 _emptech
+					ON _emptech.empire_id = _empire
+						AND _emptech.technology_name_id = _valid.technology_name_id
+			WHERE _emptech.empire_id IS NULL AND _valid.emptech_has_dependencies;
+
+	RETURN TRUE;
+END;
+$technology_implement$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.technology_implement( INT , TEXT )
+	FROM PUBLIC;
+
+GRANT EXECUTE
+	ON FUNCTION emp.technology_implement( INT , TEXT )
+	TO :dbuser;
+
+
+/*
+ * Compute a technology identifier
+ * --------------------------------
+ * 
+ * This function returns the identifier of a technology as seen from the
+ * player's side. The identifier is either the string identifier for the
+ * technology's name, or a MD5 hash including both the empire's identifier
+ * and the string identifier for "unknown" technologies.
+ * 
+ * Parameters:
+ *		_empire		The empire's identifier
+ *		_technology	The technology's string identifier
+ *		_visible	TRUE if the technology is supposed to be visible, FALSE
+ *						otherwise
+ *
+ * Returns:
+ *		?			The technology's client-side identifier
+ */
+DROP FUNCTION IF EXISTS emp.technology_make_identifier( INT , TEXT , BOOLEAN );
+CREATE FUNCTION emp.technology_make_identifier(
+				_empire INT , _technology TEXT , _visible BOOLEAN )
+	RETURNS TEXT
+	LANGUAGE SQL
+	STRICT IMMUTABLE
+	SECURITY DEFINER
+AS $technology_make_identifier$
+
+	SELECT ( CASE
+		WHEN $3 THEN
+			$2
+		ELSE
+			md5( $1::TEXT || ' (making hash less obvious) ' || $2 )
+	END );
+
+$technology_make_identifier$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.technology_make_identifier( INT , TEXT , BOOLEAN )
+	FROM PUBLIC;
+
+
+/*
+ * Initialise a research priorities update
+ * ----------------------------------------
+ * 
+ * This stored procedure prepares a temporary table which is used to update
+ * an empire's research priorities.
+ *
+ * Parameters:
+ *		_empire		The empire's identifier
+ *
+ * Returns:
+ *		?			TRUE if the empire exists, is not on vacation mode and has
+ *						in-progress research, FALSE otherwise.
+ */
+DROP FUNCTION IF EXISTS emp.resprio_update_start( INT );
+CREATE FUNCTION emp.resprio_update_start( _empire INT )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $resprio_update_start$
+BEGIN
+
+	-- Create temporary table
+	CREATE TEMPORARY TABLE rprio_update(
+		_empire_id			INT ,
+		_technology_name_id	INT ,
+		_emptech_id			TEXT ,
+		_emptech_priority	INT
+	) ON COMMIT DROP;
+
+	-- Lock records and fill table
+	INSERT INTO rprio_update (
+			_empire_id , _technology_name_id , _emptech_id , _emptech_priority
+		) SELECT _emp.name_id , _tech.technology_name_id ,
+				emp.technology_make_identifier( empire_id , _str.name , emptech_visible ) ,
+				_etech.emptech_priority
+			FROM emp.empires _emp
+				INNER JOIN emp.technologies_v2 _etech
+					ON _etech.empire_id = _emp.name_id
+						AND _etech.emptech_state = 'RESEARCH'
+				INNER JOIN defs.technologies _tech
+					USING ( technology_name_id )
+				INNER JOIN emp.technology_visibility_view _vis
+					USING ( empire_id , technology_name_id )
+				INNER JOIN defs.strings _str
+					ON _str.id = _tech.technology_name_id
+				INNER JOIN naming.empire_names _ename
+					ON _ename.id = _emp.name_id
+				LEFT OUTER JOIN users.vacations _vac
+					ON _vac.account_id = _ename.owner_id
+						AND _vac.status = 'PROCESSED'
+			WHERE _emp.name_id = _empire AND _vac.account_id IS NULL
+			FOR UPDATE OF _emp , _etech
+			FOR SHARE OF _tech , _str , _ename;
+
+	RETURN FOUND;
+END;
+$resprio_update_start$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.resprio_update_start( INT )
+	FROM PUBLIC;
+
+GRANT EXECUTE
+	ON FUNCTION emp.resprio_update_start( INT )
+	TO :dbuser;
+
+
+/*
+ * Set the priority of some research
+ * ----------------------------------
+ * 
+ * This stored procedure updates the priority of some in-progress empire
+ * research. It will only function correctly if emp.resprio_update_start() was
+ * already executed.
+ * 
+ * Parameters:
+ *		_technology		The client-side identifier of the technology, as
+ *							returned by emp.technology_make_identifier()
+ *		_priority		The priority to assign to the technology
+ *
+ * Returns:
+ *		?				TRUE if the technology was found, FALSE if it wasn't.
+ */
+DROP FUNCTION IF EXISTS emp.resprio_update_set( TEXT , INT );
+CREATE FUNCTION emp.resprio_update_set( _technology TEXT , _priority INT )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $resprio_update_set$
+BEGIN
+	
+	UPDATE rprio_update
+		SET _emptech_priority = _priority
+		WHERE _emptech_id = _technology;
+	RETURN FOUND;
+
+END;
+$resprio_update_set$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.resprio_update_set( TEXT , INT )
+	FROM PUBLIC;
+
+GRANT EXECUTE
+	ON FUNCTION emp.resprio_update_set( TEXT , INT )
+	TO :dbuser;
+
+
+/*
+ * Apply an update to research priorities
+ * ---------------------------------------
+ * 
+ * This stored procedure applies changes listed in the temporary research
+ * priority table by updating the actual table with the new values.
+ * 
+ * Returns:
+ *		?		TRUE if the update was valid, FALSE if one of the updated
+ *					values was incorrect
+ */
+DROP FUNCTION IF EXISTS emp.resprio_update_apply( );
+CREATE FUNCTION emp.resprio_update_apply( )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $resprio_update_apply$
+BEGIN
+	
+	UPDATE emp.technologies_v2
+		SET emptech_priority = _emptech_priority
+		FROM rprio_update
+		WHERE _empire_id = empire_id
+			AND _technology_name_id = technology_name_id;
+	RETURN TRUE;
+
+EXCEPTION
+
+	WHEN check_violation THEN
+		RETURN FALSE;
+
+END;
+$resprio_update_apply$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.resprio_update_apply( )
+	FROM PUBLIC;
+
+GRANT EXECUTE
+	ON FUNCTION emp.resprio_update_apply( )
+	TO :dbuser;
+
+
+
+
+/*
+ * Technology visibility view
+ * ---------------------------
+ * 
+ * This view can be used to determine whether entries from empires' research
+ * and technologies table are fully visible or only displayed as "unknown
+ * technologies".
+ * 
+ * Columns:
+ *		empire_id			The empire's identifier
+ *		technology_name_id	The technology's identifier
+ *		emptech_visible		TRUE if the technology's details are visible,
+ *								FALSE if they should be hidden
+ */
+DROP VIEW IF EXISTS emp.technology_visibility_view CASCADE;
+CREATE VIEW emp.technology_visibility_view
+	AS SELECT empire_id , technology_name_id ,
+			( emptech_state <> 'RESEARCH'
+				OR emptech_points >= sys.get_constant( 'game.research.visibility.points' )
+				OR emptech_points / technology_points::DOUBLE PRECISION >= sys.get_constant( 'game.research.visibility.ratio' )
+			) AS emptech_visible
+		FROM emp.technologies_v2
+			INNER JOIN defs.technologies
+				USING ( technology_name_id );
+
+
+
+/*
+ * Empire research and technologies
+ * ---------------------------------
+ * 
+ * This view lists empires' research and technologies, along with their
+ * current state.
+ * 
+ * Columns:
+ *		empire_id			The empire's identifier
+ *		emptech_id			An identifier for the technology, which is either
+ *								the string identifier of the technology's name
+ *								or a MD5 hash if the technology is not
+ *								supposed to be visible
+ *		emptech_state		The state of the technology, straight from the
+ *								empire technology table
+ *		emptech_visible		Whether the technology is supposed to be visible
+ *								or not
+ *		technology_category	The string identifier of the technology's category
+ *		technology_name		The string identifier of the technology's name,
+ *								or NULL if the technology is not supposed to
+ *								be visible
+ *		technology_description	The string identifier of the technology's name,
+ *									or NULL if the technology is not supposed
+ *									to be visible
+ *		emptech_points		The amount of points accumulated while researching
+ *								the technology, or NULL if the technology is
+ *								not supposed to be visible
+ *		emptech_priority	The current research priority, or NULL if the
+ *								technology is no longer being researched
+ *		emptech_ratio		The percentage of points accumulated while
+ *								researching the technology, or NULL if the
+ *								technology is no longer being researched
+ *		technology_price	The monetary price of the technology, or NULL if
+ *								the technology is not supposed to be visible
+ *		technology_dependencies	The technology's dependencies from the
+ *									dependencies view
+ */
+DROP VIEW IF EXISTS emp.technologies_v2_view CASCADE;
+CREATE VIEW emp.technologies_v2_view
+	AS SELECT empire_id ,
+			emp.technology_make_identifier( empire_id , _name_str.name , emptech_visible ) AS emptech_id ,
+			emptech_state ,
+			emptech_visible ,
+			_cat_str.name AS technology_category ,
+			( CASE
+				WHEN emptech_visible THEN
+					_name_str.name
+				ELSE
+					NULL::TEXT
+			END ) AS technology_name ,
+			( CASE
+				WHEN emptech_visible THEN
+					_descr_str.name
+				ELSE
+					NULL::TEXT
+			END ) AS technology_description ,
+			( CASE
+				WHEN emptech_state <> 'RESEARCH' then
+					technology_points
+				WHEN emptech_visible THEN
+					FLOOR( emptech_points )::BIGINT
+				ELSE
+					NULL::BIGINT
+			END ) AS emptech_points ,
+			emptech_priority ,
+			( CASE
+				WHEN emptech_state = 'RESEARCH' THEN
+					FLOOR( 100.0 * emptech_points / technology_points::DOUBLE PRECISION )::INT
+				ELSE
+					NULL::INT
+			END ) AS emptech_ratio ,
+			( CASE
+				WHEN emptech_visible THEN
+					technology_price
+				ELSE
+					NULL::INT
+			END ) AS technology_price ,
+			technology_dependencies
+		FROM emp.technologies_v2
+			INNER JOIN emp.technology_visibility_view
+				USING ( technology_name_id , empire_id )
+			INNER JOIN defs.technologies _tech
+				USING ( technology_name_id )
+			INNER JOIN defs.technology_dependencies_view
+				USING ( technology_name_id )
+			INNER JOIN defs.strings _name_str
+				ON _name_str.id = _tech.technology_name_id
+			INNER JOIN defs.strings _cat_str
+				ON _cat_str.id = _tech.technology_category_id
+			INNER JOIN defs.strings _descr_str
+				ON _descr_str.id = _tech.technology_description_id;
+
+GRANT SELECT
+	ON emp.technologies_v2_view
+	TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/015-technology-implement.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/010-technology-implement.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/015-technology-implement.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/010-technology-implement.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/016-technology-make-identifier.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/020-technology-make-identifier.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/016-technology-make-identifier.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/020-technology-make-identifier.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/017-resprio-update-start.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/030-resprio-update-start.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/017-resprio-update-start.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/030-resprio-update-start.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/018-resprio-update-set.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/040-resprio-update-set.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/018-resprio-update-set.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/040-resprio-update-set.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/019-resprio-update-apply.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/050-resprio-update-apply.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/019-resprio-update-apply.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/050-resprio-update-apply.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/040-technology-visibility-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/060-technology-visibility-view.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/040-technology-visibility-view.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/060-technology-visibility-view.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/050-technologies-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-technologies-view.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/050-technologies-view.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-technologies-view.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/015-technology-implement.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/010-technology-implement.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/015-technology-implement.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/010-technology-implement.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/016-technology-make-identifier.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/020-technology-make-identifier.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/016-technology-make-identifier.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/020-technology-make-identifier.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/017-resprio-update-start.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/030-resprio-update-start.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/017-resprio-update-start.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/030-resprio-update-start.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/018-resprio-update-set.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/040-resprio-update-set.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/018-resprio-update-set.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/040-resprio-update-set.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/019-resprio-update-apply.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/050-resprio-update-apply.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/019-resprio-update-apply.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/050-resprio-update-apply.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/040-technology-visibility-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/060-technology-visibility-view.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/040-technology-visibility-view.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/060-technology-visibility-view.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/050-technologies-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/070-technologies-view.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/040-empire/050-technologies-view.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/070-technologies-view.sql

From a14601df377df096091f8dc0ee950c0cef2f3567 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 1 Mar 2012 12:42:05 +0100
Subject: [PATCH 68/94] Research weight computations

* Added game.research.weightBase constant

* Added view which computes the weights for each in-progress research

* Added view which computes total weights per empire
---
 .../lw/beans/sys/ConstantsRegistrarBean.java  |  6 ++-
 .../040-functions/045-empire-research.sql     | 38 +++++++++++++++++
 .../070-research-weights-view.sql             | 42 +++++++++++++++++++
 .../080-research-total-weights-view.sql       | 35 ++++++++++++++++
 ...ies-view.sql => 090-technologies-view.sql} |  0
 .../070-research-weights-view.sql             | 15 +++++++
 .../080-research-total-weights-view.sql       | 15 +++++++
 ...ies-view.sql => 090-technologies-view.sql} |  0
 8 files changed, 150 insertions(+), 1 deletion(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-research-weights-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/080-research-total-weights-view.sql
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/{070-technologies-view.sql => 090-technologies-view.sql} (100%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/070-research-weights-view.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/080-research-total-weights-view.sql
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/{070-technologies-view.sql => 090-technologies-view.sql} (100%)

diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 5fd56cc..fe2fbd9 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -128,7 +128,7 @@ public class ConstantsRegistrarBean
 		
 		// Research
 		String[] rcNames = {
-				"basePoints" ,  "visibility.points" , "visibility.ratio"
+				"basePoints" ,  "visibility.points" , "visibility.ratio" , "weightBase"
 		};
 		for ( int i = 0 ; i < wcNames.length ; i++ ) {
 			rcNames[ i ] = "game.research." + rcNames[ i ];
@@ -140,6 +140,10 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( rcNames[ 1 ] , cat , cDesc , 2500.0 , 0.01 , true ) );
 		cDesc = "Completion ratio above which a technology becomes visible.";
 		defs.add( new ConstantDefinition( rcNames[ 2 ] , cat , cDesc , 0.10 , 0.01 , 0.99 ) );
+		cDesc = "Technology weight base value. This value is taken to the Xth power (where X is a priority) "
+				+ "to compute the actual weight when determining how research points are distributed between "
+				+ "an empire's in-progress research.";
+		defs.add( new ConstantDefinition( rcNames[ 3 ] , cat , cDesc , 10.0 , 1.0 , true ) );
 
 		// Vacation mode
 		cDesc = "Initial vacation credits.";
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
index 0c32b71..2cf07f9 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
@@ -319,6 +319,44 @@ CREATE VIEW emp.technology_visibility_view
 
 
 
+/*
+ * Research weights
+ * -----------------
+ * 
+ * This view computes weights based on priorities for each in-progress
+ * research.
+ * 
+ * Columns:
+ *		empire_id			The empire's identifier
+ *		technology_name_id	The technology's identifier
+ *		emptech_weight		The weight
+ */
+DROP VIEW IF EXISTS emp.research_weights_view CASCADE;
+CREATE VIEW emp.research_weights_view
+	AS SELECT empire_id , technology_name_id ,
+			POW( sys.get_constant( 'game.research.weightBase' ) ,
+					emptech_priority ) AS emptech_weight
+		FROM emp.technologies_v2
+		WHERE emptech_state = 'RESEARCH';
+
+/*
+ * Total research weights
+ * -----------------------
+ * 
+ * This view computes total research weights for each empire with in-progress
+ * research.
+ * 
+ * Columns:
+ *		empire_id				The empire's identifier
+ *		emptech_total_weight	The total research weight
+ */
+DROP VIEW IF EXISTS emp.research_total_weights_view CASCADE;
+CREATE VIEW emp.research_total_weights_view
+	AS SELECT empire_id , SUM( emptech_weight ) AS emptech_total_weight
+		FROM emp.research_weights_view
+		GROUP BY empire_id;
+
+
 /*
  * Empire research and technologies
  * ---------------------------------
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-research-weights-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-research-weights-view.sql
new file mode 100644
index 0000000..37f9dac
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-research-weights-view.sql
@@ -0,0 +1,42 @@
+/*
+ * Test emp.research_weights_view
+ */
+BEGIN;
+	/* Remove foreign keys from the empire research table */
+	ALTER TABLE emp.technologies_v2
+		DROP CONSTRAINT fk_emptech_empire ,
+		DROP CONSTRAINT fk_emptech_technology;
+
+	/* Insert a few records */
+	DELETE FROM emp.technologies_v2;
+	INSERT INTO emp.technologies_v2 (
+			empire_id , technology_name_id ,
+			emptech_state , emptech_points , emptech_priority
+		) VALUES
+			( 1 , 1 , 'RESEARCH' , 0 , 0 ) ,
+			( 1 , 2 , 'RESEARCH' , 0 , 1 ) ,
+			( 1 , 3 , 'RESEARCH' , 0 , 2 ) ,
+			( 1 , 4 , 'RESEARCH' , 0 , 3 ) ,
+			( 1 , 5 , 'RESEARCH' , 0 , 4 ) ,
+			( 1 , 6 , 'PENDING' , NULL , NULL ) ,
+			( 1 , 7 , 'KNOWN' , NULL , NULL );
+
+	/* Set the constant */
+	SELECT sys.uoc_constant( 'game.research.weightBase' , '(test)' , 'Test' , 10.0 );
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 2 );
+
+	SELECT diag_test_name( 'emp.research_weights_view - Rows present' );
+	SELECT is( COUNT(*)::INT , 5 )
+		FROM emp.research_weights_view;
+
+	SELECT diag_test_name( 'emp.research_weights_view - weight = game.research.weightBase ^ priority' );
+	SELECT is_empty( $$
+		SELECT * FROM emp.research_weights_view
+			WHERE emptech_weight IS NULL
+				OR emptech_weight <> POW( 10 , technology_name_id - 1 )
+	$$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/080-research-total-weights-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/080-research-total-weights-view.sql
new file mode 100644
index 0000000..6e05a66
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/080-research-total-weights-view.sql
@@ -0,0 +1,35 @@
+/*
+ * Test emp.total_mining_weights_view
+ */
+BEGIN;
+	/* Create a table which will server as an alternate source for
+	 * emp.total_weights_view ; the table is not temporary (PostgreSQL
+	 * won't allow replacing the view otherwise), but will be dropped
+	 * on rollback anyway. 
+	 */
+	CREATE TABLE fake_weights(
+		empire_id			INT ,
+		technology_name_id	INT ,
+		emptech_weight		DOUBLE PRECISION
+	);
+	
+	CREATE OR REPLACE VIEW emp.research_weights_view
+		AS SELECT * FROM fake_weights;
+
+	/* Insert fake records for two different empires */
+	INSERT INTO fake_weights VALUES
+		( 1 , 0 , 1 ) ,
+		( 1 , 1 , 2 ) ,
+		( 2 , 0 , 4 ) ,
+		( 2 , 1 , 5 );
+
+	/***** TESTS BEGIN HERE *****/
+	SELECT plan( 1 );
+	
+	SELECT set_eq(
+		$$ SELECT * FROM emp.research_total_weights_view $$ ,
+		$$ VALUES ( 1 , 3.0 ) , ( 2 , 9.0 ) $$
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-technologies-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/090-technologies-view.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-technologies-view.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/090-technologies-view.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/070-research-weights-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/070-research-weights-view.sql
new file mode 100644
index 0000000..807548c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/070-research-weights-view.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on emp.research_weights_view
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.research_weights_view - No SELECT privilege' );
+	SELECT throws_ok( $$
+		SELECT * FROM emp.research_weights_view;
+	$$ , 42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/080-research-total-weights-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/080-research-total-weights-view.sql
new file mode 100644
index 0000000..d2767f6
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/080-research-total-weights-view.sql
@@ -0,0 +1,15 @@
+/*
+ * Test privileges on emp.research_total_weights_view
+ */
+BEGIN;
+	\i utils/strings.sql
+
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'emp.research_total_weights_view - No SELECT privilege' );
+	SELECT throws_ok( $$
+		SELECT * FROM emp.research_total_weights_view;
+	$$ , 42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/070-technologies-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/090-technologies-view.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/070-technologies-view.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/090-technologies-view.sql

From c7949e41ccce2c8666260ec0a5f8880c6b5828d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 2 Apr 2012 13:29:43 +0200
Subject: [PATCH 69/94] Research update

* Implemented new research update. Old research system no longer
updates.

* Fixed a major bug in the constants registrar

* Added game.research.vacation constant (determines the rate of
research when players are in vacation mode)
---
 .../lw/beans/sys/ConstantsRegistrarBean.java  |  39 +--
 ...50-computation.sql => 043-computation.sql} |   0
 .../040-functions/045-empire-research.sql     |  52 +++-
 .../parts/050-updates/020-empire-research.sql | 236 +++++++++++++-----
 .../010-get-random-part.sql                   |   0
 .../020-adjust-production.sql                 |   0
 .../010-gu-research-get-empires.sql           |  24 ++
 .../010-get-random-part.sql                   |   0
 .../020-adjust-production.sql                 |   0
 .../010-gu-research-get-empires.sql           |  11 +
 .../setup-gu-research-get-empires-test.sql    | 137 ++++++++++
 11 files changed, 414 insertions(+), 85 deletions(-)
 rename legacyworlds-server-data/db-structure/parts/040-functions/{050-computation.sql => 043-computation.sql} (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{050-computation => 043-computation}/010-get-random-part.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/admin/040-functions/{050-computation => 043-computation}/020-adjust-production.sql (100%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/050-updates/020-empire-research/010-gu-research-get-empires.sql
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{050-computation => 043-computation}/010-get-random-part.sql (100%)
 rename legacyworlds-server-data/db-structure/tests/user/040-functions/{050-computation => 043-computation}/020-adjust-production.sql (100%)
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/050-updates/020-empire-research/010-gu-research-get-empires.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-research-get-empires-test.sql

diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index fe2fbd9..13fa90f 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -53,7 +53,7 @@ public class ConstantsRegistrarBean
 				true ) );
 		cDesc = "Initial universe size (offset relative to the centre).";
 		defs.add( new ConstantDefinition( "game.universe.initialSize" , "Universe" , cDesc , 1.0 , 1.0 , true ) );
-		
+
 		// Natural resources
 		cDesc = "Global recovery rate multiplier for natural resources. This should be kept close to 0.";
 		defs.add( new ConstantDefinition( "game.resources.recovery" , "Natural resources" , cDesc , 0.01 , 0.00001 ,
@@ -125,12 +125,12 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( wcNames[ 6 ] , cat , cDesc , 0.50 , 0.01 , true ) );
 		cDesc = "Proportion of queue investments that is recovered when flushing the queue.";
 		defs.add( new ConstantDefinition( wcNames[ 7 ] , cat , cDesc , 0.1 , 0.01 , 1.0 ) );
-		
+
 		// Research
 		String[] rcNames = {
-				"basePoints" ,  "visibility.points" , "visibility.ratio" , "weightBase"
+				"basePoints" , "visibility.points" , "visibility.ratio" , "weightBase" , "vacation"
 		};
-		for ( int i = 0 ; i < wcNames.length ; i++ ) {
+		for ( int i = 0 ; i < rcNames.length ; i++ ) {
 			rcNames[ i ] = "game.research." + rcNames[ i ];
 		}
 		cat = "Research & technologies";
@@ -144,6 +144,8 @@ public class ConstantsRegistrarBean
 				+ "to compute the actual weight when determining how research points are distributed between "
 				+ "an empire's in-progress research.";
 		defs.add( new ConstantDefinition( rcNames[ 3 ] , cat , cDesc , 10.0 , 1.0 , true ) );
+		cDesc = "Research points multiplier used when the player is on vacation.";
+		defs.add( new ConstantDefinition( rcNames[ 4 ] , cat , cDesc , 0.1 , 0.01 , 0.99 ) );
 
 		// Vacation mode
 		cDesc = "Initial vacation credits.";
@@ -188,7 +190,7 @@ public class ConstantsRegistrarBean
 		// Ticker
 		cDesc = "Interval between ticks with the highest frequency, in milliseconds.";
 		defs.add( new ConstantDefinition( "ticker.interval" , "Ticker" , cDesc , 5000.0 , 1000.0 , true ) );
-		
+
 		// Accounts
 		cDesc = "Minimal interval between address change requests (seconds)";
 		defs.add( new ConstantDefinition( "accounts.acrDelay" , "Accounts" , cDesc , 14400.0 , 1.0 , true ) );
@@ -202,25 +204,32 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( "accounts.banDelay" , "Accounts" , cDesc , 178000.0 , 3600.0 , true ) );
 		cDesc = "Delay before a ban request expires (seconds)";
 		defs.add( new ConstantDefinition( "accounts.banExpiration" , "Accounts" , cDesc , oneWeek , 3600.0 , true ) );
-		
+
 		// Accounts - warnings
 		cDesc = "Amount of warnings that triggers an automatic ban request.";
-		defs.add( new ConstantDefinition( "accounts.warnings.autoBan" , "Accounts - Warnings" , cDesc , 3.0 , 1.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.warnings.autoBan" , "Accounts - Warnings" , cDesc , 3.0 , 1.0 ,
+				true ) );
 		cDesc = "Period after a warning is received during which additional warnings will be ignored (seconds).";
-		defs.add( new ConstantDefinition( "accounts.warnings.grace" , "Accounts - Warnings" , cDesc , 7200.0 , 60.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.warnings.grace" , "Accounts - Warnings" , cDesc , 7200.0 , 60.0 ,
+				true ) );
 		cDesc = "Time after which warnings are decreased (expressed in units as defined by a.w.expiration.units).";
-		defs.add( new ConstantDefinition( "accounts.warnings.expiration" , "Accounts - Warnings" , cDesc , 60.0 , 1.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.warnings.expiration" , "Accounts - Warnings" , cDesc , 60.0 , 1.0 ,
+				true ) );
 		cDesc = "Units used to express warning expiration time (seconds).";
-		defs.add( new ConstantDefinition( "accounts.warnings.expiration.units" , "Accounts - Warnings" , cDesc , 86400.0 , 1.0 , true ) );
-		
+		defs.add( new ConstantDefinition( "accounts.warnings.expiration.units" , "Accounts - Warnings" , cDesc ,
+				86400.0 , 1.0 , true ) );
+
 		// Account inactivity
 		cDesc = "Time units (seconds)";
-		defs.add( new ConstantDefinition( "accounts.inactivity.units" , "Accounts - Inactivity" , cDesc , oneWeek , 3600.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.inactivity.units" , "Accounts - Inactivity" , cDesc , oneWeek ,
+				3600.0 , true ) );
 		cDesc = "Time after which the inactivity warning e-mail is to be sent, expressed using units defined by a.i.units.";
-		defs.add( new ConstantDefinition( "accounts.inactivity.warningMail" , "Accounts - Inactivity" , cDesc , 3.0 , 1.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.inactivity.warningMail" , "Accounts - Inactivity" , cDesc , 3.0 ,
+				1.0 , true ) );
 		cDesc = "Time between the inactivity warning e-mail and actual account deletion, expressed using units defined by a.i.units.";
-		defs.add( new ConstantDefinition( "accounts.inactivity.deletion" , "Accounts - Inactivity" , cDesc , 1.0 , 1.0 , true ) );
-		
+		defs.add( new ConstantDefinition( "accounts.inactivity.deletion" , "Accounts - Inactivity" , cDesc , 1.0 , 1.0 ,
+				true ) );
+
 		// Bug reports
 		cDesc = "Amount of credits granted for low priority bug reports.";
 		defs.add( new ConstantDefinition( "bugtracker.lowCredits" , "Bug tracking system" , cDesc , 1.0 , 1.0 , true ) );
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/050-computation.sql b/legacyworlds-server-data/db-structure/parts/040-functions/043-computation.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/parts/040-functions/050-computation.sql
rename to legacyworlds-server-data/db-structure/parts/040-functions/043-computation.sql
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
index 2cf07f9..c25bd64 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
@@ -21,7 +21,7 @@
  *		_empire			The empire's identifier
  *		_technology		The string identifier for the technology to implement
  */
-DROP FUNCTION emp.technology_implement( INT , TEXT );
+DROP FUNCTION IF EXISTS emp.technology_implement( INT , TEXT );
 CREATE FUNCTION emp.technology_implement( _empire INT , _technology TEXT )
 		RETURNS BOOLEAN
 		LANGUAGE PLPGSQL
@@ -291,6 +291,56 @@ GRANT EXECUTE
 
 
 
+/*
+ * Compute an empire's total research points
+ * ------------------------------------------
+ * 
+ * Obtain an empire's total research points by adding the happiness-adjusted
+ * value for each planet, then applying the global modifier for vacation mode
+ * if necessary.
+ *
+ * FIXME: time factor is hard-coded
+ * 
+ * Parameters:
+ *		_empire			The empire's identifier
+ *		_on_vacation	TRUE if the player is on vacation, FALSE otherwise
+ *
+ * Returns:
+ *		?				The amount of research points.
+ */
+DROP FUNCTION IF EXISTS emp.research_get_points( INT , BOOLEAN );
+CREATE FUNCTION emp.research_get_points( _empire INT , _on_vacation BOOLEAN )
+	RETURNS DOUBLE PRECISION
+	LANGUAGE SQL
+	STRICT STABLE
+	SECURITY INVOKER
+AS $research_get_points$
+
+	SELECT SUM( verse.adjust_production(
+					_planet.population * sys.get_constant( 'game.research.basePoints' ) ,
+					_happiness.current / _planet.population
+				) ) * ( CASE
+					WHEN $2 THEN
+						sys.get_constant( 'game.research.vacation' )
+					ELSE
+						1.0
+				END )::DOUBLE PRECISION
+		FROM emp.planets _emp_planet
+			INNER JOIN verse.planets _planet
+				ON _emp_planet.planet_id = _planet.name_id
+			INNER JOIN verse.planet_happiness _happiness
+				USING ( planet_id )
+		WHERE _emp_planet.empire_id = $1;
+
+$research_get_points$;
+
+REVOKE EXECUTE
+	ON FUNCTION emp.research_get_points( INT , BOOLEAN )
+	FROM PUBLIC;
+
+
+
+
 
 /*
  * Technology visibility view
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
index 152f83f..fa73323 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
@@ -3,90 +3,188 @@
 --
 -- Game updates - empire research
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2012, DeepClone Development
 -- --------------------------------------------------------
 
 
-CREATE OR REPLACE FUNCTION sys.process_empire_research_updates( c_tick BIGINT )
+/*
+ * Empire update data
+ * -------------------
+ * 
+ * This type can be used to return empires along with their "on vacation"
+ * status.
+ * 
+ * FIXME: it should probably be somewhere else, but for now this is the only
+ * file that uses it.
+ */
+DROP TYPE IF EXISTS sys.empire_update_type CASCADE;
+CREATE TYPE sys.empire_update_type AS (
+	/* The empire's identifier */
+	empire_id		INT ,
+
+	/* TRUE if the player is on vacation, FALSE otherwise */
+	on_vacation		BOOLEAN
+);
+
+
+/*
+ * Lock records and list empires which require a research update
+ * --------------------------------------------------------------
+ * 
+ * This function will lock all records needed for a research update, and
+ * return the list of empires to be updated. These empires, in addition to
+ * being marked for update, must possess planets and have in-progress
+ * research.
+ * 
+ * Parameters:
+ *		_tick		The identifier of the current update cycle
+ *
+ * Returns a set of:
+ * 		empire_id	The empire's identifier
+ *		on_vacation	TRUE if the player is on vacation, FALSE otherwise
+ */
+DROP FUNCTION IF EXISTS sys.gu_research_get_empires( BIGINT ) CASCADE;
+CREATE FUNCTION sys.gu_research_get_empires( _tick BIGINT )
+	RETURNS SETOF sys.empire_update_type
+	LANGUAGE SQL
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $gu_research_get_empires$
+
+	SELECT DISTINCT * FROM (
+		SELECT _empire.name_id AS empire_id ,
+				( _vacation.status IS NOT NULL AND
+					_vacation.status = 'PROCESSED' ) AS on_vacation
+			FROM sys.updates _upd_sys
+				INNER JOIN emp.empires_updates _emp_update
+					USING ( updtgt_id , updtype_id , update_id )
+				INNER JOIN emp.empires _empire
+					USING ( name_id )
+				INNER JOIN emp.technologies_v2 _emp_tech
+					ON _emp_tech.empire_id = _empire.name_id
+				INNER JOIN defs.technologies _tech
+					USING ( technology_name_id )
+				INNER JOIN emp.planets _emp_planet
+					USING ( empire_id )
+				INNER JOIN verse.planets _planet
+					ON _emp_planet.planet_id = _planet.name_id
+				INNER JOIN verse.planet_happiness _happiness
+					USING ( planet_id )
+				INNER JOIN naming.empire_names _emp_name
+					ON _emp_name.id = _empire.name_id
+				INNER JOIN users.credentials _user
+					ON _user.address_id = _emp_name.owner_id
+				LEFT OUTER JOIN users.vacations _vacation
+					ON _vacation.account_id = _emp_name.owner_id
+			WHERE _upd_sys.update_last = $1
+				AND _upd_sys.update_state = 'PROCESSING'
+				AND _emp_tech.emptech_state = 'RESEARCH'
+			FOR UPDATE OF _upd_sys , _emp_update , _emp_tech
+			FOR SHARE OF _empire , _tech , _emp_planet , _planet ,
+				_happiness , _emp_name , _user
+		) _sub;
+
+$gu_research_get_empires$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.gu_research_get_empires( BIGINT )
+	FROM PUBLIC;
+
+
+/*
+ * Update research for a single empire
+ * ------------------------------------
+ *
+ * This stored procedure updates research points for all in-progress research
+ * of a single empire.
+ *
+ * Parameters:
+ * 		_empire_id		The empire's identifier
+ *		_on_vacation	TRUE if the player is on vacation, FALSE otherwise
+ */
+DROP FUNCTION IF EXISTS sys.gu_research_update_empire( INT , BOOLEAN ) CASCADE;
+CREATE FUNCTION sys.gu_research_update_empire( _empire INT , _on_vacation BOOLEAN )
 		RETURNS VOID
+		LANGUAGE PLPGSQL
 		STRICT VOLATILE
 		SECURITY INVOKER
-	AS $$
+	AS $gu_research_update_empire$
+
 DECLARE
-	rec			RECORD;
-	r_points	REAL;
-	tu_rec		RECORD;
+	_points		DOUBLE PRECISION;
+	_record		RECORD;
+
 BEGIN
-	-- Lock empires for update and planets for share
-	PERFORM e.name_id
-		FROM sys.updates _upd_sys
-			INNER JOIN emp.empires_updates eu
-				USING ( updtgt_id , updtype_id , update_id )
-			INNER JOIN emp.empires e USING ( name_id )
-			INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
-			INNER JOIN verse.planets p ON p.name_id = ep.planet_id
-		WHERE _upd_sys.update_last = c_tick
-			AND _upd_sys.update_state = 'PROCESSING'
-		FOR UPDATE OF e
-		FOR SHARE OF ep , p;
 
-	-- Process empires
-	FOR rec IN SELECT e.name_id AS id , ( v.status = 'PROCESSED' ) AS on_vacation ,
-						sum( p.population ) AS population
-					FROM sys.updates _upd_sys
-						INNER JOIN emp.empires_updates eu
-							USING ( updtgt_id , updtype_id , update_id )
-						INNER JOIN emp.empires e USING ( name_id )
-						INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
-						INNER JOIN verse.planets p ON p.name_id = ep.planet_id
-						INNER JOIN naming.empire_names en ON en.id = e.name_id
-						LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
-					WHERE _upd_sys.update_last = c_tick
-						AND _upd_sys.update_state = 'PROCESSING'
-					GROUP BY e.name_id , v.status
+	_points := emp.research_get_points( _empire , _on_vacation );	
+	FOR _record IN
+			SELECT _emp_tech.technology_name_id , _emp_tech.emptech_points , _emp_tech.emptech_priority ,
+					_points * _weights.emptech_weight / ( _totals.emptech_total_weight * 1440 ) AS emptech_new_points ,
+					_def.technology_points::DOUBLE PRECISION AS technology_points
+				FROM emp.technologies_v2 _emp_tech
+					INNER JOIN emp.research_weights_view _weights
+						USING ( empire_id , technology_name_id )
+					INNER JOIN emp.research_total_weights_view _totals
+						USING ( empire_id )
+					INNER JOIN defs.technologies _def
+						USING ( technology_name_id )
+				WHERE _emp_tech.empire_id = _empire
+					AND _emp_tech.emptech_state = 'RESEARCH'
 	LOOP
-		-- Insert any missing tech line
-		INSERT INTO emp.technologies ( empire_id , line_id )
-			SELECT rec.id , l.name_id
-				FROM tech.lines l
-					LEFT OUTER JOIN emp.technologies t
-						ON t.line_id = l.name_id AND t.empire_id = rec.id
-				WHERE t.empire_id IS NULL;
 
-		-- Compute research output
-		r_points := rec.population * sys.get_constant( 'game.work.rpPerPopUnit' ) / 1440.0;
-		IF rec.on_vacation
-		THEN
-			r_points := r_points / sys.get_constant( 'vacation.researchDivider' );
+		IF _record.emptech_points + _record.emptech_new_points >= _record.technology_points THEN
+			UPDATE emp.technologies_v2
+				SET emptech_state = 'PENDING' ,
+					emptech_points = NULL ,
+					emptech_priority = NULL
+				WHERE technology_name_id = _record.technology_name_id
+					AND empire_id = _empire;
+
+		ELSE
+			UPDATE emp.technologies_v2 
+				SET emptech_points = emptech_points + _record.emptech_new_points
+				WHERE technology_name_id = _record.technology_name_id
+					AND empire_id = _empire;
 		END IF;
 
-		-- Update technologies where:
-		-- 1) the level actually exists and
-		-- 2) accumulated points haven't reach the level's
-		FOR tu_rec IN SELECT t.line_id AS line_id , t.accumulated AS accumulated ,
-							l.points AS points , ( l.points - t.accumulated ) AS diff ,
-							l.id AS level_id
-						FROM emp.technologies t
-							INNER JOIN tech.levels l ON l.line_id = t.line_id
-								AND l.level = t.level AND t.accumulated < l.points
-						WHERE t.empire_id = rec.id
-						FOR UPDATE OF t
-		LOOP
-			UPDATE emp.technologies t SET accumulated = ( CASE
-						WHEN tu_rec.diff <= r_points THEN tu_rec.points
-						ELSE tu_rec.accumulated + r_points
-					END )
-				WHERE t.line_id = tu_rec.line_id AND t.empire_id = rec.id;
-
-			-- Send message
-			IF tu_rec.diff <= r_points
-			THEN
-				PERFORM events.tech_ready_event( rec.id , tu_rec.level_id );
-			END IF;
-		END LOOP;
 	END LOOP;
+
 END;
-$$ LANGUAGE plpgsql;
+$gu_research_update_empire$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.gu_research_update_empire( INT , BOOLEAN )
+	FROM PUBLIC;
+
+
+
+/*
+ * Process a batch of empire research updates
+ * -------------------------------------------
+ * 
+ * Update all empires in the batch which have both in-progress research and
+ * planets.
+ * 
+ * Parameters:
+ *		_tick		The identifier of the current update cycle
+ */
+DROP FUNCTION IF EXISTS sys.process_empire_research_updates( BIGINT ) CASCADE;
+CREATE FUNCTION sys.process_empire_research_updates( _tick BIGINT )
+	RETURNS VOID
+	LANGUAGE SQL
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $process_empire_research_updates$
+
+	SELECT sys.gu_research_update_empire( empire_id , on_vacation )
+		FROM sys.gu_research_get_empires( $1::BIGINT );
+
+$process_empire_research_updates$;
+
+REVOKE EXECUTE
+	ON FUNCTION sys.process_empire_research_updates( BIGINT )
+	FROM PUBLIC;
+
 
 SELECT sys.register_update_type( 'Empires' , 'EmpireResearch' ,
 		'Empire research points are being attributed to technologies.' ,
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/010-get-random-part.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/043-computation/010-get-random-part.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/010-get-random-part.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/043-computation/010-get-random-part.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/020-adjust-production.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/043-computation/020-adjust-production.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/admin/040-functions/050-computation/020-adjust-production.sql
rename to legacyworlds-server-data/db-structure/tests/admin/040-functions/043-computation/020-adjust-production.sql
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/020-empire-research/010-gu-research-get-empires.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/020-empire-research/010-gu-research-get-empires.sql
new file mode 100644
index 0000000..ad02ba8
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/020-empire-research/010-gu-research-get-empires.sql
@@ -0,0 +1,24 @@
+/*
+ * Test the sys.gu_research_get_empires() function
+ */
+BEGIN;
+	\i utils/common-setup/setup-gu-research-get-empires-test.sql
+
+	SELECT plan( 2 );
+
+	SELECT diag_test_name( 'gu_research_get_empires() - Selected empires and vacation state' );
+	SELECT set_eq( $$
+		SELECT * FROM sys.gu_research_get_empires( 0::BIGINT );
+	$$ , $$ VALUES (
+		_get_emp_name( 'emp4') , FALSE
+	) , (
+		_get_emp_name( 'emp5') , TRUE
+	) $$ );
+
+	SELECT diag_test_name( 'gu_research_get_empires() - No results with "bad" update identifier' );
+	SELECT is_empty( $$
+		SELECT * FROM sys.gu_research_get_empires( 12::BIGINT );
+	$$ );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/010-get-random-part.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/043-computation/010-get-random-part.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/010-get-random-part.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/043-computation/010-get-random-part.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/020-adjust-production.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/043-computation/020-adjust-production.sql
similarity index 100%
rename from legacyworlds-server-data/db-structure/tests/user/040-functions/050-computation/020-adjust-production.sql
rename to legacyworlds-server-data/db-structure/tests/user/040-functions/043-computation/020-adjust-production.sql
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/020-empire-research/010-gu-research-get-empires.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/020-empire-research/010-gu-research-get-empires.sql
new file mode 100644
index 0000000..0830456
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/020-empire-research/010-gu-research-get-empires.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.gu_research_get_empires()
+ */
+BEGIN;
+	SELECT plan( 1 );
+
+	SELECT diag_test_name( 'sys.gu_research_get_empires() - Privileges' );
+	SELECT throws_ok( 'SELECT sys.gu_research_get_empires( 0::BIGINT )' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-research-get-empires-test.sql b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-research-get-empires-test.sql
new file mode 100644
index 0000000..104a22e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-research-get-empires-test.sql
@@ -0,0 +1,137 @@
+/*
+ * Common setup for research update locking and empire selection
+ * --------------------------------------------------------------
+ *
+ * Empires are selected when:
+ *		- they are part of the current update batch,
+ *		- they possess planets,
+ *		- they have in-progress research entries.
+ * All related data is locked: the update entries, empire, empire technology
+ * records, technology definitions, planets (including ownership records and
+ * happiness records), as well as the empire's name and associated account.
+ */
+
+-- Make sure the only update type left is EmpireResearch
+DELETE FROM sys.update_types
+	WHERE updtype_name <> 'EmpireResearch';
+
+\i utils/strings.sql
+\i utils/resources.sql
+\i utils/accounts.sql
+\i utils/naming.sql
+\i utils/universe.sql
+
+-- Create some technology definitions, dropping constraints on unused columns
+ALTER TABLE defs.technologies
+	ALTER technology_description_id DROP NOT NULL ,
+	ALTER technology_discovery_id DROP NOT NULL ,
+	ALTER technology_category_id DROP NOT NULL ,
+	ALTER technology_price DROP NOT NULL;
+SELECT _create_test_strings( 3 , 'tech' );
+INSERT INTO defs.technologies ( technology_name_id , technology_points )
+	SELECT id , 100.0 FROM defs.strings
+		WHERE name LIKE 'tech%';
+
+-- Create the empire names and planets
+SELECT _create_emp_names( 6 , 'emp' );
+SELECT _create_raw_planets( 4 , 'planet' );
+INSERT INTO verse.planet_happiness( planet_id , target , current )
+	SELECT id , 0.75 , 1500
+		FROM naming.map_names
+		WHERE name LIKE 'planet%';
+
+
+/*
+ * Empire 1 has no planets and no in-progress research. It will not be
+ * selected or locked.
+ */
+INSERT INTO emp.empires( name_id , cash )
+	VALUES( _get_emp_name( 'emp1' ) , 100.0 );
+INSERT INTO emp.technologies_v2 (
+		empire_id , technology_name_id ,
+		emptech_state , emptech_points , emptech_priority )
+	SELECT _get_emp_name( 'emp1' ) , technology_name_id , 'KNOWN' , NULL , NULL
+		FROM defs.technologies;
+
+/*
+ * Empire 2 has planets, but no in-progress research. It will not be
+ * selected.
+ */
+INSERT INTO emp.empires( name_id , cash )
+	VALUES( _get_emp_name( 'emp2' ) , 100.0 );
+INSERT INTO emp.technologies_v2 (
+		empire_id , technology_name_id ,
+		emptech_state , emptech_points , emptech_priority )
+	SELECT _get_emp_name( 'emp2' ) , technology_name_id , 'KNOWN' , NULL , NULL
+		FROM defs.technologies;
+INSERT INTO emp.planets( empire_id , planet_id )
+	VALUES( _get_emp_name( 'emp2' ) , _get_map_name( 'planet1' ) );
+
+/*
+ * Empire 3 has in-progress research, but no planets. It will not be selected.
+ */
+INSERT INTO emp.empires( name_id , cash )
+	VALUES( _get_emp_name( 'emp3' ) , 100.0 );
+INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+	SELECT _get_emp_name( 'emp3' ) , technology_name_id
+		FROM defs.technologies;
+
+/*
+ * Empire 4 has in-progress research and planets. It has no vacation mode
+ * record. It will be selected.
+ */
+INSERT INTO emp.empires( name_id , cash )
+	VALUES( _get_emp_name( 'emp4' ) , 100.0 );
+INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+	SELECT _get_emp_name( 'emp4' ) , technology_name_id
+		FROM defs.technologies;
+INSERT INTO emp.planets( empire_id , planet_id )
+	VALUES( _get_emp_name( 'emp4' ) , _get_map_name( 'planet2' ) );
+
+/*
+ * Empire 5 has in-progress research, planets and a vacation mode record.
+ * It will be selected.
+ * 
+ * Note: we need to add an entry to users.active_accounts for vacation mode
+ * records to work.
+ */
+INSERT INTO emp.empires( name_id , cash )
+	VALUES( _get_emp_name( 'emp5' ) , 100.0 );
+INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+	SELECT _get_emp_name( 'emp5' ) , technology_name_id
+		FROM defs.technologies;
+INSERT INTO emp.planets( empire_id , planet_id )
+	VALUES( _get_emp_name( 'emp5' ) , _get_map_name( 'planet3' ) );
+INSERT INTO users.active_accounts( credentials_id ,vacation_credits )
+	SELECT id , 12
+		FROM users.addresses
+		WHERE address = 'emp5@example.org';
+INSERT INTO users.vacations ( account_id , since , status )
+	SELECT id , NOW() , 'PROCESSED'
+		FROM users.addresses
+		WHERE address = 'emp5@example.org';
+
+/*
+ * Empire 6 is similar to empire 4, but will not be marked for update in the
+ * current batch.
+ */
+INSERT INTO emp.empires( name_id , cash )
+	VALUES( _get_emp_name( 'emp6' ) , 100.0 );
+INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+	SELECT _get_emp_name( 'emp6' ) , technology_name_id
+		FROM defs.technologies;
+INSERT INTO emp.planets( empire_id , planet_id )
+	VALUES( _get_emp_name( 'emp6' ) , _get_map_name( 'planet4' ) );
+
+
+/* Speaking of current batch - set it up */
+UPDATE sys.updates su
+	SET update_state = 'PROCESSING' , update_last = 0
+	FROM emp.empires_updates eu
+	WHERE eu.update_id = su.update_id
+		AND eu.name_id <>  _get_emp_name( 'emp6' );
+UPDATE sys.updates su
+	SET update_state = 'PROCESSED' , update_last = 0
+	FROM emp.empires_updates eu
+	WHERE eu.update_id = su.update_id
+		AND eu.name_id =  _get_emp_name( 'emp6' );
\ No newline at end of file

From 9a7bc031714d89f45e174021a6b9aa4ffb0df542 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 5 Apr 2012 11:25:08 +0200
Subject: [PATCH 70/94] Improved I18N support:

* GamePageData now includes the selected language's code.
* Added support for multiple fetches in one call to the Translator
service.
---
 .../com/deepclone/lw/beans/i18n/I18NData.java |  17 ++-
 .../lw/beans/i18n/TranslatorBean.java         |  46 +++++-
 .../lw/beans/empire/EmpireDAOBean.java        |  13 +-
 .../lw/beans/empire/EmpireManagementBean.java |   2 +-
 .../empire/GeneralInformationRowMapper.java   |  36 +++++
 .../parts/040-functions/040-empire.sql        |  13 +-
 .../lw/sqld/game/GeneralInformation.java      |  68 +++++++--
 .../lw/interfaces/i18n/Translator.java        |  30 +++-
 .../lw/cmd/player/gdata/GamePageData.java     | 141 ++++++++++++++++--
 9 files changed, 316 insertions(+), 50 deletions(-)
 create mode 100644 legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/GeneralInformationRowMapper.java

diff --git a/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
index 77bca8a..3ebac74 100644
--- a/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
+++ b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/I18NData.java
@@ -239,6 +239,20 @@ class I18NData
 	}
 
 
+	/**
+	 * Access the store for some language
+	 * 
+	 * @param language
+	 *            the language to access
+	 * 
+	 * @return the language store, or <code>null</code> if the language is not defined.
+	 */
+	LanguageStore getStore( String language )
+	{
+		return this.languages.get( language );
+	}
+
+
 	/**
 	 * Sets or creates the translation for a given language/string identifier pair.
 	 * 
@@ -257,7 +271,8 @@ class I18NData
 	 * @throws IllegalArgumentException
 	 *             if the string does not exist
 	 */
-	String setTranslation( final int administrator , final String language , final String string , final String translation )
+	String setTranslation( final int administrator , final String language , final String string ,
+			final String translation )
 	{
 		// Get existing translation
 		LanguageStore store = this.languages.get( language );
diff --git a/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
index 7ce8046..4e97fbf 100644
--- a/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
+++ b/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
@@ -1,7 +1,10 @@
 package com.deepclone.lw.beans.i18n;
 
 
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -13,10 +16,13 @@ import com.deepclone.lw.interfaces.i18n.UnknownStringException;
 
 
 /**
+ * Translation component
+ * 
+ * <p>
  * The translator bean's implementation uses the contents of the {@link I18NData} instance, which it
  * only accesses in read-only mode.
  * 
- * @author tseeker
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
  */
 public class TranslatorBean
 		implements Translator
@@ -83,6 +89,23 @@ public class TranslatorBean
 	}
 
 
+	/* Documentation in Translator interface */
+	@Override
+	public String getLanguageName( String language )
+			throws UnknownLanguageException
+	{
+		this.data.readLock( ).lock( );
+		try {
+			if ( !this.data.isLanguageComplete( language ) ) {
+				throw new UnknownLanguageException( language );
+			}
+			return this.data.getLanguageName( language );
+		} finally {
+			this.data.readLock( ).unlock( );
+		}
+	}
+
+
 	/* Documentation in Translator interface */
 	@Override
 	public String translate( String language , String string )
@@ -103,17 +126,30 @@ public class TranslatorBean
 	}
 
 
-	/* Documentation in Translator interface */
+	/**
+	 * Access the store for the specified language then extract translations
+	 */
 	@Override
-	public String getLanguageName( String language )
-			throws UnknownLanguageException
+	public Map< String , String > translate( String language , Collection< String > strings )
+			throws UnknownStringException , UnknownLanguageException
 	{
 		this.data.readLock( ).lock( );
 		try {
 			if ( !this.data.isLanguageComplete( language ) ) {
 				throw new UnknownLanguageException( language );
 			}
-			return this.data.getLanguageName( language );
+			
+			LanguageStore store = this.data.getStore( language );
+			HashMap< String , String > result = new HashMap< String , String >( );
+			for ( String identifier : strings ) {
+				String value = store.getTranslation( identifier );
+				if ( value == null ) {
+					throw new UnknownStringException( identifier );
+				}
+				result.put( identifier , value );
+			}
+
+			return result;
 		} finally {
 			this.data.readLock( ).unlock( );
 		}
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
index 43524db..e515d04 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
@@ -79,19 +79,8 @@ public class EmpireDAOBean
 	public GeneralInformation getInformation( int empireId )
 	{
 		String sql = "SELECT * FROM emp.general_information WHERE id = ?";
-		RowMapper< GeneralInformation > mapper = new RowMapper< GeneralInformation >( ) {
-			@Override
-			public GeneralInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				String st = rs.getString( "status" );
-				Character status = ( st == null ) ? null : st.charAt( 0 );
-				return new GeneralInformation( status , rs.getString( "name" ) , rs.getString( "alliance" ) , rs
-						.getLong( "cash" ) , rs.getLong( "game_time" ) , rs.getInt( "account_id" ) );
-			}
-		};
 		try {
-			return this.dTemplate.queryForObject( sql , mapper , empireId );
+			return this.dTemplate.queryForObject( sql , new GeneralInformationRowMapper( ) , empireId );
 		} catch ( EmptyResultDataAccessException e ) {
 			return null;
 		}
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
index 7a4c6fe..a940ef7 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
@@ -117,7 +117,7 @@ public class EmpireManagementBean
 
 		return new GamePageData( generalInformation.getName( ) , generalInformation.getStatus( ) ,
 				generalInformation.getTag( ) , generalInformation.getCash( ) , generalInformation.getNextTick( ) ,
-				planets , rlTime );
+				planets , rlTime, generalInformation.getLanguage( ) );
 	}
 
 
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/GeneralInformationRowMapper.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/GeneralInformationRowMapper.java
new file mode 100644
index 0000000..fd8be8a
--- /dev/null
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/GeneralInformationRowMapper.java
@@ -0,0 +1,36 @@
+package com.deepclone.lw.beans.empire;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.sqld.game.GeneralInformation;
+
+
+
+class GeneralInformationRowMapper
+		implements RowMapper< GeneralInformation >
+{
+
+	@Override
+	public GeneralInformation mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		GeneralInformation info = new GeneralInformation( );
+		
+		info.setName( rs.getString( "name" ) );
+		info.setTag( rs.getString( "alliance" ) );
+		info.setCash( rs.getLong( "cash" ) );
+		info.setLanguage( rs.getString( "language" ) );
+		info.setNextTick( rs.getLong( "game_time" ) );
+		info.setAccountId( rs.getInt( "account_id" ) );
+
+		String statusString = rs.getString( "status" );
+		info.setStatus( rs.wasNull( ) ? null : statusString.charAt( 0 ) );
+
+		return info;
+	}
+
+}
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 563c356..16443aa 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -506,6 +506,7 @@ CREATE VIEW emp.enemies
 -- General information view
 --
 
+DROP VIEW IF EXISTS emp.general_information CASCADE;
 CREATE VIEW emp.general_information
 	AS SELECT e.name_id AS id , en.name AS name ,
 			  ( CASE
@@ -516,13 +517,17 @@ CREATE VIEW emp.general_information
 			  END ) AS status ,
 			  e.cash AS cash , a.tag AS alliance ,
 			  st.next_tick AS game_time ,
-			  av.id AS account_id
+			  av.id AS account_id ,
+			  av.language AS language
 		FROM emp.empires e
-			INNER JOIN naming.empire_names en ON en.id = e.name_id
-			INNER JOIN users.accounts_view av ON av.id = en.owner_id
+			INNER JOIN naming.empire_names en
+				ON en.id = e.name_id
+			INNER JOIN users.accounts_view av
+				ON av.id = en.owner_id
 			LEFT OUTER JOIN emp.alliance_members am
 				ON am.empire_id = e.name_id AND NOT am.is_pending
-			LEFT OUTER JOIN emp.alliances a ON a.id = am.alliance_id
+			LEFT OUTER JOIN emp.alliances a
+				ON a.id = am.alliance_id
 			CROSS JOIN sys.status st;
 
 GRANT SELECT ON emp.general_information TO :dbuser;
diff --git a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
index 82b7270..afd37d3 100644
--- a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
+++ b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
@@ -3,11 +3,12 @@ package com.deepclone.lw.sqld.game;
 
 public class GeneralInformation
 {
+	private String name;
+
+	private String language;
 
 	private Character status;
 
-	private String name;
-
 	private String tag;
 
 	private long cash;
@@ -17,50 +18,87 @@ public class GeneralInformation
 	private int accountId;
 
 
-	public GeneralInformation( Character status , String name , String tag , long cash , long nextTick , int accountId )
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	public void setName( String name )
 	{
-		this.status = status;
 		this.name = name;
-		this.tag = tag;
-		this.cash = cash;
-		this.nextTick = nextTick;
-		this.accountId = accountId;
+	}
+
+
+	public String getLanguage( )
+	{
+		return this.language;
+	}
+
+
+	public void setLanguage( String language )
+	{
+		this.language = language;
 	}
 
 
 	public Character getStatus( )
 	{
-		return status;
+		return this.status;
 	}
 
 
-	public String getName( )
+	public void setStatus( Character status )
 	{
-		return name;
+		this.status = status;
 	}
 
 
 	public String getTag( )
 	{
-		return tag;
+		return this.tag;
+	}
+
+
+	public void setTag( String tag )
+	{
+		this.tag = tag;
 	}
 
 
 	public long getCash( )
 	{
-		return cash;
+		return this.cash;
+	}
+
+
+	public void setCash( long cash )
+	{
+		this.cash = cash;
 	}
 
 
 	public long getNextTick( )
 	{
-		return nextTick;
+		return this.nextTick;
+	}
+
+
+	public void setNextTick( long nextTick )
+	{
+		this.nextTick = nextTick;
 	}
 
 
 	public int getAccountId( )
 	{
-		return accountId;
+		return this.accountId;
+	}
+
+
+	public void setAccountId( int accountId )
+	{
+		this.accountId = accountId;
 	}
 
 }
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
index 9c5eb39..92ada50 100644
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
@@ -1,6 +1,8 @@
 package com.deepclone.lw.interfaces.i18n;
 
 
+import java.util.Collection;
+import java.util.Map;
 import java.util.Set;
 
 
@@ -8,12 +10,12 @@ import java.util.Set;
 /**
  * Translator service interface
  * 
+ * <p>
  * This interface defines the methods available on the Translator service. One such service should
  * be present on all nodes. All Translator service instances are managed by the I18NManager service,
  * which is shared between nodes and notifies Translator instances of database updates.
  * 
- * @author tseeker
- * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
  */
 public interface Translator
 {
@@ -62,4 +64,28 @@ public interface Translator
 	 */
 	public String translate( String language , String string )
 			throws UnknownStringException , UnknownLanguageException;
+
+
+	/**
+	 * Translate multiple strings based on their identifiers
+	 * 
+	 * <p>
+	 * This method must be implemented to allow "en masse" string translations. It will fetch the
+	 * translations in a given language for an arbitrary quantity of string identifiers, returning
+	 * the results as a map of identifiers to translations.
+	 * 
+	 * @param language
+	 *            the identifier of the language to translate to
+	 * @param strings
+	 *            a collection of string identifiers
+	 * 
+	 * @return a map associating string identifiers to translations
+	 * 
+	 * @throws UnknownStringException
+	 *             if one of the string identifiers does not match a string
+	 * @throws UnknownLanguageException
+	 *             if the language does not exist or is not supported
+	 */
+	public Map< String , String > translate( String language , Collection< String > strings )
+			throws UnknownStringException , UnknownLanguageException;
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GamePageData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GamePageData.java
index 82e1767..59cef53 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GamePageData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GamePageData.java
@@ -8,23 +8,91 @@ import java.util.List;
 
 
 
+/**
+ * General information included in a game response
+ * 
+ * <p>
+ * This class stores common information which is included in most in-game responses. This
+ * information includes some essential preferences, the general state of the empire, and some
+ * information about the server's state.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
 public class GamePageData
 		implements Serializable
 {
-
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M1 with ID 1
+	 * <li>Modified in B6M2, ID set to 2
+	 * </ul>
+	 */
 	private static final long serialVersionUID = 1L;
+
+	/** The name of the current empire */
 	private final String empire;
+
+	/**
+	 * State of the account
+	 * 
+	 * <p>
+	 * This field indicates the "special" state of the account: <code>q</code> is for accounts that
+	 * are quitting, <code>v</code> for accounts in vacation mode and <code>s</code> for accounts
+	 * that are about to enter vacation mode. <code>null</code> indicates "none of the above".
+	 */
 	private final Character special;
+
+	/** Alliance tag, or <code>null</code> if not a full member of any alliance */
 	private final String alliance;
+
+	/**
+	 * Current cash of the empire
+	 * 
+	 * <p>
+	 * FIXME: will be replaced with actual resources
+	 */
 	private final long cash;
+
+	/** Timestamp from the server's system */
 	private final Date serverTime;
+
+	/** Current game time (from last update identifier) */
 	private final GameTime gameTime;
+
+	/** Planets owned by the empire */
 	private final List< NameIdPair > planets;
+
+	/** Whether the player wants to see "real" times or in-game times */
 	private final boolean useRLTime;
 
+	/** Code of selected language */
+	private final String language;
 
+
+	/**
+	 * Initialise the general game information record
+	 * 
+	 * @param empire
+	 *            the empire's name
+	 * @param special
+	 *            the special account state character (see {@link #special})
+	 * @param alliance
+	 *            current alliance tag
+	 * @param cash
+	 *            current cash
+	 * @param gameTime
+	 *            last update identifier
+	 * @param planets
+	 *            list of planets owned by the empire
+	 * @param useRLTime
+	 *            whether the player wants to see "real" times or in-game times
+	 * @param language
+	 *            selected language code
+	 */
 	public GamePageData( String empire , Character special , String alliance , long cash , long gameTime ,
-			List< NameIdPair > planets , boolean useRLTime )
+			List< NameIdPair > planets , boolean useRLTime , String language )
 	{
 		this.empire = empire;
 		this.special = special;
@@ -34,54 +102,107 @@ public class GamePageData
 		this.gameTime = new GameTime( gameTime );
 		this.planets = Collections.unmodifiableList( planets );
 		this.useRLTime = useRLTime;
+		this.language = language;
 	}
 
 
+	/**
+	 * Gets the name of the current empire.
+	 * 
+	 * @return the name of the current empire
+	 */
 	public String getEmpire( )
 	{
-		return empire;
+		return this.empire;
 	}
 
 
+	/**
+	 * Gets the state of the account
+	 * 
+	 * @return the state of the account
+	 */
 	public Character getSpecial( )
 	{
-		return special;
+		return this.special;
 	}
 
 
+	/**
+	 * Gets the alliance tag
+	 * 
+	 * @return the alliance tag, or <code>null</code> if not a full member of any alliance
+	 */
 	public String getAlliance( )
 	{
-		return alliance;
+		return this.alliance;
 	}
 
 
+	/**
+	 * Gets the current cash of the empire
+	 * 
+	 * @return the current cash of the empire
+	 */
 	public long getCash( )
 	{
-		return cash;
+		return this.cash;
 	}
 
 
+	/**
+	 * Gets the timestamp from the server's system.
+	 * 
+	 * @return the timestamp from the server's system
+	 */
 	public Date getServerTime( )
 	{
-		return serverTime;
+		return this.serverTime;
 	}
 
 
+	/**
+	 * Gets the current game time
+	 * 
+	 * @return the current game time
+	 */
 	public GameTime getGameTime( )
 	{
-		return gameTime;
+		return this.gameTime;
 	}
 
 
+	/**
+	 * Gets the planets owned by the empire.
+	 * 
+	 * @return the list of planets owned by the empire
+	 */
 	public List< NameIdPair > getPlanets( )
 	{
-		return planets;
+		return this.planets;
 	}
 
 
+	/**
+	 * Checks if the player wants to see "real" times or in-game times.
+	 * 
+	 * @return <code>true</code> if the player wants to see "real" times, <code>false</code> for
+	 *         in-game times
+	 */
 	public boolean isUseRLTime( )
 	{
-		return useRLTime;
+		return this.useRLTime;
+	}
+
+
+	/**
+	 * Gets the code of selected language.
+	 * 
+	 * @return the code of selected language
+	 */
+	public String getLanguage( )
+	{
+		return this.language;
 	}
 
 }

From 154f215e24e541692cbdf826104c39ab05731cd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 3 Apr 2012 10:35:57 +0200
Subject: [PATCH 71/94] Removed client-side display for old technology system

* Removed data classes and database access that were used to display
technology information

* Removed "Research" tab from Overview page on the web client
---
 .../lw/beans/empire/EmpireDAOBean.java        | 56 ------------
 .../lw/beans/empire/EmpireManagementBean.java | 25 +-----
 .../lw/sqld/game/EmpireTechLine.java          | 66 --------------
 .../lw/sqld/game/EmpireTechnology.java        | 90 -------------------
 .../lw/interfaces/game/EmpireDAO.java         |  4 -
 .../lw/cmd/player/EmpireResponse.java         | 13 +--
 .../player/gdata/empire/ResearchLineData.java | 70 ---------------
 .../player/gdata/empire/TechnologyData.java   | 70 ---------------
 .../Raw/WEB-INF/fm/en/types/overview.ftl      | 50 -----------
 .../Raw/WEB-INF/fm/fr/types/overview.ftl      | 50 -----------
 10 files changed, 2 insertions(+), 492 deletions(-)
 delete mode 100644 legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
 delete mode 100644 legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
 delete mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchLineData.java
 delete mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyData.java

diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
index e515d04..26a6d01 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
@@ -4,9 +4,7 @@ package com.deepclone.lw.beans.empire;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import javax.sql.DataSource;
 
@@ -20,8 +18,6 @@ import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.PlanetListData;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
 import com.deepclone.lw.interfaces.game.EmpireDAO;
-import com.deepclone.lw.sqld.game.EmpireTechLine;
-import com.deepclone.lw.sqld.game.EmpireTechnology;
 import com.deepclone.lw.sqld.game.GeneralInformation;
 import com.deepclone.lw.utils.StoredProc;
 
@@ -145,58 +141,6 @@ public class EmpireDAOBean
 	}
 
 
-	@Override
-	public List< EmpireTechLine > getTechnology( int empireId )
-	{
-		String sql = "SELECT * FROM emp.tech_lines_view WHERE empire = ?";
-		RowMapper< EmpireTechLine > lineMapper = new RowMapper< EmpireTechLine >( ) {
-			@Override
-			public EmpireTechLine mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				EmpireTechLine etl = new EmpireTechLine( );
-				etl.setId( rs.getInt( "tech_line" ) );
-				etl.setName( rs.getString( "name" ) );
-				etl.setDescription( rs.getString( "description" ) );
-				return etl;
-			}
-		};
-
-		List< EmpireTechLine > lines = this.dTemplate.query( sql , lineMapper , empireId );
-		if ( lines.isEmpty( ) ) {
-			return lines;
-		}
-
-		Map< Integer , EmpireTechLine > linesById = new HashMap< Integer , EmpireTechLine >( );
-		for ( EmpireTechLine etl : lines ) {
-			linesById.put( etl.getId( ) , etl );
-		}
-
-		sql = "SELECT * FROM emp.technologies_view WHERE empire = ?";
-		RowMapper< EmpireTechnology > techMapper = new RowMapper< EmpireTechnology >( ) {
-			@Override
-			public EmpireTechnology mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				EmpireTechnology et = new EmpireTechnology( );
-				et.setLine( rs.getInt( "tech_line" ) );
-				et.setName( rs.getString( "name" ) );
-				et.setDescription( rs.getString( "description" ) );
-				et.setImplemented( rs.getBoolean( "implemented" ) );
-				et.setProgress( (int) rs.getDouble( "progress" ) );
-				et.setCost( rs.getInt( "cost" ) );
-				return et;
-			}
-		};
-
-		for ( EmpireTechnology et : this.dTemplate.query( sql , techMapper , empireId ) ) {
-			linesById.get( et.getLine( ) ).addTechnology( et );
-		}
-
-		return lines;
-	}
-
-
 	@Override
 	public void implementTechnology( int empireId , int lineId )
 	{
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
index a940ef7..4602ce0 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
@@ -21,8 +21,6 @@ import com.deepclone.lw.cmd.player.gdata.PlanetListData;
 import com.deepclone.lw.cmd.player.gdata.PlanetListResourceRecord;
 import com.deepclone.lw.cmd.player.gdata.battles.BattleListEntry;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
-import com.deepclone.lw.cmd.player.gdata.empire.ResearchLineData;
-import com.deepclone.lw.cmd.player.gdata.empire.TechnologyData;
 import com.deepclone.lw.interfaces.acm.UsersDAO;
 import com.deepclone.lw.interfaces.game.BattlesCache;
 import com.deepclone.lw.interfaces.game.BattlesDAO;
@@ -33,8 +31,6 @@ import com.deepclone.lw.interfaces.naming.NamingDAO;
 import com.deepclone.lw.interfaces.prefs.AccountPreferences;
 import com.deepclone.lw.interfaces.prefs.PreferencesDAO;
 import com.deepclone.lw.sqld.accounts.Account;
-import com.deepclone.lw.sqld.game.EmpireTechLine;
-import com.deepclone.lw.sqld.game.EmpireTechnology;
 import com.deepclone.lw.sqld.game.GeneralInformation;
 import com.deepclone.lw.sqld.game.battle.BattleListRecord;
 import com.deepclone.lw.utils.EmailAddress;
@@ -125,25 +121,6 @@ public class EmpireManagementBean
 	public EmpireResponse getOverview( int empireId )
 	{
 		OverviewData overview = this.empireDao.getOverview( empireId );
-		List< ResearchLineData > research = new LinkedList< ResearchLineData >( );
-
-		for ( EmpireTechLine etl : this.empireDao.getTechnology( empireId ) ) {
-			List< TechnologyData > implemented = new LinkedList< TechnologyData >( );
-			TechnologyData current = null;
-
-			for ( EmpireTechnology et : etl.getTechnologies( ) ) {
-				if ( et.isImplemented( ) ) {
-					implemented.add( new TechnologyData( et.getName( ) , et.getDescription( ) ) );
-				} else if ( et.getProgress( ) == 100 ) {
-					current = new TechnologyData( et.getName( ) , et.getDescription( ) , 100 , et.getCost( ) );
-				} else {
-					current = new TechnologyData( et.getName( ) , et.getDescription( ) , et.getProgress( ) );
-				}
-			}
-
-			research.add( new ResearchLineData( etl.getId( ) , etl.getName( ) , etl.getDescription( ) , implemented ,
-					current ) );
-		}
 
 		List< BattleListEntry > battles = new LinkedList< BattleListEntry >( );
 		for ( BattleListRecord record : this.battlesDao.getBattles( empireId ) ) {
@@ -165,7 +142,7 @@ public class EmpireManagementBean
 
 		overview.setEconomy( this.resourcesInformationDao.getEmpireInformation( empireId ) );
 
-		return new EmpireResponse( this.getGeneralInformation( empireId ) , overview , research , battles );
+		return new EmpireResponse( this.getGeneralInformation( empireId ) , overview , battles );
 	}
 
 
diff --git a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
deleted file mode 100644
index 7ab9d68..0000000
--- a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.deepclone.lw.sqld.game;
-
-
-import java.util.LinkedList;
-import java.util.List;
-
-
-
-public class EmpireTechLine
-{
-	private int id;
-
-	private String name;
-
-	private String description;
-
-	private List< EmpireTechnology > technologies = new LinkedList< EmpireTechnology >( );
-
-
-	public int getId( )
-	{
-		return id;
-	}
-
-
-	public void setId( int id )
-	{
-		this.id = id;
-	}
-
-
-	public String getName( )
-	{
-		return name;
-	}
-
-
-	public void setName( String name )
-	{
-		this.name = name;
-	}
-
-
-	public String getDescription( )
-	{
-		return description;
-	}
-
-
-	public void setDescription( String description )
-	{
-		this.description = description;
-	}
-
-
-	public List< EmpireTechnology > getTechnologies( )
-	{
-		return technologies;
-	}
-
-
-	public void addTechnology( EmpireTechnology eTech )
-	{
-		this.technologies.add( eTech );
-	}
-}
diff --git a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
deleted file mode 100644
index b36d4dd..0000000
--- a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.deepclone.lw.sqld.game;
-
-
-public class EmpireTechnology
-{
-	private int line;
-
-	private String name;
-
-	private String description;
-
-	private boolean implemented;
-
-	private int progress;
-
-	private int cost;
-
-
-	public int getLine( )
-	{
-		return line;
-	}
-
-
-	public void setLine( int line )
-	{
-		this.line = line;
-	}
-
-
-	public String getName( )
-	{
-		return name;
-	}
-
-
-	public void setName( String name )
-	{
-		this.name = name;
-	}
-
-
-	public String getDescription( )
-	{
-		return description;
-	}
-
-
-	public void setDescription( String description )
-	{
-		this.description = description;
-	}
-
-
-	public boolean isImplemented( )
-	{
-		return implemented;
-	}
-
-
-	public void setImplemented( boolean implemented )
-	{
-		this.implemented = implemented;
-	}
-
-
-	public int getProgress( )
-	{
-		return progress;
-	}
-
-
-	public void setProgress( int progress )
-	{
-		this.progress = progress;
-	}
-
-
-	public int getCost( )
-	{
-		return cost;
-	}
-
-
-	public void setCost( int cost )
-	{
-		this.cost = cost;
-	}
-
-}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
index 59342d4..291ba89 100644
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
@@ -7,7 +7,6 @@ import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.PlanetListData;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
-import com.deepclone.lw.sqld.game.EmpireTechLine;
 import com.deepclone.lw.sqld.game.GeneralInformation;
 
 
@@ -24,9 +23,6 @@ public interface EmpireDAO
 	public OverviewData getOverview( int empireId );
 
 
-	public List< EmpireTechLine > getTechnology( int empireId );
-
-
 	public void implementTechnology( int empireId , int lineId );
 
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/EmpireResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/EmpireResponse.java
index 7b6eea1..4fa9dd3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/EmpireResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/EmpireResponse.java
@@ -1,14 +1,12 @@
 package com.deepclone.lw.cmd.player;
 
 
-import java.util.Collections;
 import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.battles.BattleListEntry;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
-import com.deepclone.lw.cmd.player.gdata.empire.ResearchLineData;
 
 
 
@@ -18,17 +16,14 @@ public class EmpireResponse
 
 	private static final long serialVersionUID = 1L;
 	private final OverviewData overview;
-	private final List< ResearchLineData > research;
 	private final List< BattleListEntry > battles;
 
 
-	public EmpireResponse( GamePageData page , OverviewData overview , List< ResearchLineData > research ,
-			List< BattleListEntry > battles )
+	public EmpireResponse( GamePageData page , OverviewData overview , List< BattleListEntry > battles )
 	{
 		super( page );
 		this.overview = overview;
 		this.battles = battles;
-		this.research = Collections.unmodifiableList( research );
 	}
 
 
@@ -38,12 +33,6 @@ public class EmpireResponse
 	}
 
 
-	public List< ResearchLineData > getResearch( )
-	{
-		return research;
-	}
-
-
 	public List< BattleListEntry > getBattles( )
 	{
 		return battles;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchLineData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchLineData.java
deleted file mode 100644
index a48f027..0000000
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchLineData.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.deepclone.lw.cmd.player.gdata.empire;
-
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-
-
-public class ResearchLineData
-		implements Serializable , Comparable< ResearchLineData >
-{
-
-	private static final long serialVersionUID = 1L;
-
-	private final long id;
-	private final String name;
-	private final String description;
-	private final List< TechnologyData > implemented;
-	private final TechnologyData current;
-
-
-	public ResearchLineData( long id , String name , String description , List< TechnologyData > implemented ,
-			TechnologyData current )
-	{
-		this.id = id;
-		this.name = name;
-		this.description = description;
-		this.implemented = Collections.unmodifiableList( implemented );
-		this.current = current;
-	}
-
-
-	@Override
-	public int compareTo( ResearchLineData other )
-	{
-		return this.name.compareTo( other.name );
-	}
-
-
-	public long getId( )
-	{
-		return id;
-	}
-
-
-	public String getName( )
-	{
-		return name;
-	}
-
-
-	public String getDescription( )
-	{
-		return description;
-	}
-
-
-	public List< TechnologyData > getImplemented( )
-	{
-		return implemented;
-	}
-
-
-	public TechnologyData getCurrent( )
-	{
-		return current;
-	}
-
-}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyData.java
deleted file mode 100644
index 7ea35f0..0000000
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyData.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.deepclone.lw.cmd.player.gdata.empire;
-
-
-import java.io.Serializable;
-
-
-
-public class TechnologyData
-		implements Serializable
-{
-
-	private static final long serialVersionUID = 1L;
-
-	private final String name;
-	private final String description;
-	private final Integer researched;
-	private final Long cost;
-
-
-	public TechnologyData( String name , String description )
-	{
-		this.name = name;
-		this.description = description;
-		this.researched = null;
-		this.cost = null;
-	}
-
-
-	public TechnologyData( String name , String description , int researched )
-	{
-		this.name = name;
-		this.description = description;
-		this.researched = researched;
-		this.cost = null;
-	}
-
-
-	public TechnologyData( String name , String description , int researched , long cost )
-	{
-		this.name = name;
-		this.description = description;
-		this.researched = researched;
-		this.cost = cost;
-	}
-
-
-	public String getName( )
-	{
-		return name;
-	}
-
-
-	public String getDescription( )
-	{
-		return description;
-	}
-
-
-	public Integer getResearched( )
-	{
-		return researched;
-	}
-
-
-	public Long getCost( )
-	{
-		return cost;
-	}
-
-}
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
index 28d1686..4eae28b 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
@@ -67,56 +67,6 @@
 			<@overviewResources />
 		</@tab>
 
-		<@tab id="research" title="Research">
-			<#if rs?size == 0>
-				<p>Our scientists are still settling in.</p>
-			</#if>
-			<#list rs as research>
-				<div>
-					<h3>${research.name?xhtml}</h3>
-					<p>${research.description?xhtml}</p>
-
-					<@left_column>
-						<#if research.implemented?size == 0>
-							<p>No usable technologies.</p>
-						<#else>
-							<@dt_main>
-								<#list research.implemented as tech>
-									<@dt_status>
-										${tech.name?xhtml}
-										<div class="auto-hide">${tech.description?xhtml}</div>
-									</@dt_status>
-								</#list>
-							</@dt_main>
-						</#if>
-					</@left_column>
-					
-					<#if research.current?has_content>
-						<@right_column>
-							<@dt_main>
-								<@dt_status>
-									Current research: <strong>${research.current.name?xhtml}</strong>
-									<p>
-										${research.current.description?xhtml}
-									</p>
-								</@dt_status>
-								<@dt_entry title="Progress">${research.current.researched}%</@dt_entry>
-								<#if research.current.cost?has_content>
-									<@dt_entry title="Cost">${research.current.cost?string(",##0")} <@abbr_bgc/></@dt_entry>
-									<#if data.page.cash gte research.current.cost && data.page.special! != 'v'>
-										<@dt_status><form action="implement-${research.id}.action#research" method="post">
-											<div><@ff_submit label="Implement technology" /></div>
-										</form></@dt_status>
-									</#if>
-								</#if>
-							</@dt_main>
-						</@right_column>
-					</#if>
-
-				</div>
-			</#list>
-		</@tab>
-
 	</@tabs>
 
 </@page>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
index 01aa309..7821aa5 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
@@ -66,56 +66,6 @@
 			<#include "overview/resources.ftl" />
 			<@overviewResources />
 		</@tab>
-		
-		<@tab id="research" title="Recherche">
-			<#if rs?size == 0>
-				<p>Nos scientifiques sont encore en train de s'installer.</p>
-			</#if>
-			<#list rs as research>
-				<div>
-					<h3>${research.name?xhtml}</h3>
-					<p>${research.description?xhtml}</p>
-
-					<@left_column>
-						<#if research.implemented?size == 0>
-							<p>Aucune technologie utilisable.</p>
-						<#else>
-							<@dt_main>
-								<#list research.implemented as tech>
-									<@dt_status>
-										${tech.name?xhtml}
-										<div class="auto-hide">${tech.description?xhtml}</div>
-									</@dt_status>
-								</#list>
-							</@dt_main>
-						</#if>
-					</@left_column>
-					
-					<#if research.current?has_content>
-						<@right_column>
-							<@dt_main>
-								<@dt_status>
-									Recherche actuelle : <strong>${research.current.name?xhtml}</strong>
-									<p>
-										${research.current.description?xhtml}
-									</p>
-								</@dt_status>
-								<@dt_entry title="Progression">${research.current.researched}%</@dt_entry>
-								<#if research.current.cost?has_content>
-									<@dt_entry title="Coût">${research.current.cost?string(",##0")} <@abbr_bgc/></@dt_entry>
-									<#if data.page.cash gte research.current.cost && data.page.special! != 'v'>
-										<@dt_status><form action="implement-${research.id}.action#research" method="post">
-											<div><@ff_submit label="Appliquer la technologie" /></div>
-										</form></@dt_status>
-									</#if>
-								</#if>
-							</@dt_main>
-						</@right_column>
-					</#if>
-
-				</div>
-			</#list>
-		</@tab>
 
 	</@tabs>
 

From 6dcd59d7bc7fe706475c2b2cf2ee1ed95c2ac9de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 7 Apr 2012 13:06:03 +0200
Subject: [PATCH 72/94] New research and technology page
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Added legacyworlds-server-beans-technologies Maven module, including
the player-level DAO and controller.

* Added session classes to carry technology information, modified web
client session façade accordingly

* Various changes to common UI elements (forms, lists, etc...) so the
start and end of some element can be drawn separately

* Added controller, templates and JavaScript for research page
---
 .../lw/beans/empire/EmpireDAOBean.java        |  12 -
 .../lw/beans/empire/EmpireManagementBean.java |   8 -
 .../pom.xml                                   |  26 ++
 .../src/main/java/.empty                      |   0
 .../PlayerTechnologiesDAOBean.java            | 143 +++++++
 .../technologies/ResearchControllerBean.java  | 197 +++++++++
 .../game/technologies/ResearchRowMapper.java  |  69 ++++
 .../src/main/resources/.empty                 |   0
 .../configuration/game/technologies.xml       |   9 +
 .../src/test/java/.empty                      |   0
 .../src/test/resources/.empty                 |   0
 .../ImplementTechCommandDelegateBean.java     |  56 ---
 .../tech/GetResearchCommandDelegateBean.java  |  70 ++++
 .../ImplementTechCommandDelegateBean.java     |  72 ++++
 ...ResearchPrioritiesCommandDelegateBean.java |  77 ++++
 .../configuration/session-types/player.xml    |   6 +-
 legacyworlds-server-beans/pom.xml             |   1 +
 .../040-functions/045-empire-research.sql     |   4 +
 .../lw/interfaces/game/EmpireDAO.java         |   3 -
 .../lw/interfaces/game/EmpireManagement.java  |   3 -
 .../technologies/PlayerTechnologiesDAO.java   |  92 +++++
 .../game/technologies/ResearchController.java |  69 ++++
 legacyworlds-server-main/pom.xml              |   4 +
 .../src/main/resources/configuration/game.xml |   1 +
 .../lw/cmd/player/ImplementTechCommand.java   |  28 --
 .../cmd/player/gdata/empire/ResearchData.java | 373 ++++++++++++++++++
 .../cmd/player/tech/GetResearchCommand.java   |  26 ++
 .../cmd/player/tech/GetResearchResponse.java  |  66 ++++
 .../cmd/player/tech/ImplementTechCommand.java |  53 +++
 .../tech/UpdateResearchPrioritiesCommand.java |  50 +++
 .../deepclone/lw/web/csess/PlayerSession.java |  40 +-
 .../Raw/WEB-INF/fm/en/containers/game.ftl     |   3 +
 .../Raw/WEB-INF/fm/en/types/overview.ftl      |   1 -
 .../Raw/WEB-INF/fm/en/types/research.ftl      | 255 ++++++++++++
 .../Raw/WEB-INF/fm/fr/containers/game.ftl     |   5 +-
 .../Raw/WEB-INF/fm/fr/types/overview.ftl      |   1 -
 .../Raw/WEB-INF/fm/fr/types/research.ftl      | 255 ++++++++++++
 .../Content/Raw/WEB-INF/fm/layout/form.ftl    |  15 +-
 .../Content/Raw/WEB-INF/fm/layout/lists.ftl   |  22 +-
 .../Content/Raw/WEB-INF/fm/layout/tabs.ftl    |  10 +-
 .../Content/Raw/css/main.css                  |  56 ++-
 .../Content/Raw/js/research.js                |  87 ++++
 .../lw/web/main/game/OverviewPage.java        |  33 +-
 .../lw/web/main/game/ResearchPage.java        | 186 +++++++++
 legacyworlds/pom.xml                          |   5 +
 45 files changed, 2314 insertions(+), 178 deletions(-)
 create mode 100644 legacyworlds-server-beans-technologies/pom.xml
 create mode 100644 legacyworlds-server-beans-technologies/src/main/java/.empty
 create mode 100644 legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/PlayerTechnologiesDAOBean.java
 create mode 100644 legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java
 create mode 100644 legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java
 create mode 100644 legacyworlds-server-beans-technologies/src/main/resources/.empty
 create mode 100644 legacyworlds-server-beans-technologies/src/main/resources/configuration/game/technologies.xml
 create mode 100644 legacyworlds-server-beans-technologies/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-technologies/src/test/resources/.empty
 delete mode 100644 legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java
 create mode 100644 legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/GetResearchCommandDelegateBean.java
 create mode 100644 legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/ImplementTechCommandDelegateBean.java
 create mode 100644 legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/UpdateResearchPrioritiesCommandDelegateBean.java
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/PlayerTechnologiesDAO.java
 create mode 100644 legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/ResearchController.java
 delete mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ImplementTechCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchResponse.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/ImplementTechCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/UpdateResearchPrioritiesCommand.java
 create mode 100644 legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl
 create mode 100644 legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/research.ftl
 create mode 100644 legacyworlds-web-main/Content/Raw/js/research.js
 create mode 100644 legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ResearchPage.java

diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
index 26a6d01..9d15e1e 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
@@ -27,7 +27,6 @@ public class EmpireDAOBean
 		implements EmpireDAO
 {
 	private JdbcTemplate dTemplate;
-	private StoredProc fImplementTech;
 	private StoredProc fAddEmpEnemy;
 	private StoredProc fAddAllEnemy;
 	private StoredProc fRemoveEmpEnemy;
@@ -42,10 +41,6 @@ public class EmpireDAOBean
 	{
 		this.dTemplate = new JdbcTemplate( dataSource );
 
-		this.fImplementTech = new StoredProc( dataSource , "emp" , "implement_tech" );
-		this.fImplementTech.addParameter( "empire_id" , Types.INTEGER );
-		this.fImplementTech.addParameter( "line_id" , Types.INTEGER );
-
 		this.fAddEmpEnemy = new StoredProc( dataSource , "emp" , "add_enemy_empire" );
 		this.fAddEmpEnemy.addParameter( "empire_id" , Types.INTEGER );
 		this.fAddEmpEnemy.addParameter( "enemy_name" , Types.VARCHAR );
@@ -141,13 +136,6 @@ public class EmpireDAOBean
 	}
 
 
-	@Override
-	public void implementTechnology( int empireId , int lineId )
-	{
-		this.fImplementTech.execute( empireId , lineId );
-	}
-
-
 	@Override
 	public List< PlanetListData > getPlanetList( int empireId )
 	{
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
index 4602ce0..2e3838b 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
@@ -146,14 +146,6 @@ public class EmpireManagementBean
 	}
 
 
-	@Override
-	public EmpireResponse implementTechnology( int empireId , int techId )
-	{
-		this.empireDao.implementTechnology( empireId , techId );
-		return this.getOverview( empireId );
-	}
-
-
 	@Override
 	public ListPlanetsResponse getPlanetList( int empireId )
 	{
diff --git a/legacyworlds-server-beans-technologies/pom.xml b/legacyworlds-server-beans-technologies/pom.xml
new file mode 100644
index 0000000..f3ddea4
--- /dev/null
+++ b/legacyworlds-server-beans-technologies/pom.xml
@@ -0,0 +1,26 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<parent>
+		<artifactId>legacyworlds-server-beans</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-server-beans-technologies</artifactId>
+	<name>Legacy Worlds - Server - Components - Technologies</name>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<description>This module contains the components which manage and access in-universe technologies.</description>
+
+	<dependencies>
+
+		<dependency>
+			<groupId>postgresql</groupId>
+			<artifactId>postgresql</artifactId>
+		</dependency>
+
+	</dependencies>
+
+</project>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-technologies/src/main/java/.empty b/legacyworlds-server-beans-technologies/src/main/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/PlayerTechnologiesDAOBean.java b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/PlayerTechnologiesDAOBean.java
new file mode 100644
index 0000000..dc4e437
--- /dev/null
+++ b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/PlayerTechnologiesDAOBean.java
@@ -0,0 +1,143 @@
+package com.deepclone.lw.beans.game.technologies;
+
+
+import java.sql.Types;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+import com.deepclone.lw.interfaces.game.technologies.PlayerTechnologiesDAO;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Data access component for player technologies
+ * 
+ * <p>
+ * This class implements database queries and calls to stored procedures which allow players to
+ * access and manage their empires' research.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class PlayerTechnologiesDAOBean
+		implements PlayerTechnologiesDAO
+{
+
+	/** SQL query that fetches an empire's research state */
+	private static final String Q_EMPIRE_RESEARCH = "SELECT * FROM emp.technologies_v2_view WHERE empire_id = ?";
+
+	/** Row mapper for research state entries */
+	private final ResearchRowMapper mResearch;
+	
+	/** Stored procedure that initiates a research priority update */
+	private StoredProc fResprioUpdateStart;
+
+	/** Stored procedure that uploads a single research priority */
+	private StoredProc fResprioUpdateSet;
+
+	/** Stored procedure that applies a research priority update */
+	private StoredProc fResprioUpdateApply;
+	
+	/** Stored procedure that implements a technology */
+	private StoredProc fTechnologyImplement;
+
+	/** Spring JDBC interface */
+	private JdbcTemplate dTemplate;
+
+
+	/** Initialise the necessary row mappers */
+	public PlayerTechnologiesDAOBean( )
+	{
+		this.mResearch = new ResearchRowMapper( );
+	}
+
+
+	/**
+	 * Dependency injector that sets the data source and initialises stored procedures 
+	 * 
+	 * @param dataSource
+	 *            the data source
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.dTemplate = new JdbcTemplate( dataSource );
+
+		this.fResprioUpdateStart = new StoredProc( dataSource , "emp" , "resprio_update_start" )
+			.addParameter( "_empire" , Types.INTEGER )
+			.addOutput( "_result" , Types.BOOLEAN );
+
+		this.fResprioUpdateSet = new StoredProc( dataSource , "emp" , "resprio_update_set" )
+			.addParameter( "_technology" , Types.VARCHAR )
+			.addParameter( "_priority" , Types.INTEGER )
+			.addOutput( "_result" , Types.BOOLEAN );
+
+		this.fResprioUpdateApply = new StoredProc( dataSource , "emp" , "resprio_update_apply" )
+			.addOutput( "_result" , Types.BOOLEAN );
+
+		this.fTechnologyImplement = new StoredProc( dataSource , "emp" , "technology_implement" )
+			.addParameter( "_empire" , Types.INTEGER )
+			.addParameter( "_technology" , Types.VARCHAR )
+			.addOutput( "_result" , Types.BOOLEAN );
+	}
+
+
+	/**
+	 * Query and map research entries for an empire
+	 * 
+	 * <p>
+	 * Run the query against <code>emp.technologies_view</code> then map all rows using a
+	 * {@link ResearchRowMapper}.
+	 */
+	@Override
+	public List< ResearchData > getResearchData( int empire )
+	{
+		return this.dTemplate.query( Q_EMPIRE_RESEARCH , this.mResearch , empire );
+	}
+
+
+	/**
+	 * Call <code>emp.resprio_update_start</code>
+	 */
+	@Override
+	public boolean startPriorityUpdate( int empire )
+	{
+		return (Boolean) this.fResprioUpdateStart.execute( empire ).get( "_result" );
+	}
+
+
+	/**
+	 * Call <code>emp.resprio_update_set</code>
+	 */
+	@Override
+	public boolean uploadResearchPriority( String identifier , int priority )
+	{
+		return (Boolean) this.fResprioUpdateSet.execute( identifier , priority ).get( "_result" );
+	}
+
+
+	/**
+	 * Call <code>emp.resprio_update_apply</code>
+	 */
+	@Override
+	public boolean applyPriorityUpdate( )
+	{
+		return (Boolean) this.fResprioUpdateApply.execute( ).get( "_result" );
+	}
+
+
+	/**
+	 * Call <code>emp.technology_implement</code>
+	 */
+	@Override
+	public boolean implement( int empire , String technology )
+	{
+		return (Boolean) this.fTechnologyImplement.execute( empire , technology ).get( "_result" );
+	}
+
+}
diff --git a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java
new file mode 100644
index 0000000..c1c9388
--- /dev/null
+++ b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java
@@ -0,0 +1,197 @@
+package com.deepclone.lw.beans.game.technologies;
+
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+import com.deepclone.lw.cmd.player.tech.GetResearchResponse;
+import com.deepclone.lw.interfaces.game.EmpireManagement;
+import com.deepclone.lw.interfaces.game.technologies.PlayerTechnologiesDAO;
+import com.deepclone.lw.interfaces.game.technologies.ResearchController;
+import com.deepclone.lw.interfaces.i18n.TranslationException;
+import com.deepclone.lw.interfaces.i18n.Translator;
+
+
+
+/**
+ * Research control component
+ * 
+ * <p>
+ * This component implements all actions allowing a player to view and update its empire's research
+ * state.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@Transactional
+class ResearchControllerBean
+		implements ResearchController
+{
+	/** Main empire management component */
+	private EmpireManagement empireManagement;
+
+	/** String translator */
+	private Translator translator;
+
+	/** Data access object for technologies */
+	private PlayerTechnologiesDAO playerTechnologiesDAO;
+
+
+	/**
+	 * Dependency injector that sets the empire management component
+	 * 
+	 * @param empireManagement
+	 *            the empire management component
+	 */
+	@Autowired( required = true )
+	public void setEmpireManagement( EmpireManagement empireManagement )
+	{
+		this.empireManagement = empireManagement;
+	}
+
+
+	/**
+	 * Dependency injector that sets the translation component
+	 * 
+	 * @param translator
+	 *            the translation component
+	 */
+	@Autowired( required = true )
+	public void setTranslator( Translator translator )
+	{
+		this.translator = translator;
+	}
+
+
+	/**
+	 * Dependency injector that sets the data access object for technologies
+	 * 
+	 * @param playerTechnologiesDAO
+	 *            the data access object for technologies
+	 */
+	@Autowired( required = true )
+	public void setPlayerTechnologiesDAO( PlayerTechnologiesDAO playerTechnologiesDAO )
+	{
+		this.playerTechnologiesDAO = playerTechnologiesDAO;
+	}
+
+
+	/**
+	 * Get the raw research data from the database, then handle translation and dependencies
+	 * 
+	 * <p>
+	 * In order to generate the response, this method fetches the raw research state information
+	 * from the database. It then proceeds with translating all categories, names and descriptions,
+	 * then adds reverse dependency information to all loaded technologies.
+	 */
+	@Override
+	public GetResearchResponse getResearchData( int empire )
+	{
+		GamePageData pageData = this.empireManagement.getGeneralInformation( empire );
+		List< ResearchData > technologies = this.playerTechnologiesDAO.getResearchData( empire );
+
+		// Translate categories, names and descriptions
+		Map< String , String > translations = this.getTranslationsFor( technologies , pageData.getLanguage( ) );
+		Map< String , ResearchData > byId = new HashMap< String , ResearchData >( );
+		for ( ResearchData tech : technologies ) {
+			byId.put( tech.getIdentifier( ) , tech );
+			tech.setCategory( translations.get( tech.getCategory( ) ) );
+			if ( tech.getName( ) != null ) {
+				tech.setName( translations.get( tech.getName( ) ) );
+				tech.setDescription( translations.get( tech.getDescription( ) ) );
+			}
+		}
+
+		// Add reverse dependency identifiers
+		for ( ResearchData tech : technologies ) {
+			for ( String dependency : tech.getDependencies( ) ) {
+				byId.get( dependency ).addReverseDependency( tech.getIdentifier( ) );
+			}
+		}
+
+		return new GetResearchResponse( pageData , technologies );
+	}
+
+
+	/**
+	 * Get translations used by a list of raw research entries
+	 * 
+	 * <p>
+	 * This method is fed the raw research state data from the player technologies data access
+	 * object. It generates a set of string identifiers which contains all categories, names and
+	 * descriptions, then fetches these strings' translations.
+	 * 
+	 * @param technologies
+	 *            the research state data from the DAO
+	 * @param language
+	 *            the player's selected language
+	 * 
+	 * @return the map of string identifiers to string contents
+	 */
+	private Map< String , String > getTranslationsFor( List< ResearchData > technologies , String language )
+	{
+		Set< String > identifiers = new HashSet< String >( );
+		for ( ResearchData tech : technologies ) {
+			identifiers.add( tech.getCategory( ) );
+			if ( tech.getName( ) != null ) {
+				identifiers.add( tech.getName( ) );
+				identifiers.add( tech.getDescription( ) );
+			}
+		}
+
+		try {
+			return this.translator.translate( language , identifiers );
+		} catch ( TranslationException e ) {
+			throw new RuntimeException( "error while translating technology-related data" , e );
+		}
+	}
+
+
+	/**
+	 * Update research priorities
+	 * 
+	 * <p>
+	 * This method will start a research priority update for the specified empire, then upload each
+	 * value in the map to the database. Finally, it will try to apply the update.
+	 * 
+	 * <p>
+	 * If any of the above steps fail, the method will abort.
+	 */
+	@Override
+	public boolean updatePriorities( int empire , Map< String , Integer > priorities )
+	{
+		if ( !this.playerTechnologiesDAO.startPriorityUpdate( empire ) ) {
+			return false;
+		}
+
+		for ( Map.Entry< String , Integer > entry : priorities.entrySet( ) ) {
+			if ( !this.playerTechnologiesDAO.uploadResearchPriority( entry.getKey( ) , entry.getValue( ) ) ) {
+				return false;
+			}
+		}
+
+		return this.playerTechnologiesDAO.applyPriorityUpdate( );
+	}
+
+
+	/**
+	 * Implement a technology
+	 * 
+	 * <p>
+	 * Call the DAO's technology implementation method in order to access the corresponding stored
+	 * procedure.
+	 */
+	@Override
+	public boolean implement( int empire , String technology )
+	{
+		return this.playerTechnologiesDAO.implement( empire , technology );
+	}
+
+}
diff --git a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java
new file mode 100644
index 0000000..e69c979
--- /dev/null
+++ b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java
@@ -0,0 +1,69 @@
+package com.deepclone.lw.beans.game.technologies;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.postgresql.util.PGobject;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+
+
+
+/**
+ * Row mapper for empire research state entries
+ * 
+ * <p>
+ * This class is responsible for converting empire research state information rows into instances of
+ * the corresponding class, {@link ResearchData}. The instances it produces are "raw": they contain
+ * string identifiers instead of translations, and their reverse dependencies are not set.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+class ResearchRowMapper
+		implements RowMapper< ResearchData >
+{
+
+	/**
+	 * Map a row from <code>emp.technologies_view</code>
+	 * 
+	 * <p>
+	 * This method maps a single row from the <code>emp.technologies_view</code> view into a
+	 * {@link ResearchData} instance.
+	 */
+	@Override
+	public ResearchData mapRow( ResultSet rs , int rowNum )
+			throws SQLException
+	{
+		ResearchData output = new ResearchData( );
+
+		output.setIdentifier( rs.getString( "emptech_id" ) );
+		output.setCategory( rs.getString( "technology_category" ) );
+
+		if ( rs.getBoolean( "emptech_visible" ) ) {
+			output.setName( rs.getString( "technology_name" ) );
+			output.setDescription( rs.getString( "technology_description" ) );
+			output.setPrice( rs.getLong( "technology_price" ) );
+		}
+
+		int ratio = rs.getInt( "emptech_ratio" );
+		if ( rs.wasNull( ) ) {
+			String state = ( (PGobject) rs.getObject( "emptech_state" ) ).getValue( );
+			output.setImplemented( "KNOWN".equals( state ) );
+		} else {
+			output.setCompletion( ratio );
+			output.setPriority( rs.getInt( "emptech_priority" ) );
+		}
+		
+		String dependencies = rs.getString( "technology_dependencies" );
+		if ( ! "".equals( dependencies ) ) {
+			output.setDependencies( dependencies.split( "," ) );
+		}
+
+		return output;
+	}
+
+}
diff --git a/legacyworlds-server-beans-technologies/src/main/resources/.empty b/legacyworlds-server-beans-technologies/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-technologies/src/main/resources/configuration/game/technologies.xml b/legacyworlds-server-beans-technologies/src/main/resources/configuration/game/technologies.xml
new file mode 100644
index 0000000..e0b7bdc
--- /dev/null
+++ b/legacyworlds-server-beans-technologies/src/main/resources/configuration/game/technologies.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+	<bean id="playerTechnologiesDAO" class="com.deepclone.lw.beans.game.technologies.PlayerTechnologiesDAOBean" />
+	<bean id="reesearchController" class="com.deepclone.lw.beans.game.technologies.ResearchControllerBean" />
+
+</beans>
diff --git a/legacyworlds-server-beans-technologies/src/test/java/.empty b/legacyworlds-server-beans-technologies/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-technologies/src/test/resources/.empty b/legacyworlds-server-beans-technologies/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java
deleted file mode 100644
index d6d7165..0000000
--- a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.deepclone.lw.beans.user.player.game;
-
-
-import org.springframework.beans.factory.annotation.Autowired;
-
-import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
-import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
-import com.deepclone.lw.beans.user.player.GameSubTypeBean;
-import com.deepclone.lw.cmd.player.ImplementTechCommand;
-import com.deepclone.lw.interfaces.game.EmpireManagement;
-import com.deepclone.lw.interfaces.session.ServerSession;
-import com.deepclone.lw.session.Command;
-import com.deepclone.lw.session.CommandResponse;
-
-
-
-public class ImplementTechCommandDelegateBean
-		implements AutowiredCommandDelegate
-
-{
-
-	private EmpireManagement empireManagement;
-
-
-	@Autowired( required = true )
-	public void setEmpireManager( EmpireManagement manager )
-	{
-		this.empireManagement = manager;
-	}
-
-
-	@Override
-	public Class< ? extends Command > getType( )
-	{
-		return ImplementTechCommand.class;
-	}
-
-
-	@Override
-	public Class< ? extends SessionCommandHandler > getCommandHandler( )
-	{
-		return GameSubTypeBean.class;
-	}
-
-
-	@Override
-	public CommandResponse execute( ServerSession session , Command cParam )
-	{
-		ImplementTechCommand command = (ImplementTechCommand) cParam;
-		int empireId = session.get( "empireId" , Integer.class );
-		if ( session.get( "vacation" , Boolean.class ) ) {
-			return this.empireManagement.getOverview( empireId );
-		}
-		return this.empireManagement.implementTechnology( empireId , command.getTech( ) );
-	}
-}
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/GetResearchCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/GetResearchCommandDelegateBean.java
new file mode 100644
index 0000000..8735745
--- /dev/null
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/GetResearchCommandDelegateBean.java
@@ -0,0 +1,70 @@
+package com.deepclone.lw.beans.user.player.game.tech;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.player.GameSubTypeBean;
+import com.deepclone.lw.cmd.player.tech.GetResearchCommand;
+import com.deepclone.lw.interfaces.game.technologies.ResearchController;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+
+
+
+/**
+ * Command delegate for {@link GetResearchCommand}
+ * 
+ * <p>
+ * This command delegate uses the {@link ResearchController} to obtain all information pertaining to
+ * an empire's research.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class GetResearchCommandDelegateBean
+		implements AutowiredCommandDelegate
+{
+	/** The research controller */
+	private ResearchController researchController;
+
+
+	/**
+	 * Dependency injector that sets the research controller
+	 * 
+	 * @param researchController
+	 *            the research controller
+	 */
+	@Autowired
+	public void setResearchController( ResearchController researchController )
+	{
+		this.researchController = researchController;
+	}
+
+
+	/** This delegate handles {@link GetResearchCommand} instances */
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return GetResearchCommand.class;
+	}
+
+
+	/** This delegate is part of the game session sub-type */
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return GameSubTypeBean.class;
+	}
+
+
+	/** When the command is executed, fetch relevant data from the research controller */
+	@Override
+	public CommandResponse execute( ServerSession session , Command command )
+	{
+		int empire = session.get( "empireId" , Integer.class );
+		return this.researchController.getResearchData( empire );
+	}
+
+}
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/ImplementTechCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/ImplementTechCommandDelegateBean.java
new file mode 100644
index 0000000..5b13636
--- /dev/null
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/ImplementTechCommandDelegateBean.java
@@ -0,0 +1,72 @@
+package com.deepclone.lw.beans.user.player.game.tech;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.player.GameSubTypeBean;
+import com.deepclone.lw.cmd.player.tech.ImplementTechCommand;
+import com.deepclone.lw.interfaces.game.technologies.ResearchController;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+import com.deepclone.lw.session.NullResponse;
+
+
+
+public class ImplementTechCommandDelegateBean
+		implements AutowiredCommandDelegate
+
+{
+	/** The research controller */
+	private ResearchController researchController;
+
+
+	/**
+	 * Dependency injector that sets the research controller
+	 * 
+	 * @param researchController
+	 *            the research controller
+	 */
+	@Autowired( required = true )
+	public void setResearchController( ResearchController researchController )
+	{
+		this.researchController = researchController;
+	}
+
+
+	/** This class handles {@link ImplementTechCommand} instances */
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return ImplementTechCommand.class;
+	}
+
+
+	/** This class is enabled for the {@link GameSubTypeBean} session type */
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return GameSubTypeBean.class;
+	}
+
+
+	/**
+	 * Implement a technology
+	 * 
+	 * <p>
+	 * If the empire is not in vacation mode, try to implement the technology. Always return a
+	 * {@link NullResponse}.
+	 */
+	@Override
+	public CommandResponse execute( ServerSession session , Command cParam )
+	{
+		ImplementTechCommand command = (ImplementTechCommand) cParam;
+		int empire = session.get( "empireId" , Integer.class );
+		if ( !session.get( "vacation" , Boolean.class ) ) {
+			this.researchController.implement( empire , command.getTech( ) );
+		}
+		return new NullResponse( );
+	}
+}
diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/UpdateResearchPrioritiesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/UpdateResearchPrioritiesCommandDelegateBean.java
new file mode 100644
index 0000000..c3dc103
--- /dev/null
+++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/UpdateResearchPrioritiesCommandDelegateBean.java
@@ -0,0 +1,77 @@
+package com.deepclone.lw.beans.user.player.game.tech;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.player.GameSubTypeBean;
+import com.deepclone.lw.cmd.player.tech.UpdateResearchPrioritiesCommand;
+import com.deepclone.lw.interfaces.game.technologies.ResearchController;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+import com.deepclone.lw.session.NullResponse;
+
+
+
+/**
+ * Command handler for research priority updates
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class UpdateResearchPrioritiesCommandDelegateBean
+		implements AutowiredCommandDelegate
+{
+	/** The research controller */
+	private ResearchController researchController;
+
+
+	/**
+	 * Dependency injector that sets the research controller
+	 * 
+	 * @param researchController
+	 *            the research controller
+	 */
+	@Autowired( required = true )
+	public void setResearchController( ResearchController researchController )
+	{
+		this.researchController = researchController;
+	}
+
+
+	/** This class handles {@link UpdateResearchPrioritiesCommand} instances */
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return UpdateResearchPrioritiesCommand.class;
+	}
+
+
+	/** This class is enabled for the {@link GameSubTypeBean} session type */
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return GameSubTypeBean.class;
+	}
+
+
+	/**
+	 * Update research priorities
+	 * 
+	 * <p>
+	 * If the empire is not in vacation mode, access the research controller and update mining
+	 * priorities using the specified values. Return a {@link NullResponse} in all cases.
+	 */
+	@Override
+	public CommandResponse execute( ServerSession session , Command command )
+	{
+		if ( !session.get( "vacation" , Boolean.class ) ) {
+			int empireId = session.get( "empireId" , Integer.class );
+			this.researchController.updatePriorities( empireId ,
+					( (UpdateResearchPrioritiesCommand) command ).getPriorities( ) );
+		}
+		return new NullResponse( );
+	}
+
+}
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
index ff9a382..8398256 100644
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
+++ b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
@@ -44,9 +44,13 @@
 
 	<!-- Game: empire -->
 	<bean class="com.deepclone.lw.beans.user.player.game.OverviewCommandDelegateBean" />
-	<bean class="com.deepclone.lw.beans.user.player.game.ImplementTechCommandDelegateBean" />
+	<bean class="com.deepclone.lw.beans.user.player.game.tech.ImplementTechCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.player.game.UpdateEmpireMiningSettingsCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.player.game.GetNewPlanetCommandDelegateBean" />
+	
+	<!-- Game: research -->
+	<bean class="com.deepclone.lw.beans.user.player.game.tech.GetResearchCommandDelegateBean" />
+	<bean class="com.deepclone.lw.beans.user.player.game.tech.UpdateResearchPrioritiesCommandDelegateBean" />
 
 	<!-- Game: planet list -->
 	<bean class="com.deepclone.lw.beans.user.player.game.ListPlanetsCommandDelegateBean" />
diff --git a/legacyworlds-server-beans/pom.xml b/legacyworlds-server-beans/pom.xml
index a03d945..d2c3b59 100644
--- a/legacyworlds-server-beans/pom.xml
+++ b/legacyworlds-server-beans/pom.xml
@@ -30,6 +30,7 @@
 		<module>../legacyworlds-server-beans-resources</module>
 		<module>../legacyworlds-server-beans-simple</module>
 		<module>../legacyworlds-server-beans-system</module>
+		<module>../legacyworlds-server-beans-technologies</module>
 		<module>../legacyworlds-server-beans-updates</module>
 		<module>../legacyworlds-server-beans-user</module>
 	</modules>
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
index c25bd64..7860984 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
@@ -134,6 +134,10 @@ REVOKE EXECUTE
 	ON FUNCTION emp.technology_make_identifier( INT , TEXT , BOOLEAN )
 	FROM PUBLIC;
 
+GRANT EXECUTE
+	ON FUNCTION emp.technology_make_identifier( INT , TEXT , BOOLEAN )
+	TO :dbuser;
+
 
 /*
  * Initialise a research priorities update
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
index 291ba89..68f9159 100644
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
@@ -23,9 +23,6 @@ public interface EmpireDAO
 	public OverviewData getOverview( int empireId );
 
 
-	public void implementTechnology( int empireId , int lineId );
-
-
 	public List< PlanetListData > getPlanetList( int empireId );
 
 
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
index eb92be9..26c27d6 100644
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
@@ -22,9 +22,6 @@ public interface EmpireManagement
 	public EmpireResponse getOverview( int empireId );
 
 
-	public EmpireResponse implementTechnology( int empireId , int techId );
-
-
 	public ListPlanetsResponse getPlanetList( int empireId );
 
 
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/PlayerTechnologiesDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/PlayerTechnologiesDAO.java
new file mode 100644
index 0000000..e0c349e
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/PlayerTechnologiesDAO.java
@@ -0,0 +1,92 @@
+package com.deepclone.lw.interfaces.game.technologies;
+
+
+import java.util.List;
+
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+
+
+
+/**
+ * Player-oriented data access interface for technologies
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public interface PlayerTechnologiesDAO
+{
+
+	/**
+	 * Obtain the list of technologies for an empire
+	 * 
+	 * <p>
+	 * Query the database for technology entries as seen from an empire's perspective. The
+	 * technologies will be returned with I18N string identifiers for categories, names and
+	 * descriptions (unless the details are not available).
+	 * 
+	 * @param empire
+	 *            the empire's identifier
+	 * 
+	 * @return the list of technology entries
+	 */
+	public List< ResearchData > getResearchData( int empire );
+
+
+	/**
+	 * Start a research priority update
+	 * 
+	 * <p>
+	 * This method initialises a research priorities update by calling the appropriate stored
+	 * procedure.
+	 * 
+	 * @param empire
+	 *            the empire for which an update is being performed
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> on failure
+	 */
+	public boolean startPriorityUpdate( int empire );
+
+
+	/**
+	 * Upload a single research priority
+	 * 
+	 * <p>
+	 * This method uploads the priority for one of the in-progress research items. It must be called
+	 * only after a research priority update has been initiated.
+	 * 
+	 * @param identifier
+	 *            the technology's identifier
+	 * @param priority
+	 *            the research priority
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> on failure
+	 */
+	public boolean uploadResearchPriority( String identifier , int priority );
+
+
+	/**
+	 * Apply an in-progress research priority update
+	 * 
+	 * <p>
+	 * This method applies a previously uploaded set of research priority updates.
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> on failure
+	 */
+	public boolean applyPriorityUpdate( );
+
+
+	/**
+	 * Implement a technology
+	 * 
+	 * <p>
+	 * This method calls the stored procedure which causes an empire to implement a technology.
+	 * 
+	 * @param empire
+	 *            the empire's identifier
+	 * @param technology
+	 *            the technology's identifier
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> on failure
+	 */
+	public boolean implement( int empire , String technology );
+
+}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/ResearchController.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/ResearchController.java
new file mode 100644
index 0000000..e32d840
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/ResearchController.java
@@ -0,0 +1,69 @@
+package com.deepclone.lw.interfaces.game.technologies;
+
+
+import java.util.Map;
+
+import com.deepclone.lw.cmd.player.tech.GetResearchCommand;
+import com.deepclone.lw.cmd.player.tech.GetResearchResponse;
+
+
+
+/**
+ * Research control interface
+ * 
+ * <p>
+ * This interface is implemented by the component which allows empires (and therefore players) to
+ * view the research state and control it.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public interface ResearchController
+{
+
+	/**
+	 * Obtain the state of an empire's research
+	 * 
+	 * <p>
+	 * This method must be overridden to generate the response to a {@link GetResearchCommand}.
+	 * 
+	 * @param empire
+	 *            the empire's identifier
+	 * 
+	 * @return the appropriate {@link GetResearchResponse} instance
+	 */
+	public GetResearchResponse getResearchData( int empire );
+
+
+	/**
+	 * Update research priorities
+	 * 
+	 * <p>
+	 * This method must be implemented so it updates research priorities for an empire.
+	 * 
+	 * @param empire
+	 *            the empire to update
+	 * @param priorities
+	 *            the new research priorities, as a map associating technology identifiers to
+	 *            priority values
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> otherwise
+	 */
+	public boolean updatePriorities( int empire , Map< String , Integer > priorities );
+
+
+	/**
+	 * Implement a technology
+	 * 
+	 * <p>
+	 * This method implements a technology which has been fully researched.
+	 * 
+	 * @param empire
+	 *            the empire trying to implement a technology
+	 * @param technology
+	 *            the technology's identifier
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> otherwise
+	 */
+	public boolean implement( int empire , String technology );
+
+}
diff --git a/legacyworlds-server-main/pom.xml b/legacyworlds-server-main/pom.xml
index 1e4af1b..86b8a84 100644
--- a/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server-main/pom.xml
@@ -50,6 +50,10 @@
 			<artifactId>legacyworlds-server-beans-system</artifactId>
 			<groupId>com.deepclone.lw</groupId>
 		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-beans-technologies</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-updates</artifactId>
 			<groupId>com.deepclone.lw</groupId>
diff --git a/legacyworlds-server-main/src/main/resources/configuration/game.xml b/legacyworlds-server-main/src/main/resources/configuration/game.xml
index 2702f02..8a6c8d6 100644
--- a/legacyworlds-server-main/src/main/resources/configuration/game.xml
+++ b/legacyworlds-server-main/src/main/resources/configuration/game.xml
@@ -8,6 +8,7 @@
 	<!-- Spring configuration loader for all "real" game components -->
 	<!-- ========================================================== -->
 	<import resource="game/resources.xml" />
+	<import resource="game/technologies.xml" />
 	<import resource="game/updates.xml" />
 
 </beans>
\ No newline at end of file
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ImplementTechCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ImplementTechCommand.java
deleted file mode 100644
index 9a26ed7..0000000
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ImplementTechCommand.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.deepclone.lw.cmd.player;
-
-
-import com.deepclone.lw.session.Command;
-
-
-
-public class ImplementTechCommand
-		extends Command
-{
-
-	private static final long serialVersionUID = 1L;
-
-	private final int tech;
-
-
-	public ImplementTechCommand( int tech )
-	{
-		this.tech = tech;
-	}
-
-
-	public int getTech( )
-	{
-		return tech;
-	}
-
-}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
new file mode 100644
index 0000000..67add05
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
@@ -0,0 +1,373 @@
+package com.deepclone.lw.cmd.player.gdata.empire;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+
+
+/**
+ * An entry from the research page
+ * 
+ * <p>
+ * This class represents entries from the research page. It is capable of representing in-progress
+ * research (with or without details), as well as pending and implemented technologies.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class ResearchData
+		implements Serializable , Comparable< ResearchData >
+{
+
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** The identifier of the technology */
+	private String identifier;
+
+	/** The category the technology belongs to */
+	private String category;
+
+	/**
+	 * The translated name of the technology, or <code>null</code> if the technology is being
+	 * researched and progress is insufficient to display details.
+	 */
+	private String name;
+
+	/**
+	 * The description of the technology, or <code>null</code> if the technology is being researched
+	 * and progress is insufficient to display details.
+	 */
+	private String description;
+
+	/**
+	 * The implementation price of the technology, or <code>null</code> if the technology is being
+	 * researched and progress is insufficient to display details.
+	 */
+	private Long price;
+
+	/**
+	 * The completion percentage of the technology, or <code>null</code> if the technology has been
+	 * fully researched.
+	 */
+	private Integer completion;
+
+	/**
+	 * Priority of the research on this technology, or <code>null</code> if the technology is not
+	 * being researched.
+	 */
+	private Integer priority;
+
+	/**
+	 * Whether the technology is being researched (<code>null</code>), pending implementation (
+	 * <code>false</code>) or implemented (<code>true</code>).
+	 */
+	private Boolean implemented;
+
+	/** List of identifiers of technologies the current technology depends on. */
+	private List< String > dependencies = new ArrayList< String >( );
+
+	/** List of identifiers of technologies that depend on the current technology. */
+	private List< String > revDependencies = new LinkedList< String >( );
+
+
+	/**
+	 * Gets the identifier of the technology.
+	 * 
+	 * @return the identifier of the technology
+	 */
+	public String getIdentifier( )
+	{
+		return this.identifier;
+	}
+
+
+	/**
+	 * Sets the identifier of the technology.
+	 * 
+	 * @param identifier
+	 *            the new identifier of the technology
+	 */
+	public void setIdentifier( String identifier )
+	{
+		this.identifier = identifier;
+	}
+
+
+	/**
+	 * Gets the category the technology belongs to.
+	 * 
+	 * @return the category the technology belongs to
+	 */
+	public String getCategory( )
+	{
+		return this.category;
+	}
+
+
+	/**
+	 * Sets the category the technology belongs to.
+	 * 
+	 * @param category
+	 *            the new category the technology belongs to
+	 */
+	public void setCategory( String category )
+	{
+		this.category = category;
+	}
+
+
+	/**
+	 * Gets the translated name of the technology
+	 * 
+	 * @return the translated name of the technology, or <code>null</code> if the technology is
+	 *         being researched and progress is insufficient to display details
+	 */
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	/**
+	 * Sets the translated name of the technology
+	 * 
+	 * @param name
+	 *            the new translated name of the technology
+	 */
+	public void setName( String name )
+	{
+		this.name = name;
+	}
+
+
+	/**
+	 * Gets the description of the technology
+	 * 
+	 * @return the description of the technology, or <code>null</code> if the technology is being
+	 *         researched and progress is insufficient to display details
+	 */
+	public String getDescription( )
+	{
+		return this.description;
+	}
+
+
+	/**
+	 * Sets the description of the technology
+	 * 
+	 * @param description
+	 *            the new description of the technology
+	 */
+	public void setDescription( String description )
+	{
+		this.description = description;
+	}
+
+
+	/**
+	 * Gets the implementation price of the technology
+	 * 
+	 * @return the implementation price of the technology, or <code>null</code> if the technology is
+	 *         being researched and progress is insufficient to display details
+	 */
+	public Long getPrice( )
+	{
+		return this.price;
+	}
+
+
+	/**
+	 * Sets the implementation price of the technology
+	 * 
+	 * @param price
+	 *            the new implementation price of the technology
+	 */
+	public void setPrice( Long price )
+	{
+		this.price = price;
+	}
+
+
+	/**
+	 * Gets the completion percentage of the technology.
+	 * 
+	 * @return the completion percentage of the technology, or <code>null</code> if the technology
+	 *         has been fully researched
+	 */
+	public Integer getCompletion( )
+	{
+		return this.completion;
+	}
+
+
+	/**
+	 * Sets the completion percentage of the technology.
+	 * 
+	 * @param completion
+	 *            the new completion percentage of the technology
+	 */
+	public void setCompletion( Integer completion )
+	{
+		this.completion = completion;
+	}
+
+
+	/**
+	 * Gets the priority of the research on this technology
+	 * 
+	 * @return the priority of the research on this technology, or <code>null</code> if the
+	 *         technology is not being researched
+	 */
+	public Integer getPriority( )
+	{
+		return this.priority;
+	}
+
+
+	/**
+	 * Sets the priority of the research on this technology.
+	 * 
+	 * @param priority
+	 *            the new priority of the research on this technology
+	 */
+	public void setPriority( Integer priority )
+	{
+		this.priority = priority;
+	}
+
+
+	/**
+	 * Gets whether the technology is being researched (<code>null</code>), pending implementation (
+	 * <code>false</code>) or implemented (<code>true</code>).
+	 * 
+	 * @return whether the technology is being researched (<code>null</code>), pending
+	 *         implementation ( <code>false</code>) or implemented (<code>true</code>)
+	 */
+	public Boolean getImplemented( )
+	{
+		return this.implemented;
+	}
+
+
+	/**
+	 * Sets whether the technology is being researched (<code>null</code>), pending implementation (
+	 * <code>false</code>) or implemented (<code>true</code>).
+	 * 
+	 * @param implemented
+	 *            whether the technology is being researched (<code>null</code>), pending
+	 *            implementation ( <code>false</code>) or implemented (<code>true</code>)
+	 */
+	public void setImplemented( Boolean implemented )
+	{
+		this.implemented = implemented;
+	}
+
+
+	/**
+	 * Get the list of dependencies
+	 * 
+	 * @return the list of dependencies
+	 */
+	public List< String > getDependencies( )
+	{
+		return Collections.unmodifiableList( this.dependencies );
+	}
+
+
+	/**
+	 * Update the list of dependencies
+	 * 
+	 * @param dependencies
+	 *            the new list of dependencies
+	 */
+	public void setDependencies( String[] dependencies )
+	{
+		this.dependencies = Arrays.asList( dependencies );
+	}
+
+
+	/**
+	 * Get the list of reverse dependencies
+	 * 
+	 * @return the list of reverse dependencies
+	 */
+	public List< String > getReverseDependencies( )
+	{
+		return this.revDependencies;
+	}
+
+
+	/**
+	 * Add a reverse dependency
+	 * 
+	 * @param identifier
+	 *            the identifier of the reverse dependency
+	 */
+	public void addReverseDependency( String identifier )
+	{
+		this.revDependencies.add( identifier );
+	}
+
+
+	/**
+	 * Research page entry comparison
+	 * 
+	 * <p>
+	 * This method compares research entries based on:
+	 * <ul>
+	 * <li>The entries' states (in progress, pending implementation, implemented),
+	 * <li>For in-progress entries, their current completion ratio,
+	 * <li>The entries' names
+	 * </ul>
+	 * 
+	 * @param other
+	 *            the entry to compare to
+	 * 
+	 * @return 1 if the current entry is "bigger", -1 if it is "smaller", and 0 if both entries are
+	 *         equal
+	 */
+	@Override
+	public int compareTo( ResearchData other )
+	{
+		if ( other == null ) {
+			return 1;
+		}
+
+		if ( this.implemented == null && other.implemented != null ) {
+			return -1;
+		}
+		if ( other.implemented == null && this.implemented != null ) {
+			return 1;
+		}
+
+		if ( this.implemented != null ) {
+			if ( this.implemented && !other.implemented ) {
+				return 1;
+			}
+			if ( other.implemented && !this.implemented ) {
+				return -1;
+			}
+			return this.name.compareTo( other.name );
+		}
+
+		if ( this.completion != other.completion ) {
+			return other.completion - this.completion;
+		}
+		if ( this.name == null ) {
+			return other.name == null ? 0 : -1;
+		}
+		return this.name.compareTo( other.name );
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchCommand.java
new file mode 100644
index 0000000..f5961c3
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchCommand.java
@@ -0,0 +1,26 @@
+package com.deepclone.lw.cmd.player.tech;
+
+
+import com.deepclone.lw.session.Command;
+
+
+
+/**
+ * Command that obtains the current research &amp; technology data
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class GetResearchCommand
+		extends Command
+{
+
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchResponse.java
new file mode 100644
index 0000000..22fd824
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchResponse.java
@@ -0,0 +1,66 @@
+package com.deepclone.lw.cmd.player.tech;
+
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+
+
+
+/**
+ * Response that lists all research entries (sent by the server in response to
+ * {@link GetResearchCommand})
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class GetResearchResponse
+		extends GameResponseBase
+{
+
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2 with ID 1
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** Sorted list of research entries */
+	private final List< ResearchData > researchData;
+
+
+	/**
+	 * Initialise the response
+	 * 
+	 * <p>
+	 * Copy and sort the list of research entries
+	 * 
+	 * @param page
+	 *            the common page information
+	 * @param technologies
+	 *            the list of research entries
+	 */
+	public GetResearchResponse( GamePageData page , List< ResearchData > technologies )
+	{
+		super( page );
+		this.researchData = new LinkedList< ResearchData >( technologies );
+		Collections.sort( this.researchData );
+	}
+
+
+	/**
+	 * Get the list of research entries
+	 * 
+	 * @return the sorted list of research entries
+	 */
+	public List< ResearchData > getResearchData( )
+	{
+		return this.researchData;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/ImplementTechCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/ImplementTechCommand.java
new file mode 100644
index 0000000..b3dbc93
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/ImplementTechCommand.java
@@ -0,0 +1,53 @@
+package com.deepclone.lw.cmd.player.tech;
+
+
+import com.deepclone.lw.session.Command;
+
+
+
+/**
+ * Technology implementation command
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class ImplementTechCommand
+		extends Command
+{
+
+	/**
+	 * The serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M1 with ID 1
+	 * <li>Modified in B6M2, ID set to 2
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 2L;
+
+	/** Identifier of the technology to implement */
+	private final String tech;
+
+
+	/**
+	 * Initialise the command
+	 * 
+	 * @param tech
+	 *            the identifier of the technology to implement
+	 */
+	public ImplementTechCommand( String tech )
+	{
+		this.tech = tech;
+	}
+
+
+	/**
+	 * Get the identifier of the technology to implement
+	 * 
+	 * @return the identifier of the technology to implement
+	 */
+	public String getTech( )
+	{
+		return this.tech;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/UpdateResearchPrioritiesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/UpdateResearchPrioritiesCommand.java
new file mode 100644
index 0000000..4dd5b08
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/UpdateResearchPrioritiesCommand.java
@@ -0,0 +1,50 @@
+package com.deepclone.lw.cmd.player.tech;
+
+
+import java.util.Map;
+
+import com.deepclone.lw.session.Command;
+
+
+
+/**
+ * Command that updates research priorities
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class UpdateResearchPrioritiesCommand
+		extends Command
+{
+
+	/**
+	 * Serialisation version identifier
+	 * 
+	 * <ul>
+	 * <li>Introduced in B6M2
+	 * </ul>
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** The new research priorities */
+	private final Map< String , Integer > priorities;
+
+
+	/**
+	 * Initialise the command using research priority values
+	 * 
+	 * @param priorities
+	 *            a map that associates technology identifiers to priorities
+	 */
+	public UpdateResearchPrioritiesCommand( Map< String , Integer > priorities )
+	{
+		this.priorities = priorities;
+	}
+
+
+	/** @return the new research priorities */
+	public Map< String , Integer > getPriorities( )
+	{
+		return this.priorities;
+	}
+
+}
diff --git a/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
index 6d132af..f47b989 100644
--- a/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
+++ b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
@@ -17,6 +17,10 @@ import com.deepclone.lw.cmd.player.elist.*;
 import com.deepclone.lw.cmd.player.fleets.*;
 import com.deepclone.lw.cmd.player.gdata.*;
 import com.deepclone.lw.cmd.player.planets.*;
+import com.deepclone.lw.cmd.player.tech.GetResearchCommand;
+import com.deepclone.lw.cmd.player.tech.GetResearchResponse;
+import com.deepclone.lw.cmd.player.tech.ImplementTechCommand;
+import com.deepclone.lw.cmd.player.tech.UpdateResearchPrioritiesCommand;
 import com.deepclone.lw.cmd.player.msgs.*;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.SessionException;
@@ -78,10 +82,41 @@ public class PlayerSession
 	}
 
 
-	public EmpireResponse implementTechnology( int technology )
+	/**
+	 * Request research information
+	 * 
+	 * @return the research information response
+	 */
+	public GetResearchResponse getResearch( )
 			throws SessionException , SessionServerException , SessionMaintenanceException
 	{
-		return (EmpireResponse) this.execute( new ImplementTechCommand( technology ) );
+		return (GetResearchResponse) this.execute( new GetResearchCommand( ) );
+	}
+
+
+	/**
+	 * Update research priorities
+	 * 
+	 * @param priorities
+	 *            the map of research identifiers to priority values
+	 */
+	public void updateResearchPriorities( Map< String , Integer > priorities )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		this.execute( new UpdateResearchPrioritiesCommand( priorities ) );
+	}
+
+
+	/**
+	 * Implement a technology
+	 * 
+	 * @param technology
+	 *            the technology's identifier
+	 */
+	public void implementTechnology( String technology )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		this.execute( new ImplementTechCommand( technology ) );
 	}
 
 
@@ -618,4 +653,5 @@ public class PlayerSession
 	{
 		return (PostCommentResponse) this.execute( new PostCommentCommand( bugId , comment ) );
 	}
+
 }
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
index e74fbd5..ce53339 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
@@ -63,6 +63,9 @@
 			<div class="button">
 				<a href="map" title="Map of the universe">Map</a>
 			</div>
+			<div class="button">
+				<a href="research" title="Manage research priorities, implement new technologies and browse known technologies">Research</a>
+			</div>
 			<div class="button">
 				<a href="alliance" title="Alliance">Alliance</a>
 			</div>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
index 4eae28b..b0a711d 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
@@ -2,7 +2,6 @@
 <@page title="Empire">
 
 	<#assign ov = data.overview >
-	<#assign rs = data.research >
 
 	<@tabs>
 
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl
new file mode 100644
index 0000000..7e84965
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl
@@ -0,0 +1,255 @@
+<#--
+	Macro that starts a research tab for one of the 3 existing modes
+	
+	Parameters:
+		curMode		The tab to start: R for "in progress", P for pending, K for implemented
+ -->
+<#macro startResearchTab curMode>
+	<#switch curMode>
+
+		<#case 'R'>
+			<@tabStart "research" "In progress" />
+			<@rawFormStart "research-set-priorities" "form-priorities" "research" />
+			<@listviewStart />
+			<@lv_line headers=true>
+				<@lv_column width=150>Category</@lv_column>
+				<@lv_column width="x">Name</@lv_column>
+				<@lv_column width=100 centered=true>Progress</@lv_column>
+				<@lv_column width=150 centered=true>Priority</@lv_column>
+			</@lv_line>
+			<#break>
+
+		<#case 'P'>
+			<@tabStart "pending" "Pending implementation" />
+			<@listviewStart />
+			<@lv_line headers=true>
+				<@lv_column width=150>Category</@lv_column>
+				<@lv_column width="x">Name</@lv_column>
+				<@lv_column width=150 right=true>Cost</@lv_column>
+				<@lv_column width=100 centered=true>&nbsp;</@lv_column>
+			</@lv_line>
+			<#break>
+
+		<#case 'K'>
+			<@tabStart "implemented" "Implemented" />
+			<@listviewStart />
+			<@lv_line headers=true>
+				<@lv_column width=150>Category</@lv_column>
+				<@lv_column width="x">Name</@lv_column>
+			</@lv_line>
+			<#break> 
+
+	</#switch>
+</#macro>
+<#--
+	Macro that finishes a research tab depending on its type
+	
+	Parameters:
+		mode		The type of tab that is being closed
+  -->
+<#macro endResearchTab mode>
+	<#switch mode>
+
+		<#case 'R'>
+			<@lv_line headers=true>
+				<@lv_column width="x" colspan=4>&nbsp;</@lv_column>
+			</@lv_line>
+			<@lv_line>
+				<@lv_column colspan=3>&nbsp;</@lv_column>
+				<@lv_column centered=true>
+					<input type="submit" value="Update" class="input" style="margin: 15px 0 0 0" />
+				</@lv_column>
+			</@lv_line>
+			<@listviewEnd />
+			</form>
+			<#break>
+
+		<#case 'P'>
+		<#case 'K'>
+			<@listviewEnd />
+			<#break>
+
+	</#switch>
+	<@tabEnd />
+</#macro>
+<#--
+	Macro that renders an in-progress research entry
+	
+	Parameters:
+		entry		the entry
+-->
+<#macro drawInProgressResearch entry>
+	<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+		<@lv_column>${entry.category?xhtml}</@lv_column>
+		<@lv_column>
+			<#if entry.name?has_content>
+				<strong>${entry.name?xhtml}</strong>
+			<#else>
+				Unknown technology
+			</#if>
+		</@lv_column>
+		<@lv_column centered=true>${entry.completion} %</@lv_column>
+		<@lv_column centered=true>
+			<select name="rp-${entry.identifier?xhtml}" class="input">
+				<option style="padding: 0 5px" value="0" <#if entry.priority = 0>selected="selected"</#if>>lowest</option>
+				<option style="padding: 0 5px" value="1" <#if entry.priority = 1>selected="selected"</#if>>low</option>
+				<option style="padding: 0 5px" value="2" <#if entry.priority = 2>selected="selected"</#if>>normal</option>
+				<option style="padding: 0 5px" value="3" <#if entry.priority = 3>selected="selected"</#if>>high</option>
+				<option style="padding: 0 5px" value="4" <#if entry.priority = 4>selected="selected"</#if>>highest</option>
+			</select>
+		</@lv_column>
+	</@lv_line>
+</#macro>
+<#--
+	Macro that renders a pending technology
+	
+	Parameters:
+		entry		the technology
+-->
+<#macro drawPendingTechnology entry>
+	<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+		<@lv_column>${entry.category?xhtml}</@lv_column>
+		<@lv_column>
+			<strong>${entry.name?xhtml}</strong>
+		</@lv_column>
+		<@lv_column right=true>
+			<strong>${entry.price?string(",##0")}</strong> <@abbr_bgc/>
+		</@lv_column>
+		<@lv_column right=true>
+			<#if data.page.cash &gt;= entry.price>
+				<@rawFormStart "research-implement" "form-implement-${entry.identifier}" entry.identifier />
+					<input type="hidden" name="technology" value="${entry.identifier?xhtml}" />
+					<input type="submit" value="Implement" class="input" />
+				</form>
+			<#else>
+				&nbsp;
+			</#if>
+		</@lv_column>
+	</@lv_line>
+</#macro>
+<#--
+	Macro that renders an implemented technology
+	
+	Parameters:
+		entry		the technology
+-->
+<#macro drawImplementedTechnology entry>
+	<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+		<@lv_column>${entry.category?xhtml}</@lv_column>
+		<@lv_column>
+			<strong>${entry.name?xhtml}</strong>
+		</@lv_column>
+	</@lv_line>
+</#macro>
+<#--
+	Macro that renders a single entry
+	
+	Parameters:
+		mode		the current tab (see startResearchTab for details)
+		entry		the entry
+-->
+<#macro drawResearchEntry mode entry>
+	<#switch mode>
+
+		<#case 'R'>
+			<@drawInProgressResearch entry />
+			<#break>
+
+		<#case 'P'>
+			<@drawPendingTechnology entry />
+			<#break>
+
+		<#case 'K'>
+			<@drawImplementedTechnology entry />
+			<#break>
+ 
+	</#switch>
+</#macro>
+<#--
+	Render the visible part of the research page
+	
+	Parameters:
+		entries		the sorted list of research entries 
+  -->
+<#macro renderResearchTabs entries>
+	<#local prevMode = ''>
+
+	<@tabs>
+		<#list entries as entry>
+
+			<#-- Determine type of entry to display -->
+			<#if entry.implemented?has_content>
+				<#if entry.implemented>
+					<#local curMode = 'K'>
+				<#else>
+					<#local curMode = 'P'>
+				</#if>
+			<#else>
+				<#local curMode = 'R'>
+			</#if>
+
+			<#-- Start/end tabs -->
+			<#if curMode != prevMode>
+				<#if prevMode != ''>
+					<@endResearchTab prevMode />
+				</#if>
+				<@startResearchTab curMode />
+				<#local prevMode = curMode>
+			</#if>
+			
+			
+			<@drawResearchEntry curMode entry />
+	
+		</#list>
+		<@endResearchTab prevMode />
+	</@tabs>
+</#macro>
+<#--
+	Render all research information in a hidden layer which will be used by
+	the client-side code to display more information.
+  -->
+<#macro renderFullResearchData entries>
+	<div style="display: none">
+		<#list entries as tech>
+			<div class="tech-description" id="tdesc-${tech.identifier?xhtml}">
+				<h4><#if tech.name?has_content>${tech.name?xhtml}<#else>Unknown technology</#if></h4>
+				<div class="tech-info"><strong>Category:</strong> ${tech.category?xhtml}</div>
+				<#if tech.description?has_content><div class="tech-info">${tech.description?xhtml}</div></#if>
+				<#if tech.price?has_content>
+					<div class="tech-info"><strong>Implementation cost:</strong> ${tech.price?string(",##0")} <@abbr_bgc /></div>
+				</#if>
+				<#if tech.dependencies?has_content>
+					<div class="tech-info"><strong>Depends on:</strong>
+						<ul>
+							<#list tech.dependencies as depId>
+								<li class="dep">${depId}</li>
+							</#list>
+						</ul>
+					</div>
+				</#if>
+				<#if tech.reverseDependencies?has_content>
+					<div class="tech-info"><strong>Required by:</strong>
+						<ul>
+							<#list tech.reverseDependencies as depId>
+								<li class="dep">${depId}</li>
+							</#list>
+						</ul>
+					</div>
+				</#if>
+			</div>
+		</#list>
+	</div>
+	<script type="text/javascript" charset="utf-8" src="js/research.js"></script>
+</#macro>
+<#--
+	Main research page
+  -->
+<#macro render>
+<@page title="Research">
+
+	<#local entries = data.researchData>
+	<@renderResearchTabs entries />
+	<@renderFullResearchData entries />
+
+</@page>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
index c82690e..3568606 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
@@ -63,11 +63,14 @@
 			<div class="button">
 				<a href="map" title="Carte de l'univers">Carte</a>
 			</div>
+			<div class="button">
+				<a href="research" title="Gestion des priorités de recherche, implémentation et visualisation de technologies">Recherche</a>
+			</div>
 			<div class="button">
 				<a href="alliance" title="Alliance">Alliance</a>
 			</div>
 			<div class="button">
-				<a href="enemies" title="Gèstion des listes de joueurs et alliances ennemis">Listes d'ennemis</a>
+				<a href="enemies" title="Gestion des listes de joueurs et alliances ennemis">Listes d'ennemis</a>
 			</div>
 			<div class="button">
 				<a href="messages" title="Messages">Messages</a>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
index 7821aa5..c679530 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/overview.ftl
@@ -2,7 +2,6 @@
 <@page title="Empire">
 
 	<#assign ov = data.overview >
-	<#assign rs = data.research >
 
 	<@tabs>
 
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/research.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/research.ftl
new file mode 100644
index 0000000..5826b17
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/research.ftl
@@ -0,0 +1,255 @@
+<#--
+	Macro that starts a research tab for one of the 3 existing modes
+	
+	Parameters:
+		curMode		The tab to start: R for "in progress", P for pending, K for implemented
+ -->
+<#macro startResearchTab curMode>
+	<#switch curMode>
+
+		<#case 'R'>
+			<@tabStart "research" "En cours" />
+			<@rawFormStart "research-set-priorities" "form-priorities" "research" />
+			<@listviewStart />
+			<@lv_line headers=true>
+				<@lv_column width=150>Catégorie</@lv_column>
+				<@lv_column width="x">Nom</@lv_column>
+				<@lv_column width=100 centered=true>Progrès</@lv_column>
+				<@lv_column width=150 centered=true>Priorité</@lv_column>
+			</@lv_line>
+			<#break>
+
+		<#case 'P'>
+			<@tabStart "pending" "En attente" />
+			<@listviewStart />
+			<@lv_line headers=true>
+				<@lv_column width=150>Catégorie</@lv_column>
+				<@lv_column width="x">Nom</@lv_column>
+				<@lv_column width=150 right=true>Coût</@lv_column>
+				<@lv_column width=100 centered=true>&nbsp;</@lv_column>
+			</@lv_line>
+			<#break>
+
+		<#case 'K'>
+			<@tabStart "implemented" "Recherchées" />
+			<@listviewStart />
+			<@lv_line headers=true>
+				<@lv_column width=150>Catégorie</@lv_column>
+				<@lv_column width="x">Nom</@lv_column>
+			</@lv_line>
+			<#break> 
+
+	</#switch>
+</#macro>
+<#--
+	Macro that finishes a research tab depending on its type
+	
+	Parameters:
+		mode		The type of tab that is being closed
+  -->
+<#macro endResearchTab mode>
+	<#switch mode>
+
+		<#case 'R'>
+			<@lv_line headers=true>
+				<@lv_column width="x" colspan=4>&nbsp;</@lv_column>
+			</@lv_line>
+			<@lv_line>
+				<@lv_column colspan=3>&nbsp;</@lv_column>
+				<@lv_column centered=true>
+					<input type="submit" value="Modifier" class="input" style="margin: 15px 0 0 0" />
+				</@lv_column>
+			</@lv_line>
+			<@listviewEnd />
+			</form>
+			<#break>
+
+		<#case 'P'>
+		<#case 'K'>
+			<@listviewEnd />
+			<#break>
+
+	</#switch>
+	<@tabEnd />
+</#macro>
+<#--
+	Macro that renders an in-progress research entry
+	
+	Parameters:
+		entry		the entry
+-->
+<#macro drawInProgressResearch entry>
+	<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+		<@lv_column>${entry.category?xhtml}</@lv_column>
+		<@lv_column>
+			<#if entry.name?has_content>
+				<strong>${entry.name?xhtml}</strong>
+			<#else>
+				Technologie inconnue
+			</#if>
+		</@lv_column>
+		<@lv_column centered=true>${entry.completion} %</@lv_column>
+		<@lv_column centered=true>
+			<select name="rp-${entry.identifier?xhtml}" class="input">
+				<option style="padding: 0 5px" value="0" <#if entry.priority = 0>selected="selected"</#if>>très basse</option>
+				<option style="padding: 0 5px" value="1" <#if entry.priority = 1>selected="selected"</#if>>basse</option>
+				<option style="padding: 0 5px" value="2" <#if entry.priority = 2>selected="selected"</#if>>normale</option>
+				<option style="padding: 0 5px" value="3" <#if entry.priority = 3>selected="selected"</#if>>élevée</option>
+				<option style="padding: 0 5px" value="4" <#if entry.priority = 4>selected="selected"</#if>>très élevée</option>
+			</select>
+		</@lv_column>
+	</@lv_line>
+</#macro>
+<#--
+	Macro that renders a pending technology
+	
+	Parameters:
+		entry		the technology
+-->
+<#macro drawPendingTechnology entry>
+	<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+		<@lv_column>${entry.category?xhtml}</@lv_column>
+		<@lv_column>
+			<strong>${entry.name?xhtml}</strong>
+		</@lv_column>
+		<@lv_column right=true>
+			<strong>${entry.price?string(",##0")}</strong> <@abbr_bgc/>
+		</@lv_column>
+		<@lv_column right=true>
+			<#if data.page.cash &gt;= entry.price>
+				<@rawFormStart "research-implement" "form-implement-${entry.identifier}" entry.identifier />
+					<input type="hidden" name="technology" value="${entry.identifier?xhtml}" />
+					<input type="submit" value="Implémenter" class="input" />
+				</form>
+			<#else>
+				&nbsp;
+			</#if>
+		</@lv_column>
+	</@lv_line>
+</#macro>
+<#--
+	Macro that renders an implemented technology
+	
+	Parameters:
+		entry		the technology
+-->
+<#macro drawImplementedTechnology entry>
+	<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+		<@lv_column>${entry.category?xhtml}</@lv_column>
+		<@lv_column>
+			<strong>${entry.name?xhtml}</strong>
+		</@lv_column>
+	</@lv_line>
+</#macro>
+<#--
+	Macro that renders a single entry
+	
+	Parameters:
+		mode		the current tab (see startResearchTab for details)
+		entry		the entry
+-->
+<#macro drawResearchEntry mode entry>
+	<#switch mode>
+
+		<#case 'R'>
+			<@drawInProgressResearch entry />
+			<#break>
+
+		<#case 'P'>
+			<@drawPendingTechnology entry />
+			<#break>
+
+		<#case 'K'>
+			<@drawImplementedTechnology entry />
+			<#break>
+ 
+	</#switch>
+</#macro>
+<#--
+	Render the visible part of the research page
+	
+	Parameters:
+		entries		the sorted list of research entries 
+  -->
+<#macro renderResearchTabs entries>
+	<#local prevMode = ''>
+
+	<@tabs>
+		<#list entries as entry>
+
+			<#-- Determine type of entry to display -->
+			<#if entry.implemented?has_content>
+				<#if entry.implemented>
+					<#local curMode = 'K'>
+				<#else>
+					<#local curMode = 'P'>
+				</#if>
+			<#else>
+				<#local curMode = 'R'>
+			</#if>
+
+			<#-- Start/end tabs -->
+			<#if curMode != prevMode>
+				<#if prevMode != ''>
+					<@endResearchTab prevMode />
+				</#if>
+				<@startResearchTab curMode />
+				<#local prevMode = curMode>
+			</#if>
+			
+			
+			<@drawResearchEntry curMode entry />
+	
+		</#list>
+		<@endResearchTab prevMode />
+	</@tabs>
+</#macro>
+<#--
+	Render all research information in a hidden layer which will be used by
+	the client-side code to display more information.
+  -->
+<#macro renderFullResearchData entries>
+	<div style="display: none">
+		<#list entries as tech>
+			<div class="tech-description" id="tdesc-${tech.identifier?xhtml}">
+				<h4><#if tech.name?has_content>${tech.name?xhtml}<#else>Technologie inconnue</#if></h4>
+				<div class="tech-info"><strong>Catégorie:</strong> ${tech.category?xhtml}</div>
+				<#if tech.description?has_content><div class="tech-info">${tech.description?xhtml}</div></#if>
+				<#if tech.price?has_content>
+					<div class="tech-info"><strong>Coût d'implémentation:</strong> ${tech.price?string(",##0")} <@abbr_bgc /></div>
+				</#if>
+				<#if tech.dependencies?has_content>
+					<div class="tech-info"><strong>Requiert:</strong>
+						<ul>
+							<#list tech.dependencies as depId>
+								<li class="dep">${depId}</li>
+							</#list>
+						</ul>
+					</div>
+				</#if>
+				<#if tech.reverseDependencies?has_content>
+					<div class="tech-info"><strong>Requis par:</strong>
+						<ul>
+							<#list tech.reverseDependencies as depId>
+								<li class="dep">${depId}</li>
+							</#list>
+						</ul>
+					</div>
+				</#if>
+			</div>
+		</#list>
+	</div>
+	<script type="text/javascript" charset="utf-8" src="js/research.js"></script>
+</#macro>
+<#--
+	Main research page
+  -->
+<#macro render>
+<@page title="Recherche">
+
+	<#local entries = data.researchData>
+	<@renderResearchTabs entries />
+	<@renderFullResearchData entries />
+
+</@page>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/form.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/form.ftl
index 4544473..1fc8448 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/form.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/form.ftl
@@ -1,12 +1,21 @@
-<#macro form action name="" hash="">
+<#macro rawFormStart action name="" hash="">
+	<form action="${action?url}.action<#if hash != "">#${hash?url}</#if>" method="post">
+</#macro>
+<#macro formStart action name="" hash="">
 	<div class="form-container">
-		<form action="${action?url}.action<#if hash != "">#${hash?url}</#if>" method="post">
+		<@rawFormStart action name hash />
 			<table>
-				<#nested>
+</#macro>
+<#macro formEnd>
 			</table>
 		</form>
 	</div>
 </#macro>
+<#macro form action name="" hash="">
+	<@formStart action name hash />
+		<#nested>
+	<@formEnd />
+</#macro>
 <#macro form_field_line label id>
 	<tr class="form-field">
 		<th><label for="ff-${id?xhtml}">${label?xhtml}:</label></th>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
index 10b04f9..93ce018 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/lists.ftl
@@ -1,24 +1,30 @@
-<#macro listview>
+<#macro listviewStart>
 	<table class="list-view">
-		<#nested>
+</#macro> 
+<#macro listviewEnd>
 	</table>
+</#macro> 
+<#macro listview>
+	<@listviewStart />
+		<#nested>
+	<@listviewEnd />
 </#macro>
-<#macro lv_line headers=false class="">
-	<tr<#if class != ""> class="${class}<#if headers>headers</#if>"<#elseif headers> class="headers"</#if>>
+<#macro lv_line headers=false class="" id="">
+	<tr<#if class != ""> class="${class}<#if headers>headers</#if>"<#elseif headers> class="headers"</#if><#if id != ""> id="${id?xhtml}"</#if>>
 		<#nested>
 	</tr>
 </#macro>
-<#macro lv_column width=0 centered=false right=false colspan=0>
+<#macro lv_column width=0 centered=false right=false colspan=0 id="">
 	<#if width?is_string>
-		<th style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>>
+		<th style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if><#if id != ""> id="${id?xhtml}"</#if>>
 			<#nested>
 		</th>
 	<#elseif width gt 0>
-		<th style="width: ${width}px; text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>>
+		<th style="width: ${width}px; text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if><#if id != ""> id="${id?xhtml}"</#if>>
 			<#nested>
 		</th>
 	<#else>
-		<td style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>>
+		<td style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if><#if id != ""> id="${id?xhtml}"</#if>>
 			<#nested>
 		</td>
 	</#if>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/tabs.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/tabs.ftl
index b1ab861..599fcfb 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/tabs.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/layout/tabs.ftl
@@ -3,11 +3,17 @@
 		<#nested>
 	</div>
 </#macro>
-<#macro tab id title>
+<#macro tabStart id title>
 	<div class="tab" id="${id?xhtml}">
 		<h3>${title?xhtml}</h3>
 		<div class="tab-contents">
-			<#nested>
+</#macro>
+<#macro tabEnd>
 		</div>
 	</div>
+</#macro>
+<#macro tab id title>
+	<@tabStart id title />
+		<#nested>
+	<@tabEnd />
 </#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/css/main.css b/legacyworlds-web-main/Content/Raw/css/main.css
index f18397b..2fabe1c 100644
--- a/legacyworlds-web-main/Content/Raw/css/main.css
+++ b/legacyworlds-web-main/Content/Raw/css/main.css
@@ -266,10 +266,10 @@ div.button a:focus,div.button a:hover {
 
 /* Forms */
 .form-container {
-	width: -moz-calc( 100% - 128px );
-	width: -webkit-calc( 100% - 128px );
-	width: -o-calc( 100% - 128px );
-	width: calc( 100% - 128px );
+	width: -moz-calc(100% -   128px);
+	width: -webkit-calc(100% -   128px);
+	width: -o-calc(100% -   128px);
+	width: calc(100% -   128px);
 	margin: 0 64px;
 }
 
@@ -304,10 +304,10 @@ div.button a:focus,div.button a:hover {
 	padding: 5px 20px;
 }
 
-.form-submit .input:hover , .form-submit .input:focus {
+.form-submit .input:hover,.form-submit .input:focus {
 	padding: 5px 20px;
 	border-color: #dfdfdf;
-	background-color: rgba(127,127,127,0.6);
+	background-color: rgba(127, 127, 127, 0.6);
 }
 
 .form-extra td {
@@ -323,7 +323,7 @@ div.button a:focus,div.button a:hover {
 .form-error td {
 	font-size: 11pt;
 	color: white;
-	background-color: rgba(255,0,0,0.4);
+	background-color: rgba(255, 0, 0, 0.4);
 	font-weight: bold;
 	margin: 2px 0px;
 	padding: 5px 10px;
@@ -334,7 +334,7 @@ div.button a:focus,div.button a:hover {
 	border-style: solid;
 	border-width: 1px;
 	border-color: #afafaf;
-	background-color: rgba(63,63,63,0.6);
+	background-color: rgba(63, 63, 63, 0.6);
 	color: white;
 	font-size: 10pt;
 	margin: 1px 0px;
@@ -371,10 +371,10 @@ div.button a:focus,div.button a:hover {
 
 /* List display */
 .list-view {
-	width: -moz-calc( 100% - 64px );
-	width: -webkit-calc( 100% - 64px );
-	width: -o-calc( 100% - 64px );
-	width: calc( 100% - 64px );
+	width: -moz-calc(100% -   64px);
+	width: -webkit-calc(100% -   64px);
+	width: -o-calc(100% -   64px);
+	width: calc(100% -   64px);
 	margin: 0 32px 20px 32px;
 	border-collapse: collapse;
 }
@@ -604,20 +604,20 @@ table.fleets-planet,table.fleets-moving {
 	border: 1px solid white;
 	border-collapse: collapse;
 	margin: 0 0 20px 5px;
-	width: -moz-calc( 100% - 20px );
-	width: -webkit-calc( 100% - 20px );
-	width: -o-calc( 100% - 20px );
-	width: calc( 100% - 20px );
+	width: -moz-calc(100% -   20px);
+	width: -webkit-calc(100% -   20px);
+	width: -o-calc(100% -   20px);
+	width: calc(100% -   20px);
 }
 
 table.selected-fleets {
 	border: 1px solid white;
 	border-collapse: collapse;
 	margin: 10px 0 20px 15px;
-	width: -moz-calc( 100% - 30px );
-	width: -webkit-calc( 100% - 30px );
-	width: -o-calc( 100% - 30px );
-	width: calc( 100% - 30px );
+	width: -moz-calc(100% -   30px);
+	width: -webkit-calc(100% -   30px);
+	width: -o-calc(100% -   30px);
+	width: calc(100% -   30px);
 }
 
 table.fleets-planet td {
@@ -799,4 +799,20 @@ tr.alliance-msg * {
 
 tr.empire-msg * {
 	color: #afafaf;
+}
+
+/*
+ * Research page
+ */
+.tech-line {
+	height: 22px;
+}
+.tech-view h4 {
+	margin-bottom: 15px;
+}
+.tech-view .tech-info {
+	margin-bottom: 7px;
+}
+.tech-line.selected, .tech-line.selected td {
+	background-color: rgba(127, 127, 127, 0.25);
 }
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/js/research.js b/legacyworlds-web-main/Content/Raw/js/research.js
new file mode 100644
index 0000000..c9c3544
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/js/research.js
@@ -0,0 +1,87 @@
+$(function() {
+
+	/* Replace dependency identifiers */
+	$('.tech-info li.dep').each(
+			function() {
+				var _id = this.innerHTML;
+				var _targetTitle = $('#tdesc-' + _id + ' h4');
+				this.innerHTML = '';
+				$('<a/>').attr('href', '#tl-' + _id)
+						.append(_targetTitle.html()).appendTo($(this));
+			});
+
+	/* Map entries to tabs */
+	var _entries = {};
+	var _tabs = {};
+	$('.tech-line').each(
+			function() {
+				var _id = $(this).attr('id').replace(/^tl-/, '');
+				var _tab = $(this).parents('.tab-contents').attr('id').replace(
+						/^tabc-/, '');
+				_entries[_id] = _tab;
+				if (!_tabs[_tab]) {
+					_tabs[_tab] = [];
+				}
+				_tabs[_tab].push(_id);
+			});
+	var _selected = {};
+	for ( var _tab in _tabs) {
+		_selected[_tab] = _tabs[_tab][0];
+		$('#tl-' + _selected[_tab]).toggleClass('selected');
+	}
+
+	/* Insert viewing area */
+	var _viewingArea = $('<div/>').css({
+		'float' : 'right',
+		'width' : '300px',
+		'position' : 'relative',
+	}).attr('class', 'tech-view').prependTo($('#page-contents'));
+	$('div.tabs').css({
+		'margin-right' : '300px'
+	});
+
+	/* When an entry is clicked, set contents of viewing area */
+	var _displayed = '';
+	$('.tech-line').click(function() {
+		var _id = $(this).attr('id').replace(/^tl-/, '');
+		if (_id == _displayed) {
+			return;
+		}
+		_viewingArea.html($('#tdesc-' + _id).html());
+		$('a', _viewingArea).click(function() {
+			var _target = $(this).attr('href').replace(/^#tl-/, '');
+			$('#tabb-' + _entries[_target]).click();
+			$('#tl-' + _target).click();
+		});
+		if (_selected[_entries[_id]] != _id) {
+			$('#tl-' + _selected[_entries[_id]]).toggleClass('selected');
+			$(this).toggleClass('selected');
+			_selected[_entries[_id]] = _id;
+		}
+		location.hash = '#tl-'+_id;
+		return true;
+	});
+
+	/* When a tab button is clicked, select the appropriate entry */
+	$('.tab-buttons a').click(function() {
+		var _id = $(this).attr('id').replace(/^tabb-/, '');
+		var _tech = _selected[_id];
+		$('#tl-' + _tech).click();
+	});
+
+	(function() {
+		var _current = location.hash;
+		if (_current.match(/^#tl-/)) {
+			_current = _current.replace(/#tl-/, '');
+		} else if (_current.match(/^#/)) {
+			_current = _selected[_current.replace(/^#/, '')];
+		} else {
+			for ( var _tab in _selected) {
+				_current = _selected[_tab];
+				break;
+			}
+		}
+		$('#tabb-' + _entries[_current]).click();
+		$('#tl-' + _current).click();
+	})();
+});
\ No newline at end of file
diff --git a/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
index ecc8c1f..50024d2 100644
--- a/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
+++ b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
@@ -10,7 +10,6 @@ import javax.servlet.http.HttpServletRequest;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.ModelAttribute;
-import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.SessionAttributes;
@@ -73,21 +72,15 @@ public class OverviewPage
 
 
 	/**
-	 * "Implement technology" command
+	 * Empire-wide mining settings update command
 	 * 
 	 * <p>
-	 * This method is mapped to the technology implementation command URL.
+	 * This method is called when a command to update empire-wide mining settings is received.
 	 * 
 	 * @param request
 	 *            the HTTP request
-	 * @param language
-	 *            the language from the session
-	 * @param model
-	 *            the model
-	 * @param tech
-	 *            the technology identifier
 	 * 
-	 * @return the overview page rendering order
+	 * @return a redirection to the overview page's "economy" tab
 	 * 
 	 * @throws SessionException
 	 *             if some error occurs on the server
@@ -96,26 +89,8 @@ public class OverviewPage
 	 * @throws SessionMaintenanceException
 	 *             if the game is under maintenance
 	 */
-	@RequestMapping( value = "/implement-{tech}.action" , method = RequestMethod.POST )
-	public String implement( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model ,
-			@PathVariable String tech )
-			throws SessionException , SessionServerException , SessionMaintenanceException
-	{
-		int techId;
-		try {
-			techId = Integer.parseInt( tech );
-		} catch ( NumberFormatException e ) {
-			return this.redirect( "overview" );
-		}
-
-		PlayerSession pSession = this.getSession( PlayerSession.class , request );
-		return this.render( model , "game" , language , "overview" , pSession.implementTechnology( techId ) );
-	}
-
-
 	@RequestMapping( value = "/update-mining-settings.action" , method = RequestMethod.POST )
-	public String updateMiningSettings( HttpServletRequest request , @ModelAttribute( "language" ) String language ,
-			Model model )
+	public String updateMiningSettings( HttpServletRequest request )
 			throws SessionException , SessionServerException , SessionMaintenanceException
 	{
 		Map< String , Integer > miningSettings = this.getMiningSettings( request );
diff --git a/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ResearchPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ResearchPage.java
new file mode 100644
index 0000000..79c9beb
--- /dev/null
+++ b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ResearchPage.java
@@ -0,0 +1,186 @@
+package com.deepclone.lw.web.main.game;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.SessionAttributes;
+
+import com.deepclone.lw.session.SessionException;
+import com.deepclone.lw.web.beans.intercept.SessionRequirement;
+import com.deepclone.lw.web.beans.session.SessionMaintenanceException;
+import com.deepclone.lw.web.beans.session.SessionServerException;
+import com.deepclone.lw.web.beans.view.PageControllerBase;
+import com.deepclone.lw.web.csess.PlayerSession;
+
+
+
+/**
+ * Controller for the Research page
+ * 
+ * <p>
+ * This controller contains all URL handlers associated with the research page: viewing research
+ * state, setting research priorities, and implementing technologies.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@Controller
+@SessionRequirement( value = true , redirectTo = "player-session" , subType = "game" )
+@SessionAttributes( "language" )
+public class ResearchPage
+		extends PageControllerBase
+{
+
+	/**
+	 * Research page view
+	 * 
+	 * <p>
+	 * This method fetches all research information from the game server then requests the page
+	 * rendering.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * @param language
+	 *            the language from the session
+	 * @param model
+	 *            the model
+	 * 
+	 * @return the research page rendering information
+	 * 
+	 * @throws SessionException
+	 *             if some error occurs on the server
+	 * @throws SessionServerException
+	 *             if the server is unreachable
+	 * @throws SessionMaintenanceException
+	 *             if the game is under maintenance
+	 */
+	@RequestMapping( "/research" )
+	public String view( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		PlayerSession pSession = this.getSession( PlayerSession.class , request );
+		return this.render( model , "game" , language , "research" , pSession.getResearch( ) );
+	}
+
+
+	/**
+	 * Research priorities update command
+	 * 
+	 * <p>
+	 * This method handles the research priorities update command, which is triggered when the
+	 * "Update" button is clicked on the "In progress" tab of the research page.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * 
+	 * @return a redirection to the research page's "in progress" tab
+	 * 
+	 * @throws SessionException
+	 *             if some error occurs on the server
+	 * @throws SessionServerException
+	 *             if the server is unreachable
+	 * @throws SessionMaintenanceException
+	 *             if the game is under maintenance
+	 */
+	@RequestMapping( value = "/research-set-priorities.action" , method = RequestMethod.POST )
+	public String setPriorities( HttpServletRequest request )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		Map< String , Integer > priorities = this.getPriorities( request );
+		if ( priorities != null ) {
+			this.getSession( PlayerSession.class , request ).updateResearchPriorities( priorities );
+		}
+		return this.redirect( "research#research" );
+	}
+
+
+	/**
+	 * Extract research priorities from the HTTP request
+	 * 
+	 * <p>
+	 * Look for all submitted fields that begin with "rp-" then try to extract their values into a
+	 * map that associates technology identifiers to priorities.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * 
+	 * @return the map containing the submitted priorities, or <code>null</code> if one of the
+	 *         values was incorrect.
+	 */
+	private Map< String , Integer > getPriorities( HttpServletRequest request )
+	{
+		Map< String , Object > input = this.getInput( request );
+		Map< String , Integer > priorities = new HashMap< String , Integer >( );
+		for ( Entry< String , Object > entry : input.entrySet( ) ) {
+			// Ignore items which are not priorities
+			String name = entry.getKey( );
+			if ( !name.startsWith( "rp-" ) ) {
+				continue;
+			}
+			name = name.substring( 3 );
+
+			// Get values
+			if ( ! ( entry.getValue( ) instanceof String[] ) ) {
+				continue;
+			}
+			String[] values = (String[]) entry.getValue( );
+			if ( values.length < 1 ) {
+				continue;
+			}
+
+			// Pre-validate them
+			int value;
+			try {
+				value = Integer.parseInt( values[ 0 ] );
+			} catch ( NumberFormatException e ) {
+				value = -1;
+			}
+			if ( value < 0 || value > 4 ) {
+				return null;
+			}
+
+			priorities.put( name , value );
+		}
+		return priorities;
+	}
+
+
+	/**
+	 * Technology implementation command
+	 * 
+	 * <p>
+	 * This method handles the technology implementation command, which is triggered when the
+	 * "Implement" button is clicked on the "Pending" tab of the research page.
+	 * 
+	 * @param request
+	 *            the HTTP request
+	 * @param tech
+	 *            the identifier of the technology
+	 * 
+	 * @return a redirection to the research page's "Implemented" tab
+	 * 
+	 * @throws SessionException
+	 *             if some error occurs on the server
+	 * @throws SessionServerException
+	 *             if the server is unreachable
+	 * @throws SessionMaintenanceException
+	 *             if the game is under maintenance
+	 */
+	@RequestMapping( value = "/research-implement.action" , method = RequestMethod.POST )
+	public String implement( HttpServletRequest request , @RequestParam( "technology" ) String technology )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		this.getSession( PlayerSession.class , request ).implementTechnology( technology );
+		return this.redirect( "research#implemented" );
+	}
+
+}
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index bd3479b..606ac67 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -168,6 +168,11 @@
 				<groupId>com.deepclone.lw</groupId>
 				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-technologies</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
 			<dependency>
 				<artifactId>legacyworlds-server-beans-updates</artifactId>
 				<groupId>com.deepclone.lw</groupId>

From 76a01cbf1c5e2233b498fe00af687398dd01b832 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sun, 8 Apr 2012 14:29:16 +0200
Subject: [PATCH 73/94] Fixed research page bug

* In some cases, it was impossible to implement a technology or set its
research priority, due to the JS click handler exiting with a "false"
return value.
---
 legacyworlds-web-main/Content/Raw/js/research.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/legacyworlds-web-main/Content/Raw/js/research.js b/legacyworlds-web-main/Content/Raw/js/research.js
index c9c3544..31cdbfe 100644
--- a/legacyworlds-web-main/Content/Raw/js/research.js
+++ b/legacyworlds-web-main/Content/Raw/js/research.js
@@ -45,7 +45,7 @@ $(function() {
 	$('.tech-line').click(function() {
 		var _id = $(this).attr('id').replace(/^tl-/, '');
 		if (_id == _displayed) {
-			return;
+			return true;
 		}
 		_viewingArea.html($('#tdesc-' + _id).html());
 		$('a', _viewingArea).click(function() {

From 070d55dc05e5227b48b3b66408d2e60ec7aa2de3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sun, 8 Apr 2012 14:30:43 +0200
Subject: [PATCH 74/94] "Buildables" depend on tech graph

* Modified buildable definitions and loader to use technologies from the
tech graph as dependencies instead of the old research system

* Modified planet-related views and functions accordingly
---
 .../parts/030-data/090-buildables.sql         | 17 +++--
 .../parts/040-functions/030-tech.sql          | 73 +++++++++----------
 .../parts/040-functions/140-planets.sql       | 62 ++++++++--------
 legacyworlds-server-main/data/buildables.xml  | 12 +--
 legacyworlds-server-main/data/buildables.xsd  |  2 +-
 .../deepclone/lw/cli/ImportBuildables.java    | 27 ++-----
 6 files changed, 89 insertions(+), 104 deletions(-)

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql b/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql
index 3a80b4d..11eb8a2 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql
@@ -10,21 +10,26 @@
 -- "Buildables"
 --
 CREATE TABLE tech.buildables(
-	name_id			INT NOT NULL PRIMARY KEY ,
-	description_id	INT NOT NULL ,
-	cost			INT NOT NULL CHECK( cost > 0 ) ,
-	work			INT NOT NULL CHECK( work > 0 ) ,
-	upkeep			INT NOT NULL CHECK( upkeep >= 0 )
+	name_id				INT NOT NULL PRIMARY KEY ,
+	description_id		INT NOT NULL ,
+	technology_name_id	INT ,
+	cost				INT NOT NULL CHECK( cost > 0 ) ,
+	work				INT NOT NULL CHECK( work > 0 ) ,
+	upkeep				INT NOT NULL CHECK( upkeep >= 0 )
 );
 
 CREATE INDEX idx_buildables_description
 	ON tech.buildables (description_id);
+CREATE INDEX idx_buildables_technology
+	ON tech.buildables ( technology_name_id );
 
 ALTER TABLE tech.buildables
 	ADD CONSTRAINT fk_buildables_name
 		FOREIGN KEY (name_id) REFERENCES defs.strings ,
 	ADD CONSTRAINT fk_buildables_description
-		FOREIGN KEY (description_id) REFERENCES defs.strings;
+		FOREIGN KEY (description_id) REFERENCES defs.strings ,
+	ADD CONSTRAINT fk_buildables_technology
+		FOREIGN KEY (technology_name_id) REFERENCES defs.technologies;
 
 
 --
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
index 2a1f4be..58ee6ee 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
@@ -374,10 +374,8 @@ GRANT EXECUTE
 --
 
 CREATE VIEW tech.basic_buildables
-	AS SELECT b.* FROM tech.buildables b
-			LEFT OUTER JOIN tech.buildable_requirements r
-				ON r.buildable_id = b.name_id
-		WHERE r.buildable_id IS NULL;
+	AS SELECT * FROM tech.buildables
+		WHERE technology_name_id IS NULL;
 
 
 --
@@ -385,7 +383,8 @@ CREATE VIEW tech.basic_buildables
 --
 
 CREATE VIEW tech.buildings_view
-	AS SELECT b.name_id , b.description_id , b.cost , b.work , b.upkeep ,
+	AS SELECT b.name_id , b.description_id , b.technology_name_id ,
+			b.cost , b.work , b.upkeep ,
 			bld.workers , bld.output_type , bld.output
 		FROM tech.buildables b
 			INNER JOIN tech.buildings bld
@@ -397,7 +396,8 @@ CREATE VIEW tech.buildings_view
 --
 
 CREATE VIEW tech.ships_view
-	AS SELECT b.name_id , b.description_id , b.cost , b.work , b.upkeep ,
+	AS SELECT b.name_id , b.description_id , b.technology_name_id ,
+			b.cost , b.work , b.upkeep ,
 			s.flight_time , s.power
 		FROM tech.buildables b
 			INNER JOIN tech.ships s
@@ -502,50 +502,47 @@ GRANT EXECUTE ON FUNCTION tech.uoc_level( TEXT , INT , TEXT , TEXT , INT , INT )
 --	bdc		Cost
 --	bdw		Work
 --	bdu		Upkeep
---	bdtn	Dependency (name)
---	bdtl	Dependency (level)
+--	_tech	Technology dependency
 --
 -- Returns:
 --	the buildable's identifier
 --
 
-CREATE OR REPLACE FUNCTION tech.uoc_buildable( bdn TEXT , bdd TEXT , bdc INT , bdw INT , bdu INT , bdtn TEXT , bdtl INT )
+CREATE OR REPLACE FUNCTION tech.uoc_buildable( bdn TEXT , bdd TEXT , bdc INT , bdw INT , bdu INT , _tech TEXT )
 		RETURNS INT
 		STRICT
 		VOLATILE
 		SECURITY INVOKER
 	AS $$
 DECLARE
-	nid		INT;
-	did		INT;
-	tdid	INT;
+	nid			INT;
+	did			INT;
+	_tech_id	INT;
 BEGIN
 	-- Get the various translations
 	SELECT INTO nid id FROM defs.strings WHERE name = bdn;
 	SELECT INTO did id FROM defs.strings WHERE name = bdd;
-	IF bdtn <> '' THEN
-		SELECT INTO tdid tl.id FROM tech.levels tl
-			INNER JOIN defs.strings s
-				ON s.id = tl.line_id
-			WHERE s.name = bdtn AND tl.level = bdtl;
+	IF _tech <> '' THEN
+		SELECT INTO _tech_id technology_name_id
+			FROM defs.technologies
+				INNER JOIN defs.strings s
+					ON s.id = technology_name_id
+			WHERE s.name = _tech;
+	ELSE
+		_tech_id := NULL;
 	END IF;
 	
 	-- Create or update the definition
 	BEGIN
-		INSERT INTO tech.buildables ( name_id , description_id , cost , work , upkeep )
-			VALUES ( nid , did , bdc , bdw , bdu );
+		INSERT INTO tech.buildables ( name_id , description_id , technology_name_id , cost , work , upkeep )
+			VALUES ( nid , did , _tech_id , bdc , bdw , bdu );
 	EXCEPTION
 		WHEN unique_violation THEN
-			UPDATE tech.buildables SET description_id = did , cost = bdc , work = bdw , upkeep = bdu
+			UPDATE tech.buildables
+				SET description_id = did , technology_name_id = _tech_id ,
+					cost = bdc , work = bdw , upkeep = bdu
 				WHERE name_id = nid;
 	END;
-
-	-- Set dependencies
-	DELETE FROM tech.buildable_requirements WHERE buildable_id = nid;
-	IF bdtn <> '' THEN
-		INSERT INTO tech.buildable_requirements ( buildable_id , level_id )
-			VALUES ( nid , tdid );
-	END IF;
 	
 	RETURN nid;
 END;
@@ -577,7 +574,7 @@ CREATE OR REPLACE FUNCTION tech.uoc_building( bdn TEXT , bdd TEXT , bdc INT , bd
 DECLARE
 	bdid	INT;
 BEGIN
-	bdid := tech.uoc_buildable( bdn , bdd , bdc , bdw , bdu , '' , 0 );
+	bdid := tech.uoc_buildable( bdn , bdd , bdc , bdw , bdu , '' );
 	
 	PERFORM buildable_id FROM tech.ships WHERE buildable_id = bdid;
 	IF FOUND THEN
@@ -611,13 +608,12 @@ GRANT EXECUTE ON FUNCTION tech.uoc_building( TEXT , TEXT , INT , INT , INT , INT
 --	bdwk	Workers
 --	bdot	Output type
 --	bdo		Output
---	bdtn	Dependency (name)
---	bdtl	Dependency (level)
+--	_tech	Technology dependency
 --
 
 CREATE OR REPLACE FUNCTION tech.uoc_building( bdn TEXT , bdd TEXT , bdc INT , bdw INT ,
 											  bdu INT , bdwk INT , bdot building_output_type , bdo INT ,
-											  bdtn TEXT , bdtl INT )
+											  _tech TEXT )
 		RETURNS VOID
 		STRICT
 		VOLATILE
@@ -626,7 +622,7 @@ CREATE OR REPLACE FUNCTION tech.uoc_building( bdn TEXT , bdd TEXT , bdc INT , bd
 DECLARE
 	bdid	INT;
 BEGIN
-	bdid := tech.uoc_buildable( bdn , bdd , bdc , bdw , bdu , bdtn , bdtl );
+	bdid := tech.uoc_buildable( bdn , bdd , bdc , bdw , bdu , _tech );
 	
 	PERFORM buildable_id FROM tech.ships WHERE buildable_id = bdid;
 	IF FOUND THEN
@@ -644,7 +640,7 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION tech.uoc_building( TEXT , TEXT , INT , INT , INT , INT , building_output_type , INT , TEXT , INT ) TO :dbuser;
+GRANT EXECUTE ON FUNCTION tech.uoc_building( TEXT , TEXT , INT , INT , INT , INT , building_output_type , INT , TEXT ) TO :dbuser;
 
 
 
@@ -671,7 +667,7 @@ CREATE OR REPLACE FUNCTION tech.uoc_ship( sn TEXT , sd TEXT , sc INT , sw INT ,
 DECLARE
 	bdid	INT;
 BEGIN
-	bdid := tech.uoc_buildable( sn , sd , sc , sw , su , '' , 0 );
+	bdid := tech.uoc_buildable( sn , sd , sc , sw , su , '' );
 	
 	PERFORM buildable_id FROM tech.buildings WHERE buildable_id = bdid;
 	IF FOUND THEN
@@ -704,12 +700,11 @@ GRANT EXECUTE ON FUNCTION tech.uoc_ship( TEXT , TEXT , INT , INT , INT , INT , I
 --	su		Upkeep
 --	sp		Power
 --	sft		Orbital flight time
---	stdn	Tech line name
---	stdl	Tech level
+--	_tech	Technology dependency
 --
 
 CREATE OR REPLACE FUNCTION tech.uoc_ship( sn TEXT , sd TEXT , sc INT , sw INT ,
-										  su INT , sp INT , sft INT , stdn TEXT , stdl INT )
+										  su INT , sp INT , sft INT , _tech TEXT )
 		RETURNS VOID
 		STRICT
 		VOLATILE
@@ -718,7 +713,7 @@ CREATE OR REPLACE FUNCTION tech.uoc_ship( sn TEXT , sd TEXT , sc INT , sw INT ,
 DECLARE
 	bdid	INT;
 BEGIN
-	bdid := tech.uoc_buildable( sn , sd , sc , sw , su , stdn , stdl );
+	bdid := tech.uoc_buildable( sn , sd , sc , sw , su , _tech );
 	
 	PERFORM buildable_id FROM tech.buildings WHERE buildable_id = bdid;
 	IF FOUND THEN
@@ -736,6 +731,6 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION tech.uoc_ship( TEXT , TEXT , INT , INT , INT , INT , INT , TEXT , INT ) TO :dbuser;
+GRANT EXECUTE ON FUNCTION tech.uoc_ship( TEXT , TEXT , INT , INT , INT , INT , INT , TEXT ) TO :dbuser;
 
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql b/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
index 778b57d..2f7d5d9 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
@@ -468,8 +468,8 @@ AS $$
 	SELECT bv.name_id AS id , t1.translated_string AS name , t2.translated_string AS description ,
 			bv.cost AS cost ,
 			( CASE
-			    WHEN ceil( pdat.p_work ) = 0 THEN NULL
-			    ELSE ceil( bv.work / pdat.p_work )
+				WHEN ceil( pdat.p_work ) = 0 THEN NULL
+				ELSE ceil( bv.work / pdat.p_work )
 			END )::BIGINT AS time_to_build ,
 			bv.upkeep AS upkeep , bv.workers AS workers , bv.output_type AS p_type , bv.output AS p_value
 	    FROM (
@@ -478,11 +478,10 @@ AS $$
 					INNER JOIN tech.basic_buildables bb USING( name_id )
 			UNION SELECT bv.*
 			    FROM tech.buildings_view bv
-					INNER JOIN tech.buildable_requirements r ON r.buildable_id = bv.name_id
-					INNER JOIN tech.levels l ON l.id = r.level_id
 					INNER JOIN emp.planets ep ON ep.planet_id = $1
-					INNER JOIN emp.technologies t
-					    ON t.empire_id = ep.empire_id AND t.line_id = l.line_id AND t.level > l.level
+					INNER JOIN emp.technologies_v2 _emptech
+						USING ( technology_name_id , empire_id )
+				WHERE emptech_state = 'KNOWN' 
 		    ) AS bv , (
 			SELECT verse.adjust_production( ( p.population * sys.get_constant( 'game.work.wuPerPopUnit' ) )::REAL , ( ph.current / p.population )::REAL ) AS p_work ,
 				c.language_id AS language
@@ -530,11 +529,10 @@ AS $$
 					INNER JOIN tech.basic_buildables bb USING( name_id )
 			UNION SELECT bv.*
 			    FROM tech.ships_view bv
-					INNER JOIN tech.buildable_requirements r ON r.buildable_id = bv.name_id
-					INNER JOIN tech.levels l ON l.id = r.level_id
 					INNER JOIN emp.planets ep ON ep.planet_id = $1
-					INNER JOIN emp.technologies t
-					    ON t.empire_id = ep.empire_id AND t.line_id = l.line_id AND t.level > l.level
+					INNER JOIN emp.technologies_v2 t
+						USING ( empire_id , technology_name_id )
+				WHERE emptech_state = 'KNOWN' 
 		    ) AS bv , (
 			SELECT verse.adjust_production( verse.get_raw_production( $1 , 'WORK' ) , ( ph.current / p.population )::REAL ) AS p_work ,
 				c.language_id AS language
@@ -689,8 +687,8 @@ CREATE OR REPLACE FUNCTION verse.add_military_item( p_id INT , s_id INT , s_cnt
 DECLARE
 	e_id		INT;
 	qlen		INT;
-	dep_level	INT;
-	has_level	INT;
+	_needed		INT;
+	_known		INT;
 BEGIN
 	IF s_cnt < 1 THEN
 		RETURN;
@@ -709,16 +707,16 @@ BEGIN
 	END IF;
 
 	-- Check technologies
-	SELECT INTO dep_level , has_level l.level , t.level
+	SELECT INTO _needed , _known
+			b.technology_name_id , t.technology_name_id
 		FROM tech.ships s
-			LEFT OUTER JOIN tech.buildable_requirements r
-				ON r.buildable_id = s.buildable_id
-			LEFT OUTER JOIN tech.levels l
-				ON l.id = r.level_id
-			LEFT OUTER JOIN emp.technologies t
-				ON t.empire_id = e_id AND t.line_id = l.line_id AND t.level > l.level
+			INNER JOIN tech.buildables b
+				ON b.name_id = s.buildable_id
+			LEFT OUTER JOIN emp.technologies_v2 t
+				ON t.empire_id = e_id AND t.technology_name_id = b.technology_name_id
+					AND t.emptech_state = 'KNOWN'
 		WHERE s.buildable_id = s_id;
-	IF NOT FOUND OR ( has_level IS NULL AND dep_level IS NOT NULL ) THEN
+	IF NOT FOUND OR ( _known IS NULL AND _needed IS NOT NULL ) THEN
 		RETURN;
 	END IF;
 
@@ -755,8 +753,8 @@ CREATE OR REPLACE FUNCTION verse.construct_buildings( p_id INT , b_id INT , b_cn
 DECLARE
 	e_id		INT;
 	qlen		INT;
-	dep_level	INT;
-	has_level	INT;
+	_needed		INT;
+	_known		INT;
 BEGIN
 	IF b_cnt < 1 THEN
 		RETURN;
@@ -775,16 +773,16 @@ BEGIN
 	END IF;
 
 	-- Check technologies
-	SELECT INTO dep_level , has_level l.level , t.level
-		FROM tech.buildings b
-			LEFT OUTER JOIN tech.buildable_requirements r
-				ON r.buildable_id = b.buildable_id
-			LEFT OUTER JOIN tech.levels l
-				ON l.id = r.level_id
-			LEFT OUTER JOIN emp.technologies t
-				ON t.empire_id = e_id AND t.line_id = l.line_id AND t.level > l.level
-		WHERE b.buildable_id = b_id;
-	IF NOT FOUND OR ( has_level IS NULL AND dep_level IS NOT NULL ) THEN
+	SELECT INTO _needed , _known
+			b.technology_name_id , t.technology_name_id
+		FROM tech.buildings s
+			INNER JOIN tech.buildables b
+				ON b.name_id = s.buildable_id
+			LEFT OUTER JOIN emp.technologies_v2 t
+				ON t.empire_id = e_id AND t.technology_name_id = b.technology_name_id
+					AND t.emptech_state = 'KNOWN'
+		WHERE s.buildable_id = b_id;
+	IF NOT FOUND OR ( _known IS NULL AND _needed IS NOT NULL ) THEN
 		RETURN;
 	END IF;
 
diff --git a/legacyworlds-server-main/data/buildables.xml b/legacyworlds-server-main/data/buildables.xml
index 5f9ed15..5f6d22a 100644
--- a/legacyworlds-server-main/data/buildables.xml
+++ b/legacyworlds-server-main/data/buildables.xml
@@ -13,15 +13,15 @@
 	</building>
 	<building name="indFactory" description="indFactoryDescription" type="CASH" output="1" workers="500">
 		<cost build="500" upkeep="20" work="28800" />
-		<tech name="civTech" level="1" />
+		<require-technology>indFactTech</require-technology>
 	</building>
 	<building name="reanimationCentre" description="reanimationCentreDescription" type="POP" output="1" workers="300">
 		<cost build="4000" upkeep="200" work="57600" />
-		<tech name="civTech" level="2" />
+		<require-technology>reanimationTech</require-technology>
 	</building>
 	<building name="superTurret" description="superTurretDescription" type="DEF" output="500" workers="1">
 		<cost build="4000" upkeep="10" work="20000" />
-		<tech name="civTech" level="3" />
+		<require-technology>superTurretTech</require-technology>
 	</building>
 
 	<ship name="fighter" description="fighterDescription" time="3" power="10">
@@ -29,15 +29,15 @@
 	</ship>
 	<ship name="cruiser" description="cruiserDescription" time="5" power="100">
 		<cost build="500" upkeep="80" work="5000" />
-		<tech name="milTech" level="1" />
+		<require-technology>cruisersTech</require-technology>
 	</ship>
 	<ship name="bCruiser" description="bCruiserDescription" time="4" power="335">
 		<cost build="2500" upkeep="320" work="25000" />
-		<tech name="milTech" level="2" />
+		<require-technology>bCruisersTech</require-technology>
 	</ship>
 	<ship name="dreadnought" description="dreadnoughtDescription" time="6" power="5000">
 		<cost build="12500" upkeep="1280" work="125000" />
-		<tech name="milTech" level="3" />
+		<require-technology>dreadnoughtsTech</require-technology>
 	</ship>
 
 </buildables>
\ No newline at end of file
diff --git a/legacyworlds-server-main/data/buildables.xsd b/legacyworlds-server-main/data/buildables.xsd
index 0447edf..73e6a39 100644
--- a/legacyworlds-server-main/data/buildables.xsd
+++ b/legacyworlds-server-main/data/buildables.xsd
@@ -26,7 +26,7 @@
 	<xs:complexType name="buildable" abstract="true">
 		<xs:sequence>
 			<xs:element name="cost" type="cost" />
-			<xs:element name="tech" type="tech" minOccurs="0" />
+			<xs:element name="require-technology" type="xs:string" minOccurs="0" />
 		</xs:sequence>
 		<xs:attribute name="name" use="required" type="xs:string" />
 		<xs:attribute name="description" use="required" type="xs:string" />
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
index a0f4228..30124a0 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
@@ -52,7 +52,8 @@ public class ImportBuildables
 
 		public CostData cost;
 
-		public TechData tech;
+		@XStreamAlias( "require-technology" )
+		public String tech;
 	}
 
 	@SuppressWarnings( "serial" )
@@ -70,18 +71,6 @@ public class ImportBuildables
 		public int work;
 	}
 
-	@SuppressWarnings( "serial" )
-	@XStreamAlias( "tech" )
-	public static class TechData
-			implements Serializable
-	{
-		@XStreamAsAttribute
-		public String name;
-
-		@XStreamAsAttribute
-		public int level;
-	}
-
 	@SuppressWarnings( "serial" )
 	@XStreamAlias( "building" )
 	public static class BuildingData
@@ -163,7 +152,7 @@ public class ImportBuildables
 
 		// Load Hibernate bean
 		String[] cfg = {
-				"configuration/transactions.xml"
+			"configuration/transactions.xml"
 		};
 		return new ClassPathXmlApplicationContext( cfg , true , ctx );
 	}
@@ -195,8 +184,7 @@ public class ImportBuildables
 		this.uocBuildingDep.addParameter( "workers" , Types.INTEGER );
 		this.uocBuildingDep.addParameter( "output_type" , "building_output_type" );
 		this.uocBuildingDep.addParameter( "output" , Types.INTEGER );
-		this.uocBuildingDep.addParameter( "dep_name" , Types.VARCHAR );
-		this.uocBuildingDep.addParameter( "dep_level" , Types.INTEGER );
+		this.uocBuildingDep.addParameter( "_technology" , Types.VARCHAR );
 
 		this.uocShipNoDep = new StoredProc( dataSource , "tech" , "uoc_ship" );
 		this.uocShipNoDep.addParameter( "name" , Types.VARCHAR );
@@ -215,8 +203,7 @@ public class ImportBuildables
 		this.uocShipDep.addParameter( "upkeep" , Types.INTEGER );
 		this.uocShipDep.addParameter( "power" , Types.INTEGER );
 		this.uocShipDep.addParameter( "flight_time" , Types.INTEGER );
-		this.uocShipDep.addParameter( "dep_name" , Types.VARCHAR );
-		this.uocShipDep.addParameter( "dep_level" , Types.INTEGER );
+		this.uocShipDep.addParameter( "_technology" , Types.VARCHAR );
 	}
 
 
@@ -241,7 +228,7 @@ public class ImportBuildables
 					ship.cost.upkeep , ship.power , ship.time );
 		} else {
 			this.uocShipDep.execute( ship.name , ship.description , ship.cost.build , ship.cost.work ,
-					ship.cost.upkeep , ship.power , ship.time , ship.tech.name , ship.tech.level );
+					ship.cost.upkeep , ship.power , ship.time , ship.tech );
 		}
 	}
 
@@ -256,7 +243,7 @@ public class ImportBuildables
 		} else {
 			this.uocBuildingDep.execute( building.name , building.description , building.cost.build ,
 					building.cost.work , building.cost.upkeep , building.workers , building.type.toString( ) ,
-					building.output , building.tech.name , building.tech.level );
+					building.output , building.tech );
 		}
 	}
 

From 96c296e9d57a190975a73e767f25a6ec564b504b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 9 Apr 2012 11:36:09 +0200
Subject: [PATCH 75/94] Removed old research system

* Removed all tables, views and functions

* Removed references to old system in Java code, including old import
tool

* Replaced XML dump code
---
 legacyworlds-server-beans-bt/pom.xml          |   5 +
 .../lw/beans/bt/es/EmpireSummaryBean.java     |   6 +-
 .../bt/es/ResearchInformationMapper.java      |  21 +-
 .../lw/beans/bt/es/data/DebugInformation.java |   8 +-
 .../beans/bt/es/data/PlanetInformation.java   |  12 +-
 .../bt/es/data/ResearchGraphInformation.java  | 136 +++++++++++
 .../beans/bt/es/data/ResearchInformation.java |  86 ++-----
 .../bt/es/data/ResearchLineInformation.java   | 137 +++++++++++
 .../lw/beans/bt/es/data/TechnologyState.java  |  20 ++
 .../lw/beans/sys/ConstantsRegistrarBean.java  |   8 +-
 .../db-structure/parts/030-data/080-techs.sql |  53 -----
 .../parts/030-data/090-buildables.sql         |  19 --
 .../parts/030-data/110-empires.sql            |  26 ---
 .../parts/030-data/170-events.sql             |  22 --
 .../parts/040-functions/030-tech.sql          |  89 -------
 .../parts/040-functions/040-empire.sql        |  86 -------
 .../parts/040-functions/170-events.sql        |  37 ---
 .../parts/040-functions/200-bugs.sql          |   9 +-
 legacyworlds-server-main/data/techs.xml       |  23 --
 legacyworlds-server-main/data/techs.xsd       |  28 ---
 .../com/deepclone/lw/cli/ImportTechs.java     | 217 ------------------
 legacyworlds/doc/local-deployment.txt         |   2 -
 22 files changed, 345 insertions(+), 705 deletions(-)
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchGraphInformation.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchLineInformation.java
 create mode 100644 legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/TechnologyState.java
 delete mode 100644 legacyworlds-server-main/data/techs.xml
 delete mode 100644 legacyworlds-server-main/data/techs.xsd
 delete mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java

diff --git a/legacyworlds-server-beans-bt/pom.xml b/legacyworlds-server-beans-bt/pom.xml
index 378a9a5..152da94 100644
--- a/legacyworlds-server-beans-bt/pom.xml
+++ b/legacyworlds-server-beans-bt/pom.xml
@@ -15,6 +15,11 @@
 			<artifactId>xstream</artifactId>
 		</dependency>
 
+		<dependency>
+			<groupId>postgresql</groupId>
+			<artifactId>postgresql</artifactId>
+		</dependency>
+
 	</dependencies>
 
 	<artifactId>legacyworlds-server-beans-bt</artifactId>
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
index a6ec5f8..2cc7d8d 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/EmpireSummaryBean.java
@@ -21,7 +21,9 @@ import com.deepclone.lw.beans.bt.es.data.MovementInformation;
 import com.deepclone.lw.beans.bt.es.data.PlanetInformation;
 import com.deepclone.lw.beans.bt.es.data.QueueInformation;
 import com.deepclone.lw.beans.bt.es.data.QueueItemInformation;
+import com.deepclone.lw.beans.bt.es.data.ResearchGraphInformation;
 import com.deepclone.lw.beans.bt.es.data.ResearchInformation;
+import com.deepclone.lw.beans.bt.es.data.ResearchLineInformation;
 import com.deepclone.lw.beans.bt.es.data.ResourceDeltaInformation;
 import com.deepclone.lw.beans.bt.es.data.ResourceProviderInformation;
 import com.deepclone.lw.beans.bt.es.data.ShipsInformation;
@@ -122,8 +124,8 @@ public class EmpireSummaryBean
 				AccountInformation.class , AllianceInformation.class , BuildingsInformation.class ,
 				DebugInformation.class , EmpireInformation.class , FleetInformation.class , MovementInformation.class ,
 				PlanetInformation.class , QueueInformation.class , QueueItemInformation.class ,
-				ResearchInformation.class , ResourceDeltaInformation.class , ResourceProviderInformation.class ,
-				ShipsInformation.class , SystemInformation.class
+				ResearchLineInformation.class , ResearchGraphInformation.class , ResourceDeltaInformation.class ,
+				ResourceProviderInformation.class , ShipsInformation.class , SystemInformation.class
 		} );
 
 		this.mMainInfo = new DebugInformationMapper( );
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResearchInformationMapper.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResearchInformationMapper.java
index c2cc5da..09a6ce7 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResearchInformationMapper.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/ResearchInformationMapper.java
@@ -4,9 +4,12 @@ package com.deepclone.lw.beans.bt.es;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 
+import org.postgresql.util.PGobject;
 import org.springframework.jdbc.core.RowMapper;
 
+import com.deepclone.lw.beans.bt.es.data.ResearchGraphInformation;
 import com.deepclone.lw.beans.bt.es.data.ResearchInformation;
+import com.deepclone.lw.beans.bt.es.data.TechnologyState;
 
 
 
@@ -14,8 +17,8 @@ import com.deepclone.lw.beans.bt.es.data.ResearchInformation;
  * Research information row mapper
  * 
  * <p>
- * This class maps rows from <code>bugs.dump_research_view</code> into {@link ResearchInformation}
- * instances.
+ * This class maps rows from <code>bugs.dump_research_view</code> into
+ * {@link ResearchGraphInformation} instances.
  * 
  * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
  * 
@@ -28,17 +31,19 @@ final class ResearchInformationMapper
 	 * Map a <code>bugs.dump_research_view</code> row
 	 * 
 	 * <p>
-	 * Create a {@link ResearchInformation} instance from the row's contents.
+	 * Create a {@link ResearchGraphInformation} instance from the row's contents.
 	 */
 	@Override
 	public ResearchInformation mapRow( ResultSet rs , int rowNum )
 			throws SQLException
 	{
-		ResearchInformation ri = new ResearchInformation( );
-		ri.setId( rs.getInt( "line_id" ) );
-		ri.setCurrentLevel( rs.getInt( "level" ) );
-		ri.setLevelName( rs.getString( "name" ) );
-		ri.setAccumulated( rs.getDouble( "accumulated" ) );
+		ResearchGraphInformation ri = new ResearchGraphInformation( );
+		ri.setName( rs.getString( "name" ) );
+		ri.setState( TechnologyState.valueOf( ( (PGobject) rs.getObject( "state" ) ).toString( ) ) );
+		if ( ri.getState( ) == TechnologyState.RESEARCH ) {
+			ri.setPoints( rs.getDouble( "points" ) );
+			ri.setPriority( rs.getInt( "priority" ) );
+		}
 		return ri;
 	}
 }
\ No newline at end of file
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
index 2959182..ab6c15f 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/DebugInformation.java
@@ -2,7 +2,7 @@ package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
-import java.util.LinkedList;
+import java.util.ArrayList;
 import java.util.List;
 
 import com.thoughtworks.xstream.annotations.XStreamAlias;
@@ -28,13 +28,13 @@ public class DebugInformation
 	private EmpireInformation empire = new EmpireInformation( );
 
 	@XStreamAlias( "research" )
-	private List< ResearchInformation > research = new LinkedList< ResearchInformation >( );
+	private List< ResearchInformation > research = new ArrayList< ResearchInformation >( );
 
 	@XStreamAlias( "planets" )
-	private List< PlanetInformation > planets = new LinkedList< PlanetInformation >( );
+	private List< PlanetInformation > planets = new ArrayList< PlanetInformation >( );
 
 	@XStreamAlias( "fleets" )
-	private List< FleetInformation > fleets = new LinkedList< FleetInformation >( );
+	private List< FleetInformation > fleets = new ArrayList< FleetInformation >( );
 
 
 	public int getVersion( )
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
index bd60545..a03f60c 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/PlanetInformation.java
@@ -2,7 +2,7 @@ package com.deepclone.lw.beans.bt.es.data;
 
 
 import java.io.Serializable;
-import java.util.LinkedList;
+import java.util.ArrayList;
 import java.util.List;
 
 import com.thoughtworks.xstream.annotations.XStreamAlias;
@@ -59,7 +59,7 @@ public class PlanetInformation
 
 	/** List of buildings */
 	@XStreamAlias( "buildings" )
-	private final List< BuildingsInformation > buildings = new LinkedList< BuildingsInformation >( );
+	private final List< BuildingsInformation > buildings = new ArrayList< BuildingsInformation >( );
 
 	/** Civilian construction queue */
 	@XStreamAlias( "civ-queue" )
@@ -71,11 +71,11 @@ public class PlanetInformation
 
 	/** Planet resource deltas */
 	@XStreamImplicit
-	private final LinkedList< ResourceDeltaInformation > resourceDeltas = new LinkedList< ResourceDeltaInformation >( );
+	private final ArrayList< ResourceDeltaInformation > resourceDeltas = new ArrayList< ResourceDeltaInformation >( );
 
 	/** List of resource providers */
 	@XStreamImplicit( itemFieldName = "resource-provider" )
-	private final LinkedList< ResourceProviderInformation > resourceProviders = new LinkedList< ResourceProviderInformation >( );
+	private final ArrayList< ResourceProviderInformation > resourceProviders = new ArrayList< ResourceProviderInformation >( );
 
 
 	/** @return the planet's identifier */
@@ -195,14 +195,14 @@ public class PlanetInformation
 
 
 	/** @return the list of resource delta records */
-	public LinkedList< ResourceDeltaInformation > getResourceDeltas( )
+	public ArrayList< ResourceDeltaInformation > getResourceDeltas( )
 	{
 		return this.resourceDeltas;
 	}
 
 
 	/** @return the list of resource provider records */
-	public LinkedList< ResourceProviderInformation > getResourceProviders( )
+	public ArrayList< ResourceProviderInformation > getResourceProviders( )
 	{
 		return this.resourceProviders;
 	}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchGraphInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchGraphInformation.java
new file mode 100644
index 0000000..8f9e137
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchGraphInformation.java
@@ -0,0 +1,136 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * Technology from the technology graph and associated state information
+ * 
+ * <p>
+ * This class represents the state of a technology from the B6M2+ technology graph for the empire
+ * being dumped.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "technology" )
+public class ResearchGraphInformation
+		extends ResearchInformation
+{
+
+	/** The technology's name */
+	@XStreamAsAttribute
+	private String name;
+
+	/** The technology's state */
+	@XStreamAsAttribute
+	private TechnologyState state;
+
+	/**
+	 * The amount of accumulated research points, or <code>null</code> if the technology has been
+	 * researched
+	 */
+	@XStreamAsAttribute
+	private Double points;
+
+	/** The research priority, or <code>null</code> if the technology has been researched */
+	@XStreamAsAttribute
+	private Integer priority;
+
+
+	/**
+	 * Gets the technology's name.
+	 * 
+	 * @return the technology's name
+	 */
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	/**
+	 * Sets the technology's name.
+	 * 
+	 * @param name
+	 *            the technology's new name
+	 */
+	public void setName( String name )
+	{
+		this.name = name;
+	}
+
+
+	/**
+	 * Gets the technology's state.
+	 * 
+	 * @return the technology's state
+	 */
+	public TechnologyState getState( )
+	{
+		return this.state;
+	}
+
+
+	/**
+	 * Sets the technology's state.
+	 * 
+	 * @param state
+	 *            the technology's new state
+	 */
+	public void setState( TechnologyState state )
+	{
+		this.state = state;
+	}
+
+
+	/**
+	 * Gets the amount of accumulated research points
+	 * 
+	 * @return the amount of accumulated research points, or <code>null</code> if the technology has
+	 *         been researched
+	 */
+	public Double getPoints( )
+	{
+		return this.points;
+	}
+
+
+	/**
+	 * Sets the amount of accumulated research points
+	 * 
+	 * @param points
+	 *            the amount of accumulated research points
+	 */
+	public void setPoints( Double points )
+	{
+		this.points = points;
+	}
+
+
+	/**
+	 * Gets the research priority
+	 * 
+	 * @return the research priority, or <code>null</code> if the technology has been researched
+	 */
+	public Integer getPriority( )
+	{
+		return this.priority;
+	}
+
+
+	/**
+	 * Sets the research priority
+	 * 
+	 * @param priority
+	 *            the new research priority
+	 */
+	public void setPriority( Integer priority )
+	{
+		this.priority = priority;
+	}
+
+}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchInformation.java
index fc4746c..4465b2f 100644
--- a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchInformation.java
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchInformation.java
@@ -3,81 +3,23 @@ package com.deepclone.lw.beans.bt.es.data;
 
 import java.io.Serializable;
 
-import com.thoughtworks.xstream.annotations.XStreamAlias;
-import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
 
 
-
-@XStreamAlias( "research-line" )
-public class ResearchInformation
+/**
+ * Empty, base research record
+ * 
+ * <p>
+ * This class used to contain B6M1 research line entries; however, in order to support B6M2
+ * technologies while maintaining XML compatibility, it has been replaced with an empty class which
+ * servces as the base class for both old and new records.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+@SuppressWarnings( "serial" )
+public abstract class ResearchInformation
 		implements Serializable
 
 {
-
-	private static final long serialVersionUID = 1L;
-
-	@XStreamAsAttribute
-	@XStreamAlias( "line")
-	private int id;
-	
-	@XStreamAsAttribute
-	@XStreamAlias( "level")
-	private int currentLevel;
-
-	@XStreamAsAttribute
-	@XStreamAlias( "name")
-	private String levelName;
-
-	@XStreamAsAttribute
-	@XStreamAlias( "accumulated-points")
-	private double accumulated;
-
-
-	public int getId( )
-	{
-		return id;
-	}
-
-
-	public void setId( int id )
-	{
-		this.id = id;
-	}
-
-
-	public int getCurrentLevel( )
-	{
-		return currentLevel;
-	}
-
-
-	public void setCurrentLevel( int currentLevel )
-	{
-		this.currentLevel = currentLevel;
-	}
-
-
-	public String getLevelName( )
-	{
-		return levelName;
-	}
-
-
-	public void setLevelName( String levelName )
-	{
-		this.levelName = levelName;
-	}
-
-
-	public double getAccumulated( )
-	{
-		return accumulated;
-	}
-
-
-	public void setAccumulated( double accumulated )
-	{
-		this.accumulated = accumulated;
-	}
-
+	// EMPTY
 }
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchLineInformation.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchLineInformation.java
new file mode 100644
index 0000000..392ee79
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/ResearchLineInformation.java
@@ -0,0 +1,137 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * Research line information
+ * 
+ * <p>
+ * This class defines data stored in the dumps for B6M1 research lines. It is no longer actively
+ * used as of B6M2, but is kept around so the XML files can still be used if necessary.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@XStreamAlias( "research-line" )
+public class ResearchLineInformation
+		extends ResearchInformation
+{
+
+	private static final long serialVersionUID = 1L;
+
+	/** Identifier of the technology line */
+	@XStreamAsAttribute
+	@XStreamAlias( "line" )
+	private int id;
+
+	/** Current level for that line */
+	@XStreamAsAttribute
+	@XStreamAlias( "level" )
+	private int currentLevel;
+
+	/** Name of the last level */
+	@XStreamAsAttribute
+	@XStreamAlias( "name" )
+	private String levelName;
+
+	/** Accumulated points towards the next level */
+	@XStreamAsAttribute
+	@XStreamAlias( "accumulated-points" )
+	private double accumulated;
+
+
+	/**
+	 * Gets the identifier of the technology line.
+	 * 
+	 * @return the identifier of the technology line
+	 */
+	public int getId( )
+	{
+		return this.id;
+	}
+
+
+	/**
+	 * Sets the identifier of the technology line.
+	 * 
+	 * @param id
+	 *            the new identifier of the technology line
+	 */
+	public void setId( int id )
+	{
+		this.id = id;
+	}
+
+
+	/**
+	 * Gets the current level for that line.
+	 * 
+	 * @return the current level for that line
+	 */
+	public int getCurrentLevel( )
+	{
+		return this.currentLevel;
+	}
+
+
+	/**
+	 * Sets the current level for that line.
+	 * 
+	 * @param currentLevel
+	 *            the new level for that line
+	 */
+	public void setCurrentLevel( int currentLevel )
+	{
+		this.currentLevel = currentLevel;
+	}
+
+
+	/**
+	 * Gets the name of the last level.
+	 * 
+	 * @return the name of the last level
+	 */
+	public String getLevelName( )
+	{
+		return this.levelName;
+	}
+
+
+	/**
+	 * Sets the name of the last level.
+	 * 
+	 * @param levelName
+	 *            the new name of the last level
+	 */
+	public void setLevelName( String levelName )
+	{
+		this.levelName = levelName;
+	}
+
+
+	/**
+	 * Gets the accumulated points towards the next level.
+	 * 
+	 * @return the accumulated points towards the next level
+	 */
+	public double getAccumulated( )
+	{
+		return this.accumulated;
+	}
+
+
+	/**
+	 * Sets the accumulated points towards the next level.
+	 * 
+	 * @param accumulated
+	 *            the new accumulated points towards the next level
+	 */
+	public void setAccumulated( double accumulated )
+	{
+		this.accumulated = accumulated;
+	}
+
+}
diff --git a/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/TechnologyState.java b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/TechnologyState.java
new file mode 100644
index 0000000..3684b61
--- /dev/null
+++ b/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/es/data/TechnologyState.java
@@ -0,0 +1,20 @@
+package com.deepclone.lw.beans.bt.es.data;
+
+
+/**
+ * State of a (B6M2+) technology
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public enum TechnologyState {
+
+	/** The technology is being researched */
+	RESEARCH ,
+
+	/** The technology needs to be implemented */
+	PENDING ,
+
+	/** The technology has been researched and implemented */
+	KNOWN
+
+}
diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 13fa90f..5a0cf11 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -103,7 +103,7 @@ public class ConstantsRegistrarBean
 		// Work and income
 		String[] wcNames = {
 				"population" , "factory" , "strikeEffect" , "wuPerPopUnit" , "destructionRecovery" , "destructionWork" ,
-				"rpPerPopUnit" , "cancelRecovery"
+				"cancelRecovery"
 		};
 		for ( int i = 0 ; i < wcNames.length ; i++ ) {
 			wcNames[ i ] = "game.work." + wcNames[ i ];
@@ -121,10 +121,8 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( wcNames[ 4 ] , cat , cDesc , 0.1 , 0.01 , 0.99 ) );
 		cDesc = "Proportion of a building's construction work units required to destroy it";
 		defs.add( new ConstantDefinition( wcNames[ 5 ] , cat , cDesc , 0.25 , 0.01 , 1.0 ) );
-		cDesc = "Research points per population unit.";
-		defs.add( new ConstantDefinition( wcNames[ 6 ] , cat , cDesc , 0.50 , 0.01 , true ) );
 		cDesc = "Proportion of queue investments that is recovered when flushing the queue.";
-		defs.add( new ConstantDefinition( wcNames[ 7 ] , cat , cDesc , 0.1 , 0.01 , 1.0 ) );
+		defs.add( new ConstantDefinition( wcNames[ 6 ] , cat , cDesc , 0.1 , 0.01 , 1.0 ) );
 
 		// Research
 		String[] rcNames = {
@@ -158,8 +156,6 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( "vacation.cost" , "Vacation mode" , cDesc , 3.0 , 2.0 , 20.0 ) );
 		cDesc = "Income/upkeep divider used when vacation mode is active.";
 		defs.add( new ConstantDefinition( "vacation.cashDivider" , "Vacation mode" , cDesc , 3.0 , 1.1 , 10.0 ) );
-		cDesc = "Research points divider used when vacation mode is active.";
-		defs.add( new ConstantDefinition( "vacation.researchDivider" , "Vacation mode" , cDesc , 10.0 , 1.1 , 50.0 ) );
 
 		// Map names
 		cDesc = "Minimal delay between map object renaming.";
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
index de20289..2c77522 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/080-techs.sql
@@ -161,56 +161,3 @@ ALTER TABLE defs.techdep_cache
 			REFERENCES defs.techdep_cache( technology_name_id , tdcache_reverse , tdcache_id )
 				ON DELETE CASCADE;
 
-
-
-/*
- * Old B6M1 research system below.
- */
-
---
--- Technology lines
---
-
-CREATE TABLE tech.lines(
-	name_id			INT NOT NULL PRIMARY KEY ,
-	description_id	INT NOT NULL
-);
-
-CREATE INDEX idx_lines_description
-	ON tech.lines (description_id);
-
-ALTER TABLE tech.lines
-	ADD CONSTRAINT fk_lines_name
-		FOREIGN KEY (name_id) REFERENCES defs.strings ,
-	ADD CONSTRAINT fk_lines_description
-		FOREIGN KEY (description_id) REFERENCES defs.strings;
-		
-
---
--- Technology levels
---
-
-CREATE TABLE tech.levels(
-	id				SERIAL NOT NULL PRIMARY KEY ,
-	line_id			INT NOT NULL ,
-	level			INT NOT NULL CHECK( level > 0 ) ,
-	name_id			INT NOT NULL ,
-	description_id	INT NOT NULL ,
-	points			INT NOT NULL CHECK( points > 0 ) ,
-	cost			INT NOT NULL CHECK( cost > 0 )
-);
-
-CREATE UNIQUE INDEX idx_levels_linelevel
-	ON tech.levels (line_id, level);
-CREATE INDEX idx_levels_name
-	ON tech.levels (name_id);
-CREATE INDEX idx_levels_description
-	ON tech.levels (description_id);
-
-ALTER TABLE tech.levels
-	ADD CONSTRAINT fk_levels_line
-		FOREIGN KEY (line_id) REFERENCES tech.lines ,
-	ADD CONSTRAINT fk_levels_name
-		FOREIGN KEY (name_id) REFERENCES defs.strings ,
-	ADD CONSTRAINT fk_levels_description
-		FOREIGN KEY (description_id) REFERENCES defs.strings;
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql b/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql
index 11eb8a2..9fd997d 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/090-buildables.sql
@@ -32,25 +32,6 @@ ALTER TABLE tech.buildables
 		FOREIGN KEY (technology_name_id) REFERENCES defs.technologies;
 
 
---
--- Requirements
---
-CREATE TABLE tech.buildable_requirements(
-	buildable_id	INT NOT NULL ,
-	level_id		INT NOT NULL ,
-	PRIMARY KEY( buildable_id , level_id )
-);
-
-CREATE INDEX idx_buildablereqs_level
-	ON tech.buildable_requirements( level_id );
-
-ALTER TABLE tech.buildable_requirements
-	ADD CONSTRAINT fk_buildablereqs_buildable
-		FOREIGN KEY (buildable_id) REFERENCES tech.buildables ,
-	ADD CONSTRAINT fk_buildablereqs_level
-		FOREIGN KEY (level_id) REFERENCES tech.levels;
-
-
 --
 -- Buildings
 --
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
index 4480ff9..91a7c22 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
@@ -146,32 +146,6 @@ ALTER TABLE emp.technologies_v2
 		FOREIGN KEY ( technology_name_id ) REFERENCES defs.technologies;
 
 
-
---
--- Empire technologies
---
-
-CREATE TABLE emp.technologies(
-	empire_id		INT NOT NULL ,
-	line_id			INT NOT NULL ,
-	level			INT NOT NULL DEFAULT 1
-						CHECK( level > 0 ) ,
-	accumulated		REAL NOT NULL DEFAULT 0
-						CHECK( accumulated >= 0 ),
-	PRIMARY KEY( empire_id , line_id )
-);
-
-CREATE INDEX idx_technologies_line
-	ON emp.technologies (line_id);
-
-ALTER TABLE emp.technologies
-	ADD CONSTRAINT fk_technologies_empire
-		FOREIGN KEY (empire_id) REFERENCES emp.empires
-			ON DELETE CASCADE ,
-	ADD CONSTRAINT fk_technologies_line
-		FOREIGN KEY (line_id) REFERENCES tech.lines;
-
-
 --
 -- Empire planets
 -- 
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
index 186a032..87ce1fc 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
@@ -83,28 +83,6 @@ ALTER TABLE events.bqe_locations
 		FOREIGN KEY (location_id) REFERENCES verse.planets;
 
 
-
---
--- Empire events
---
-
-CREATE TABLE events.empire_events(
-	event_id		BIGINT NOT NULL PRIMARY KEY ,
-	technology_id	INT NOT NULL
-);
-
-CREATE INDEX idx_empevents_tech
-	ON events.empire_events (technology_id);
-
-ALTER TABLE events.empire_events
-	ADD CONSTRAINT fk_empevents_event
-		FOREIGN KEY (event_id) REFERENCES events.events
-			ON DELETE CASCADE,
-	ADD CONSTRAINT fk_empevents_tech
-		FOREIGN KEY (technology_id) REFERENCES tech.levels;
-
-
-
 --
 -- Fleet events
 --
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
index 58ee6ee..271609c 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
@@ -404,95 +404,6 @@ CREATE VIEW tech.ships_view
 				ON b.name_id = s.buildable_id;
 
 
-
---
--- Creates or updates a technology line
---
--- Parameters:
---	tln		Tech line name
---	tld		Tech line description
---
-
-CREATE OR REPLACE FUNCTION tech.uoc_line( tln TEXT , tld TEXT )
-		RETURNS VOID
-		STRICT
-		VOLATILE
-		SECURITY DEFINER
-	AS $$
-DECLARE
-	nid	INT;
-	did	INT;
-BEGIN
-	-- Get string identifiers
-	SELECT INTO nid id FROM defs.strings WHERE name = tln;
-	SELECT INTO did id FROM defs.strings WHERE name = tld;
-
-	-- Try creating / updating
-	BEGIN
-		INSERT INTO tech.lines ( name_id , description_id )
-			VALUES ( nid , did );
-	EXCEPTION
-		WHEN unique_violation THEN
-			UPDATE tech.lines SET description_id = did
-				WHERE name_id = nid;
-	END;
-END;
-$$ LANGUAGE plpgsql;
-
-GRANT EXECUTE ON FUNCTION tech.uoc_line( TEXT , TEXT ) TO :dbuser;
-
-
-
---
--- Creates or updates a technology level
---
--- Parameters:
---	tln		Tech line name
---	lv		Level
---	lvn		Level name
---	lvd		Level description
---	lvp		Points
---	lvc		Cost
---
-
-CREATE OR REPLACE FUNCTION tech.uoc_level( tln TEXT , lv INT , lvn TEXT , lvd TEXT , lvp INT , lvc INT )
-		RETURNS VOID
-		STRICT
-		VOLATILE
-		SECURITY DEFINER
-	AS $$
-DECLARE
-	lid		INT;
-	nid		INT;
-	did		INT;
-BEGIN
-	-- Get tech line
-	SELECT INTO lid t.name_id
-		FROM tech.lines t
-			INNER JOIN defs.strings s
-				ON s.id = t.name_id
-		WHERE s.name = tln;
-
-	-- Get name / description IDs
-	SELECT INTO nid id FROM defs.strings WHERE name = lvn;
-	SELECT INTO did id FROM defs.strings WHERE name = lvd;
-
-	-- Create or update the level
-	BEGIN
-		INSERT INTO tech.levels ( line_id , level , name_id , description_id , points , cost )
-			VALUES ( lid , lv , nid , did , lvp , lvc );
-	EXCEPTION
-		WHEN unique_violation THEN
-			UPDATE tech.levels SET name_id = nid , description_id = did , points = lvp , cost = lvc
-				WHERE line_id = lid AND level = lv;
-	END;
-END;
-$$ LANGUAGE plpgsql;
-
-GRANT EXECUTE ON FUNCTION tech.uoc_level( TEXT , INT , TEXT , TEXT , INT , INT ) to :dbuser;
-
-
-
 --
 -- Creates or updates a buildable definition
 --
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 16443aa..7b1e6ad 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -105,48 +105,6 @@ GRANT EXECUTE ON FUNCTION emp.get_current( INT ) TO :dbuser;
 
 
 
---
--- Implements a technology (OLD VERSION)
---
-
-CREATE OR REPLACE FUNCTION emp.implement_tech( e_id INT , l_id INT )
-		RETURNS VOID
-		STRICT VOLATILE
-		SECURITY DEFINER
-	AS $$
-DECLARE
-	e_cash	REAL;
-	lev		INT;
-	cost	REAL;
-BEGIN
-	SELECT INTO e_cash , lev , cost e.cash , et.level , tl.cost
-		FROM emp.empires e
-			INNER JOIN emp.technologies et
-				ON et.line_id = l_id AND et.empire_id = e.name_id
-			INNER JOIN tech.levels tl
-				ON tl.line_id = l_id AND tl.level = et.level
-					AND tl.points = floor( et.accumulated )
-					AND tl.cost <= e.cash
-		WHERE e.name_id = e_id
-		FOR UPDATE OF e , et;
-
-	IF NOT FOUND THEN
-		RETURN;
-	END IF;
-
-	UPDATE emp.empires
-		SET cash = e_cash - cost
-		WHERE name_id = e_id;
-	UPDATE emp.technologies
-		SET level = lev + 1 , accumulated = 0
-		WHERE empire_id = e_id AND line_id = l_id;
-END;
-$$ LANGUAGE plpgsql;
-
-GRANT EXECUTE ON FUNCTION emp.implement_tech( INT , INT ) TO :dbuser;
-
-
-
 --
 -- Add an enemy empire
 --
@@ -628,50 +586,6 @@ CREATE VIEW emp.overview
 GRANT SELECT ON emp.overview TO :dbuser;
 
 
---
--- Empire tech lines
---
-
-CREATE VIEW emp.tech_lines_view
-	AS SELECT e.name_id AS empire , tl.name_id AS tech_line ,
-				t1.translated_string AS name ,
-				t2.translated_string AS description
-			FROM emp.empires e
-				INNER JOIN emp.technologies et ON et.empire_id = e.name_id
-				INNER JOIN tech.lines tl ON tl.name_id = et.line_id
-				INNER JOIN naming.empire_names en ON en.id = e.name_id
-				INNER JOIN users.credentials c ON c.address_id = en.owner_id
-				INNER JOIN defs.translations t1 ON t1.string_id = tl.name_id AND t1.lang_id = c.language_id
-				INNER JOIN defs.translations t2 ON t2.string_id = tl.description_id AND t2.lang_id = c.language_id
-			ORDER BY t1.translated_string;
-
-GRANT SELECT ON emp.tech_lines_view TO :dbuser;
-
-
---
--- Empire technologies
---
-
-CREATE VIEW emp.technologies_view
-	AS SELECT e.name_id AS empire , tl.name_id AS tech_line ,
-				t1.translated_string AS name ,
-				t2.translated_string AS description ,
-				( et.level > tlv.level ) AS implemented ,
-				floor( 100 * et.accumulated / tlv.points ) AS progress ,
-				tlv.cost AS cost
-			FROM emp.empires e
-				INNER JOIN emp.technologies et ON et.empire_id = e.name_id
-				INNER JOIN tech.lines tl ON tl.name_id = et.line_id
-				INNER JOIN tech.levels tlv ON tlv.line_id = tl.name_id AND tlv.level <= et.level
-				INNER JOIN naming.empire_names en ON en.id = e.name_id
-				INNER JOIN users.credentials c ON c.address_id = en.owner_id
-				INNER JOIN defs.translations t1 ON t1.string_id = tlv.name_id AND t1.lang_id = c.language_id
-				INNER JOIN defs.translations t2 ON t2.string_id = tlv.description_id AND t2.lang_id = c.language_id
-			ORDER BY tl.name_id , tlv.level;
-
-GRANT SELECT ON emp.technologies_view TO :dbuser;
-
-
 --
 -- Enemy lists
 --
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
index 24f5e41..9e63f90 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
@@ -228,32 +228,6 @@ $$ LANGUAGE plpgsql;
 
 
 
---
--- Creates an event for a technology's availability
---
--- Parameters:
---	e_id		Empire identifier
---	t_id		Technology identifier
---
-
-CREATE OR REPLACE FUNCTION events.tech_ready_event( e_id INT , t_id INT )
-		RETURNS VOID
-		STRICT VOLATILE
-		SECURITY INVOKER
-	AS $$
-DECLARE
-	evt_id		BIGINT;
-BEGIN
-	INSERT INTO events.events ( empire_id , tick , evt_type , evt_subtype , status )
-		VALUES ( e_id , sys.get_tick( ) - 1 , 'EMPIRE' , 0 , 'READY' )
-		RETURNING event_id INTO evt_id;
-	INSERT INTO events.empire_events ( event_id , technology_id )
-		VALUES ( evt_id , t_id  );
-END;
-$$ LANGUAGE plpgsql;
-
-
-
 --
 -- Creates an event for start/end of debt
 --
@@ -856,17 +830,6 @@ CREATE VIEW events.queue_events_view
 GRANT SELECT ON events.queue_events_view TO :dbuser;
 
 
-CREATE VIEW events.empire_events_view
-	AS SELECT e.event_id AS id , e.evt_type , e.evt_subtype , e.tick , e.real_time , s.name AS technology
-			FROM events.events e
-				LEFT OUTER JOIN events.empire_events ed USING (event_id)
-				LEFT OUTER JOIN tech.levels tl ON tl.id = ed.technology_id
-				LEFT OUTER JOIN defs.strings s ON s.id = tl.name_id
-			WHERE e.evt_type = 'EMPIRE';
-
-GRANT SELECT ON events.empire_events_view TO :dbuser;
-
-
 CREATE VIEW events.fleets_events_view
 	AS SELECT e.event_id AS id , e.evt_type , e.evt_subtype , e.tick , e.real_time ,
 				ed.* , s.x , s.y , p.orbit
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
index 773e718..c339285 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
@@ -1196,11 +1196,10 @@ GRANT SELECT ON bugs.dump_main_view TO :dbuser;
 
 
 CREATE VIEW bugs.dump_research_view
-	AS SELECT et.empire_id , et.line_id AS line_id , et.level AS level ,
-				tst.name AS name , et.accumulated AS accumulated
-			FROM emp.technologies et
-				LEFT OUTER JOIN tech.levels tlv ON tlv.line_id = et.line_id AND tlv.level = et.level
-				LEFT OUTER JOIN defs.strings tst ON tst.id = tlv.name_id;
+	AS SELECT et.empire_id , tst.name AS name , et.emptech_state AS state ,
+				et.emptech_points AS points , et.emptech_priority AS priority
+			FROM emp.technologies_v2 et
+				INNER JOIN defs.strings tst ON tst.id = et.technology_name_id;
 
 GRANT SELECT ON bugs.dump_research_view TO :dbuser;
 
diff --git a/legacyworlds-server-main/data/techs.xml b/legacyworlds-server-main/data/techs.xml
deleted file mode 100644
index aef6fc6..0000000
--- a/legacyworlds-server-main/data/techs.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<technologies xmlns="http://www.deepclone.com/lw/b6/m1/techs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/techs techs.xsd">
-
-	<tech-line name="milTech" description="milTechDescription">
-		<level name="cruisersTech" description="cruisersTechDescription"
-			points="25000" cost="10000" />
-		<level name="bCruisersTech" description="bCruisersTechDescription"
-			points="900000" cost="400000" />
-		<level name="dreadnoughtsTech" description="dreadnoughtsTechDescription"
-			points="2250000" cost="1012500" />
-	</tech-line>
-
-	<tech-line name="civTech" description="civTechDescription">
-		<level name="indFactTech" description="indFactTechDescription"
-			points="10000" cost="5000" />
-		<level name="reanimationTech" description="reanimationTechDescription"
-			points="562500" cost="281250" />
-		<level name="superTurretTech" description="superTurretTechDescription"
-			points="1350000" cost="607500" />
-	</tech-line>
-
-</technologies>
\ No newline at end of file
diff --git a/legacyworlds-server-main/data/techs.xsd b/legacyworlds-server-main/data/techs.xsd
deleted file mode 100644
index 84698c3..0000000
--- a/legacyworlds-server-main/data/techs.xsd
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<xs:schema xmlns="http://www.deepclone.com/lw/b6/m1/techs" targetNamespace="http://www.deepclone.com/lw/b6/m1/techs"
-	xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
-
-	<xs:element name="technologies">
-		<xs:complexType>
-			<xs:sequence>
-				<xs:element name="tech-line" type="tech-line" minOccurs="1" maxOccurs="unbounded" />
-			</xs:sequence>
-		</xs:complexType>
-	</xs:element>
-
-	<xs:complexType name="tech-line">
-		<xs:sequence>
-			<xs:element name="level" type="tech-level" minOccurs="1" maxOccurs="unbounded" />
-		</xs:sequence>
-		<xs:attribute name="name" use="required" type="xs:token" />
-		<xs:attribute name="description" use="required" type="xs:token" />
-	</xs:complexType>
-
-	<xs:complexType name="tech-level">
-		<xs:attribute name="name" use="required" type="xs:token" />
-		<xs:attribute name="description" use="required" type="xs:token" />
-		<xs:attribute name="points" use="required" type="xs:positiveInteger" />
-		<xs:attribute name="cost" use="required" type="xs:positiveInteger" />
-	</xs:complexType>
-
-</xs:schema>
\ No newline at end of file
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
deleted file mode 100644
index bc74f83..0000000
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
+++ /dev/null
@@ -1,217 +0,0 @@
-package com.deepclone.lw.cli;
-
-
-import java.io.*;
-import java.sql.Types;
-import java.util.List;
-
-import javax.sql.DataSource;
-
-import org.apache.log4j.Logger;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.support.AbstractApplicationContext;
-import org.springframework.context.support.ClassPathXmlApplicationContext;
-import org.springframework.context.support.FileSystemXmlApplicationContext;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.TransactionCallback;
-import org.springframework.transaction.support.TransactionTemplate;
-
-import com.deepclone.lw.utils.StoredProc;
-import com.thoughtworks.xstream.XStream;
-import com.thoughtworks.xstream.annotations.XStreamAlias;
-import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
-import com.thoughtworks.xstream.annotations.XStreamImplicit;
-
-
-
-public class ImportTechs
-		extends CLITool
-{
-
-	private final Logger logger = Logger.getLogger( ImportTechs.class );
-
-	@XStreamAlias( "technologies" )
-	@SuppressWarnings( "serial" )
-	public static class Techs
-			implements Serializable
-	{
-		@XStreamImplicit( itemFieldName = "tech-line" )
-		public List< TechLine > lines;
-	}
-
-	@SuppressWarnings( "serial" )
-	public static class TechLine
-			implements Serializable
-	{
-		@XStreamAsAttribute
-		public String name;
-
-		@XStreamAsAttribute
-		public String description;
-
-		@XStreamImplicit( itemFieldName = "level" )
-		public List< TechLevel > levels;
-	}
-
-	@SuppressWarnings( "serial" )
-	public static class TechLevel
-			implements Serializable
-	{
-		@XStreamAsAttribute
-		public String name;
-
-		@XStreamAsAttribute
-		public String description;
-
-		@XStreamAsAttribute
-		public int points;
-
-		@XStreamAsAttribute
-		public int cost;
-	}
-
-	private File file;
-	private TransactionTemplate tTemplate;
-	private StoredProc uocLine;
-	private StoredProc uocLevel;
-
-
-	private XStream initXStream( )
-	{
-		XStream xstream = new XStream( );
-		xstream.processAnnotations( Techs.class );
-		return xstream;
-	}
-
-
-	private Techs loadData( )
-	{
-		FileInputStream fis;
-		try {
-			fis = new FileInputStream( this.file );
-		} catch ( FileNotFoundException e ) {
-			return null;
-		}
-
-		try {
-			XStream xstream = this.initXStream( );
-			return (Techs) xstream.fromXML( fis );
-		} catch ( Exception e ) {
-			e.printStackTrace( );
-			return null;
-		} finally {
-			try {
-				fis.close( );
-			} catch ( IOException e ) {
-				// EMPTY
-			}
-		}
-	}
-
-
-	private ClassPathXmlApplicationContext createContext( )
-	{
-		// Load data source and Hibernate properties
-		String[] dataConfig = {
-			this.getDataSource( )
-		};
-		FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( dataConfig );
-		ctx.refresh( );
-
-		// Load beans
-		String[] cfg = {
-				"configuration/transactions.xml"
-		};
-		return new ClassPathXmlApplicationContext( cfg , true , ctx );
-	}
-
-
-	private void getBeans( ApplicationContext ctx )
-	{
-		PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
-		this.tTemplate = new TransactionTemplate( tManager );
-
-		DataSource dataSource = ctx.getBean( DataSource.class );
-		this.uocLine = new StoredProc( dataSource , "tech" , "uoc_line" );
-		this.uocLine.addParameter( "tln" , Types.VARCHAR );
-		this.uocLine.addParameter( "tld" , Types.VARCHAR );
-
-		this.uocLevel = new StoredProc( dataSource , "tech" , "uoc_level" );
-		this.uocLevel.addParameter( "tech_line" , Types.VARCHAR );
-		this.uocLevel.addParameter( "level" , Types.INTEGER );
-		this.uocLevel.addParameter( "name" , Types.VARCHAR );
-		this.uocLevel.addParameter( "desc" , Types.VARCHAR );
-		this.uocLevel.addParameter( "points" , Types.INTEGER );
-		this.uocLevel.addParameter( "cost" , Types.INTEGER );
-	}
-
-
-	private void importTechnologies( Techs data )
-	{
-		for ( TechLine line : data.lines ) {
-			this.uocLine.execute( line.name , line.description );
-
-			int i = 1;
-			for ( TechLevel level : line.levels ) {
-				this.uocLevel.execute( line.name , i , level.name , level.description , level.points , level.cost );
-				i++;
-			}
-		}
-	}
-
-
-	@Override
-	public void run( )
-	{
-		final Techs data = this.loadData( );
-		if ( data == null ) {
-			System.err.println( "could not read data" );
-			return;
-		}
-
-		AbstractApplicationContext ctx = this.createContext( );
-		this.getBeans( ctx );
-		boolean rv = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
-
-			@Override
-			public Boolean doInTransaction( TransactionStatus status )
-			{
-				boolean rv;
-				try {
-					importTechnologies( data );
-					rv = true;
-				} catch ( RuntimeException e ) {
-					logger.error( e.getMessage( ) );
-					rv = false;
-				}
-				if ( !rv ) {
-					status.setRollbackOnly( );
-				}
-				return rv;
-			}
-
-		} );
-
-		if ( rv ) {
-			this.logger.info( "Import successful" );
-		}
-
-		this.tTemplate = null;
-		ToolBase.destroyContext( ctx );
-	}
-
-
-	@Override
-	public boolean setOptions( String... options )
-	{
-		if ( options.length != 1 ) {
-			return false;
-		}
-		this.file = new File( options[ 0 ] );
-		if ( ! ( this.file.isFile( ) && this.file.canRead( ) ) ) {
-			return false;
-		}
-		return true;
-	}
-}
diff --git a/legacyworlds/doc/local-deployment.txt b/legacyworlds/doc/local-deployment.txt
index b31c0ff..a2a278d 100644
--- a/legacyworlds/doc/local-deployment.txt
+++ b/legacyworlds/doc/local-deployment.txt
@@ -39,8 +39,6 @@ from the root of the server's distribution:
 
 	java -jar legacyworlds-server-main-1.0.0-0.jar \
 		--run-tool ImportText data/i18n-text.xml
-	java -jar legacyworlds-server-main-1.0.0-0.jar \
-		--run-tool ImportTechs data/techs.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \
 		--run-tool ImportTechGraph data/tech-graph.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \

From ab0475216964d8b743b304849efff411ec88d2be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 9 Apr 2012 13:08:08 +0200
Subject: [PATCH 76/94] Research page - Unlocked buildings

* The research page now includes a list of buildings unlocked by each
technology when that technology is not in the "unknown" state.
---
 .../technologies/ResearchControllerBean.java  | 27 +++++++++++++
 .../game/technologies/ResearchRowMapper.java  | 36 ++++++++++++++---
 .../parts/040-functions/030-tech.sql          | 23 +++++++++++
 .../040-functions/045-empire-research.sql     | 12 +++++-
 .../cmd/player/gdata/empire/ResearchData.java | 40 ++++++++++++++++++-
 .../Raw/WEB-INF/fm/en/types/research.ftl      |  9 +++++
 .../Raw/WEB-INF/fm/fr/types/research.ftl      |  9 +++++
 7 files changed, 149 insertions(+), 7 deletions(-)

diff --git a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java
index c1c9388..7be0c2d 100644
--- a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java
+++ b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.beans.game.technologies;
 
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -107,6 +108,8 @@ class ResearchControllerBean
 				tech.setName( translations.get( tech.getName( ) ) );
 				tech.setDescription( translations.get( tech.getDescription( ) ) );
 			}
+
+			tech.setBuildings( this.translateList( tech.getBuildings( ) , translations ) );
 		}
 
 		// Add reverse dependency identifiers
@@ -144,6 +147,7 @@ class ResearchControllerBean
 				identifiers.add( tech.getName( ) );
 				identifiers.add( tech.getDescription( ) );
 			}
+			identifiers.addAll( tech.getBuildings( ) );
 		}
 
 		try {
@@ -154,6 +158,29 @@ class ResearchControllerBean
 	}
 
 
+	/**
+	 * Translate a list of strings
+	 * 
+	 * <p>
+	 * This method fetches translations for a list of string identifiers.
+	 * 
+	 * @param input
+	 *            the list of strings to translate
+	 * @param translations
+	 *            the map of translations returned by {@link #getTranslationsFor(List, String)}
+	 * 
+	 * @return the translated list of strings
+	 */
+	private List< String > translateList( List< String > input , Map< String , String > translations )
+	{
+		ArrayList< String > result = new ArrayList< String >( input.size( ) );
+		for ( String str : input ) {
+			result.add( translations.get( str ) );
+		}
+		return result;
+	}
+
+
 	/**
 	 * Update research priorities
 	 * 
diff --git a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java
index e69c979..8f0b19f 100644
--- a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java
+++ b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java
@@ -57,13 +57,39 @@ class ResearchRowMapper
 			output.setCompletion( ratio );
 			output.setPriority( rs.getInt( "emptech_priority" ) );
 		}
-		
-		String dependencies = rs.getString( "technology_dependencies" );
-		if ( ! "".equals( dependencies ) ) {
-			output.setDependencies( dependencies.split( "," ) );
-		}
+
+		output.setDependencies( this.splitField( rs , "technology_dependencies" ) );
+		output.setBuildings( this.splitField( rs , "technology_buildings" ) );
 
 		return output;
 	}
 
+
+	/**
+	 * Extract a comma-separated field
+	 * 
+	 * <p>
+	 * This method accesses then extracts the contents of a comma-separated field (for example
+	 * dependencies or unlocked buildings).
+	 * 
+	 * @param rs
+	 *            the SQL result set
+	 * @param field
+	 *            the field's name
+	 * 
+	 * @return an array of strings containing the field's values
+	 * 
+	 * @throws SQLException
+	 *             if something goes wrong while accessing the field
+	 */
+	private String[] splitField( ResultSet rs , String field )
+			throws SQLException
+	{
+		String fValue = rs.getString( field );
+		if ( fValue == null || "".equals( fValue ) ) {
+			return new String[ 0 ];
+		}
+		return fValue.split( "," );
+	}
+
 }
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
index 271609c..212bce0 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
@@ -391,6 +391,29 @@ CREATE VIEW tech.buildings_view
 				ON b.name_id = bld.buildable_id;
 
 
+/*
+ * Buildings / technology view
+ * ----------------------------
+ * 
+ * This view generates a parseable list of buildings unlocked by a technology
+ * for each technology.
+ * 
+ * Columns:
+ *		technology_name_id		The technology's name
+ *		technology_buildings	A list of comma-separated building identifiers
+ */
+DROP VIEW IF EXISTS defs.technology_buildings_view CASCADE;
+CREATE VIEW defs.technology_buildings_view
+	AS SELECT technology_name_id ,
+			array_to_string( array_agg( _name.name ) , ',' ) AS technology_buildings
+		FROM defs.technologies _tech
+			LEFT OUTER JOIN tech.buildings_view _building
+				USING ( technology_name_id )
+			LEFT OUTER JOIN defs.strings _name
+				ON _name.id = _building.name_id
+		GROUP BY technology_name_id;
+
+
 --
 -- Ships view
 --
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
index 7860984..4b1e967 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
@@ -447,6 +447,8 @@ CREATE VIEW emp.research_total_weights_view
  *								the technology is not supposed to be visible
  *		technology_dependencies	The technology's dependencies from the
  *									dependencies view
+ *		technology_buildings	The buildings which are unlocked when the
+ *									technology is implemented
  */
 DROP VIEW IF EXISTS emp.technologies_v2_view CASCADE;
 CREATE VIEW emp.technologies_v2_view
@@ -488,7 +490,13 @@ CREATE VIEW emp.technologies_v2_view
 				ELSE
 					NULL::INT
 			END ) AS technology_price ,
-			technology_dependencies
+			technology_dependencies ,
+			( CASE
+				WHEN emptech_visible THEN
+					technology_buildings
+				ELSE
+					''
+			END ) AS technology_buildings
 		FROM emp.technologies_v2
 			INNER JOIN emp.technology_visibility_view
 				USING ( technology_name_id , empire_id )
@@ -496,6 +504,8 @@ CREATE VIEW emp.technologies_v2_view
 				USING ( technology_name_id )
 			INNER JOIN defs.technology_dependencies_view
 				USING ( technology_name_id )
+			INNER JOIN defs.technology_buildings_view
+				USING ( technology_name_id )
 			INNER JOIN defs.strings _name_str
 				ON _name_str.id = _tech.technology_name_id
 			INNER JOIN defs.strings _cat_str
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
index 67add05..a6f63d0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
@@ -78,7 +78,10 @@ public class ResearchData
 	private List< String > dependencies = new ArrayList< String >( );
 
 	/** List of identifiers of technologies that depend on the current technology. */
-	private List< String > revDependencies = new LinkedList< String >( );
+	private final List< String > revDependencies = new LinkedList< String >( );
+
+	/** List of buildings the current technology unlocks. */
+	private List< String > buildings = new ArrayList< String >( );
 
 
 	/**
@@ -320,6 +323,41 @@ public class ResearchData
 	}
 
 
+	/**
+	 * Update the list of unlocked buildings
+	 * 
+	 * @param buildings
+	 *            the new list of buildings
+	 */
+	public void setBuildings( String[] buildings )
+	{
+		this.buildings = Arrays.asList( buildings );
+	}
+
+
+	/**
+	 * Update the list of unlocked buildings
+	 * 
+	 * @param buildings
+	 *            the new list of buildings
+	 */
+	public void setBuildings( List< String > buildings )
+	{
+		this.buildings = buildings;
+	}
+
+
+	/**
+	 * Gets the list of buildings the current technology unlocks.
+	 * 
+	 * @return the list of buildings the current technology unlocks
+	 */
+	public List< String > getBuildings( )
+	{
+		return this.buildings;
+	}
+
+
 	/**
 	 * Research page entry comparison
 	 * 
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl
index 7e84965..ce4a1ba 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl
@@ -236,6 +236,15 @@
 						</ul>
 					</div>
 				</#if>
+				<#if tech.buildings?has_content>
+					<div class="tech-info"><strong>Unlocks buildings:</strong>
+						<ul>
+							<#list tech.buildings as building>
+								<li>${building}</li>
+							</#list>
+						</ul>
+					</div>
+				</#if>
 			</div>
 		</#list>
 	</div>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/research.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/research.ftl
index 5826b17..c9c982c 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/research.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/types/research.ftl
@@ -236,6 +236,15 @@
 						</ul>
 					</div>
 				</#if>
+				<#if tech.buildings?has_content>
+					<div class="tech-info"><strong>Nouveaux bâtiments:</strong>
+						<ul>
+							<#list tech.buildings as building>
+								<li>${building}</li>
+							</#list>
+						</ul>
+					</div>
+				</#if>
 			</div>
 		</#list>
 	</div>

From 071257786cbf9aad7572625eb949e89b83d9cd8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Mon, 9 Apr 2012 15:01:04 +0200
Subject: [PATCH 77/94] Renamed technology tables and views

* Removed the _v2 suffix from some tables and views.
---
 .../PlayerTechnologiesDAOBean.java            |  2 +-
 .../parts/030-data/110-empires.sql            |  8 +++---
 .../parts/040-functions/030-tech.sql          |  4 +--
 .../parts/040-functions/040-empire.sql        |  2 +-
 .../040-functions/045-empire-research.sql     | 26 +++++++++----------
 .../parts/040-functions/140-planets.sql       |  8 +++---
 .../parts/040-functions/200-bugs.sql          |  4 +--
 .../parts/050-updates/020-empire-research.sql | 10 +++----
 .../030-tech/030-uoc-technology-scale.sql     |  8 +++---
 .../040-empire/010-create-empire.sql          |  4 +--
 .../010-technology-implement.sql              | 14 +++++-----
 .../030-resprio-update-start.sql              |  4 +--
 .../050-resprio-update-apply.sql              | 10 +++----
 .../060-technology-visibility-view.sql        |  4 +--
 .../070-research-weights-view.sql             |  8 +++---
 .../090-technologies-view.sql                 | 18 ++++++-------
 .../020-technology-make-identifier.sql        |  8 +++---
 .../090-technologies-view.sql                 |  8 +++---
 .../setup-gu-research-get-empires-test.sql    | 14 +++++-----
 19 files changed, 82 insertions(+), 82 deletions(-)

diff --git a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/PlayerTechnologiesDAOBean.java b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/PlayerTechnologiesDAOBean.java
index dc4e437..ba2bf6e 100644
--- a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/PlayerTechnologiesDAOBean.java
+++ b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/PlayerTechnologiesDAOBean.java
@@ -29,7 +29,7 @@ class PlayerTechnologiesDAOBean
 {
 
 	/** SQL query that fetches an empire's research state */
-	private static final String Q_EMPIRE_RESEARCH = "SELECT * FROM emp.technologies_v2_view WHERE empire_id = ?";
+	private static final String Q_EMPIRE_RESEARCH = "SELECT * FROM emp.technologies_view WHERE empire_id = ?";
 
 	/** Row mapper for research state entries */
 	private final ResearchRowMapper mResearch;
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
index 91a7c22..54bb9b5 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/110-empires.sql
@@ -101,7 +101,7 @@ CREATE TYPE emp.technology_state
  * research system has been removed 
  */
 
-CREATE TABLE emp.technologies_v2(
+CREATE TABLE emp.technologies(
 	/* Identifier of the empire */
 	empire_id				INT NOT NULL ,
 	
@@ -136,9 +136,9 @@ CREATE TABLE emp.technologies_v2(
 );
 
 CREATE INDEX idx_emptech_technology
-	ON emp.technologies_v2 ( technology_name_id );
+	ON emp.technologies ( technology_name_id );
 
-ALTER TABLE emp.technologies_v2
+ALTER TABLE emp.technologies
 	ADD CONSTRAINT fk_emptech_empire
 		FOREIGN KEY ( empire_id ) REFERENCES emp.empires ( name_id )
 			ON DELETE CASCADE ,
@@ -358,4 +358,4 @@ ALTER TABLE emp.enemy_empires
 			ON DELETE CASCADE ,
 	ADD CONSTRAINT fk_enemyempires_enemy
 		FOREIGN KEY (enemy_id) REFERENCES emp.empires
-			ON DELETE CASCADE;
\ No newline at end of file
+			ON DELETE CASCADE;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
index 212bce0..d5779bb 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/030-tech.sql
@@ -140,7 +140,7 @@ BEGIN
 		FROM defs.technologies _def
 			LEFT OUTER JOIN (
 					SELECT technology_name_id
-						FROM emp.technologies_v2 _tech
+						FROM emp.technologies _tech
 						WHERE technology_name_id = _name_id
 								AND emptech_state = 'RESEARCH'
 						FOR UPDATE OF _tech
@@ -173,7 +173,7 @@ BEGIN
 	-- Update empire research if necessary
 	IF _old_points <> _points THEN
 		_multi := _points::DOUBLE PRECISION / _old_points::DOUBLE PRECISION;
-		UPDATE emp.technologies_v2
+		UPDATE emp.technologies
 			SET emptech_points = emptech_points * _multi
 			WHERE technology_name_id = _name_id
 				AND emptech_points IS NOT NULL;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 7b1e6ad..28f3377 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -46,7 +46,7 @@ BEGIN
 		SELECT _name_id , resource_name_id FROM defs.resources;
 
 	-- Insert technologies that have no dependencies as research in progress
-	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+	INSERT INTO emp.technologies ( empire_id , technology_name_id )
 		SELECT _name_id , technology_name_id
 			FROM defs.technologies
 				LEFT OUTER JOIN defs.technology_dependencies
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
index 4b1e967..42f3eaf 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/045-empire-research.sql
@@ -38,7 +38,7 @@ BEGIN
 	SELECT INTO _impl_data
 			technology_name_id , technology_price
 		FROM emp.empires _emp
-			INNER JOIN emp.technologies_v2 _tech
+			INNER JOIN emp.technologies _tech
 				ON _tech.empire_id = _emp.name_id
 			INNER JOIN defs.technologies _def
 				USING ( technology_name_id )
@@ -58,25 +58,25 @@ BEGIN
 	UPDATE emp.empires
 		SET cash = cash - _impl_data.technology_price
 		WHERE name_id = _empire;
-	UPDATE emp.technologies_v2
+	UPDATE emp.technologies
 		SET emptech_state = 'KNOWN'
 		WHERE empire_id = _empire
 			AND technology_name_id = _impl_data.technology_name_id;
 
 	-- Insert new research
-	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+	INSERT INTO emp.technologies ( empire_id , technology_name_id )
 		SELECT _empire , _valid.technology_name_id
 			FROM  ( SELECT _tech.technology_name_id ,
 							( COUNT(*) = COUNT(_emptech.emptech_state) ) AS emptech_has_dependencies
 						FROM defs.technologies _tech
 							INNER JOIN defs.technology_dependencies _deps
 									USING ( technology_name_id )
-							LEFT OUTER JOIN emp.technologies_v2 _emptech
+							LEFT OUTER JOIN emp.technologies _emptech
 									ON _emptech.technology_name_id = _deps.technology_name_id_depends
 										AND _emptech.emptech_state = 'KNOWN'
 										AND _emptech.empire_id = _empire
 						GROUP BY _tech.technology_name_id ) _valid
-				LEFT OUTER JOIN emp.technologies_v2 _emptech
+				LEFT OUTER JOIN emp.technologies _emptech
 					ON _emptech.empire_id = _empire
 						AND _emptech.technology_name_id = _valid.technology_name_id
 			WHERE _emptech.empire_id IS NULL AND _valid.emptech_has_dependencies;
@@ -177,7 +177,7 @@ BEGIN
 				emp.technology_make_identifier( empire_id , _str.name , emptech_visible ) ,
 				_etech.emptech_priority
 			FROM emp.empires _emp
-				INNER JOIN emp.technologies_v2 _etech
+				INNER JOIN emp.technologies _etech
 					ON _etech.empire_id = _emp.name_id
 						AND _etech.emptech_state = 'RESEARCH'
 				INNER JOIN defs.technologies _tech
@@ -270,7 +270,7 @@ CREATE FUNCTION emp.resprio_update_apply( )
 	AS $resprio_update_apply$
 BEGIN
 	
-	UPDATE emp.technologies_v2
+	UPDATE emp.technologies
 		SET emptech_priority = _emptech_priority
 		FROM rprio_update
 		WHERE _empire_id = empire_id
@@ -367,7 +367,7 @@ CREATE VIEW emp.technology_visibility_view
 				OR emptech_points >= sys.get_constant( 'game.research.visibility.points' )
 				OR emptech_points / technology_points::DOUBLE PRECISION >= sys.get_constant( 'game.research.visibility.ratio' )
 			) AS emptech_visible
-		FROM emp.technologies_v2
+		FROM emp.technologies
 			INNER JOIN defs.technologies
 				USING ( technology_name_id );
 
@@ -390,7 +390,7 @@ CREATE VIEW emp.research_weights_view
 	AS SELECT empire_id , technology_name_id ,
 			POW( sys.get_constant( 'game.research.weightBase' ) ,
 					emptech_priority ) AS emptech_weight
-		FROM emp.technologies_v2
+		FROM emp.technologies
 		WHERE emptech_state = 'RESEARCH';
 
 /*
@@ -450,8 +450,8 @@ CREATE VIEW emp.research_total_weights_view
  *		technology_buildings	The buildings which are unlocked when the
  *									technology is implemented
  */
-DROP VIEW IF EXISTS emp.technologies_v2_view CASCADE;
-CREATE VIEW emp.technologies_v2_view
+DROP VIEW IF EXISTS emp.technologies_view CASCADE;
+CREATE VIEW emp.technologies_view
 	AS SELECT empire_id ,
 			emp.technology_make_identifier( empire_id , _name_str.name , emptech_visible ) AS emptech_id ,
 			emptech_state ,
@@ -497,7 +497,7 @@ CREATE VIEW emp.technologies_v2_view
 				ELSE
 					''
 			END ) AS technology_buildings
-		FROM emp.technologies_v2
+		FROM emp.technologies
 			INNER JOIN emp.technology_visibility_view
 				USING ( technology_name_id , empire_id )
 			INNER JOIN defs.technologies _tech
@@ -514,5 +514,5 @@ CREATE VIEW emp.technologies_v2_view
 				ON _descr_str.id = _tech.technology_description_id;
 
 GRANT SELECT
-	ON emp.technologies_v2_view
+	ON emp.technologies_view
 	TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql b/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
index 2f7d5d9..8c4955a 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/140-planets.sql
@@ -479,7 +479,7 @@ AS $$
 			UNION SELECT bv.*
 			    FROM tech.buildings_view bv
 					INNER JOIN emp.planets ep ON ep.planet_id = $1
-					INNER JOIN emp.technologies_v2 _emptech
+					INNER JOIN emp.technologies _emptech
 						USING ( technology_name_id , empire_id )
 				WHERE emptech_state = 'KNOWN' 
 		    ) AS bv , (
@@ -530,7 +530,7 @@ AS $$
 			UNION SELECT bv.*
 			    FROM tech.ships_view bv
 					INNER JOIN emp.planets ep ON ep.planet_id = $1
-					INNER JOIN emp.technologies_v2 t
+					INNER JOIN emp.technologies t
 						USING ( empire_id , technology_name_id )
 				WHERE emptech_state = 'KNOWN' 
 		    ) AS bv , (
@@ -712,7 +712,7 @@ BEGIN
 		FROM tech.ships s
 			INNER JOIN tech.buildables b
 				ON b.name_id = s.buildable_id
-			LEFT OUTER JOIN emp.technologies_v2 t
+			LEFT OUTER JOIN emp.technologies t
 				ON t.empire_id = e_id AND t.technology_name_id = b.technology_name_id
 					AND t.emptech_state = 'KNOWN'
 		WHERE s.buildable_id = s_id;
@@ -778,7 +778,7 @@ BEGIN
 		FROM tech.buildings s
 			INNER JOIN tech.buildables b
 				ON b.name_id = s.buildable_id
-			LEFT OUTER JOIN emp.technologies_v2 t
+			LEFT OUTER JOIN emp.technologies t
 				ON t.empire_id = e_id AND t.technology_name_id = b.technology_name_id
 					AND t.emptech_state = 'KNOWN'
 		WHERE s.buildable_id = b_id;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
index c339285..dedf305 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/200-bugs.sql
@@ -1198,7 +1198,7 @@ GRANT SELECT ON bugs.dump_main_view TO :dbuser;
 CREATE VIEW bugs.dump_research_view
 	AS SELECT et.empire_id , tst.name AS name , et.emptech_state AS state ,
 				et.emptech_points AS points , et.emptech_priority AS priority
-			FROM emp.technologies_v2 et
+			FROM emp.technologies et
 				INNER JOIN defs.strings tst ON tst.id = et.technology_name_id;
 
 GRANT SELECT ON bugs.dump_research_view TO :dbuser;
@@ -1360,4 +1360,4 @@ CREATE VIEW bugs.dump_ships_view
 				INNER JOIN defs.strings sn ON sn.id = s.ship_id
 			ORDER BY s.ship_id;
 
-GRANT SELECT ON bugs.dump_ships_view TO :dbuser;
\ No newline at end of file
+GRANT SELECT ON bugs.dump_ships_view TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
index fa73323..6500839 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
@@ -60,7 +60,7 @@ AS $gu_research_get_empires$
 					USING ( updtgt_id , updtype_id , update_id )
 				INNER JOIN emp.empires _empire
 					USING ( name_id )
-				INNER JOIN emp.technologies_v2 _emp_tech
+				INNER JOIN emp.technologies _emp_tech
 					ON _emp_tech.empire_id = _empire.name_id
 				INNER JOIN defs.technologies _tech
 					USING ( technology_name_id )
@@ -121,7 +121,7 @@ BEGIN
 			SELECT _emp_tech.technology_name_id , _emp_tech.emptech_points , _emp_tech.emptech_priority ,
 					_points * _weights.emptech_weight / ( _totals.emptech_total_weight * 1440 ) AS emptech_new_points ,
 					_def.technology_points::DOUBLE PRECISION AS technology_points
-				FROM emp.technologies_v2 _emp_tech
+				FROM emp.technologies _emp_tech
 					INNER JOIN emp.research_weights_view _weights
 						USING ( empire_id , technology_name_id )
 					INNER JOIN emp.research_total_weights_view _totals
@@ -133,7 +133,7 @@ BEGIN
 	LOOP
 
 		IF _record.emptech_points + _record.emptech_new_points >= _record.technology_points THEN
-			UPDATE emp.technologies_v2
+			UPDATE emp.technologies
 				SET emptech_state = 'PENDING' ,
 					emptech_points = NULL ,
 					emptech_priority = NULL
@@ -141,7 +141,7 @@ BEGIN
 					AND empire_id = _empire;
 
 		ELSE
-			UPDATE emp.technologies_v2 
+			UPDATE emp.technologies 
 				SET emptech_points = emptech_points + _record.emptech_new_points
 				WHERE technology_name_id = _record.technology_name_id
 					AND empire_id = _empire;
@@ -189,4 +189,4 @@ REVOKE EXECUTE
 SELECT sys.register_update_type( 'Empires' , 'EmpireResearch' ,
 		'Empire research points are being attributed to technologies.' ,
 		'process_empire_research_updates'
-	);
\ No newline at end of file
+	);
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/030-uoc-technology-scale.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/030-uoc-technology-scale.sql
index 595bb7f..79722fa 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/030-uoc-technology-scale.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/030-tech/030-uoc-technology-scale.sql
@@ -24,11 +24,11 @@ BEGIN;
 	);
 	
 	/* Remove foreign key to empires on empire technologies */
-	ALTER TABLE emp.technologies_v2
+	ALTER TABLE emp.technologies
 		DROP CONSTRAINT fk_emptech_empire;
 
 	/* Insert records for the new technology, with different states */
-	INSERT INTO emp.technologies_v2 (
+	INSERT INTO emp.technologies (
 		empire_id , technology_name_id , emptech_state ,
 		emptech_points , emptech_priority
 	) VALUES (
@@ -46,7 +46,7 @@ BEGIN;
 	SELECT diag_test_name( 'defs.uoc_technology() - Update - Scaling of in-progress research' );
 	SELECT set_eq( $$
 		SELECT empire_id , ROUND( emptech_points )::INT
-			FROM emp.technologies_v2
+			FROM emp.technologies
 			WHERE emptech_state = 'RESEARCH'
 	$$ , $$ VALUES(
 		1 , 500
@@ -55,4 +55,4 @@ BEGIN;
 	) $$ );
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
index c079d75..9f7ba70 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
@@ -117,7 +117,7 @@ BEGIN;
 	SELECT diag_test_name( 'emp.create_empire() - Empire technologies have been initialised' );
 	SELECT set_eq( $$
 		SELECT technology_name_id , emptech_state::TEXT , emptech_points , emptech_priority
-			FROM emp.technologies_v2
+			FROM emp.technologies
 			WHERE empire_id = _get_emp_name( 'testEmp1' );
 	$$ , $$ VALUES(
 		_get_string( 'tech1' ) , 'RESEARCH' , 0.0 , 2
@@ -130,4 +130,4 @@ BEGIN;
 			AND resource_name_id = _get_string( 'natRes1' );
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/010-technology-implement.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/010-technology-implement.sql
index 8479524..5ec2087 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/010-technology-implement.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/010-technology-implement.sql
@@ -36,20 +36,20 @@ BEGIN;
 			 ( _get_string( 'tech3' ) , _get_string( 'tech2' ) );
 
 	/* Empire "emp1" has only in-progress research. */
-	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+	INSERT INTO emp.technologies ( empire_id , technology_name_id )
 		VALUES( _get_emp_name( 'emp1' ) , _get_string( 'tech1' ) );
 
 	/* Empire "emp2" has a pending technology. */
-	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
+	INSERT INTO emp.technologies ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
 		VALUES( _get_emp_name( 'emp2' ) , _get_string( 'tech1' ) , 'PENDING' , NULL , NULL );
 
 	/* Empire "emp3" has implemented 'tech1' and has 'tech2' as pending. */
-	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
+	INSERT INTO emp.technologies ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
 		VALUES( _get_emp_name( 'emp3' ) , _get_string( 'tech1' ) , 'KNOWN' , NULL , NULL ) ,
 			( _get_emp_name( 'emp3' ) , _get_string( 'tech2' ) , 'PENDING' , NULL , NULL );
 
 	/* Empire "emp4" has implemented 'tech1' and 'tech2' and has 'tech3' as pending. */
-	INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
+	INSERT INTO emp.technologies ( empire_id , technology_name_id , emptech_state , emptech_points , emptech_priority )
 		VALUES( _get_emp_name( 'emp4' ) , _get_string( 'tech1' ) , 'KNOWN' , NULL , NULL ) ,
 			( _get_emp_name( 'emp4' ) , _get_string( 'tech2' ) , 'KNOWN' , NULL , NULL ) ,
 			( _get_emp_name( 'emp4' ) , _get_string( 'tech3' ) , 'PENDING' , NULL , NULL );
@@ -72,7 +72,7 @@ BEGIN;
 	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology - No new research - Table contents' );
 	SELECT set_eq( $$
 		SELECT technology_name_id , emptech_state::TEXT
-			FROM emp.technologies_v2
+			FROM emp.technologies
 			WHERE empire_id = _get_emp_name( 'emp2' )
 	$$ , $$ VALUES(
 		_get_string( 'tech1' ) , 'KNOWN'
@@ -88,7 +88,7 @@ BEGIN;
 	SELECT diag_test_name( 'emp.technology_implement() - Call on pending technology - New research - Table contents' );
 	SELECT set_eq( $$
 		SELECT technology_name_id , emptech_state::TEXT
-			FROM emp.technologies_v2
+			FROM emp.technologies
 			WHERE empire_id = _get_emp_name( 'emp3' )
 	$$ , $$ VALUES(
 		_get_string( 'tech1' ) , 'KNOWN'
@@ -107,4 +107,4 @@ BEGIN;
 	SELECT ok( NOT emp.technology_implement( _get_emp_name( 'emp4' ) , 'tech3' ) );
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/030-resprio-update-start.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/030-resprio-update-start.sql
index 5355333..ea0cf98 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/030-resprio-update-start.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/030-resprio-update-start.sql
@@ -46,7 +46,7 @@ BEGIN;
 		AS SELECT * FROM _fake_visibility;
 
 	/* Insert empire state and data for fake views */
-	INSERT INTO emp.technologies_v2 (
+	INSERT INTO emp.technologies (
 			empire_id , technology_name_id ,
 			emptech_state , emptech_points , emptech_priority
 		) VALUES (
@@ -102,4 +102,4 @@ BEGIN;
 	DROP TABLE IF EXISTS rprio_update;
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/050-resprio-update-apply.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/050-resprio-update-apply.sql
index d7ca259..11ec682 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/050-resprio-update-apply.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/050-resprio-update-apply.sql
@@ -23,7 +23,7 @@ BEGIN;
 	INSERT INTO defs.technologies ( technology_name_id )
 		VALUES ( _get_string( 'tech1' ) );
 
-	INSERT INTO emp.technologies_v2(
+	INSERT INTO emp.technologies(
 			empire_id , technology_name_id ,
 			emptech_state , emptech_points , emptech_priority
 		) VALUES (
@@ -52,7 +52,7 @@ BEGIN;
 	SELECT diag_test_name( 'emp.resprio_update_apply() - Applying a valid update - Table contents' );
 	SELECT set_eq( $$
 		SELECT empire_id , emptech_priority
-			FROM emp.technologies_v2
+			FROM emp.technologies
 			WHERE technology_name_id = _get_string( 'tech1' );
 	$$ , $$ VALUES(
 		_get_emp_name( 'emp1' ) , 1
@@ -60,7 +60,7 @@ BEGIN;
 		_get_emp_name( 'emp2' ) , 2
 	) $$ );
 	DELETE FROM rprio_update;
-	UPDATE emp.technologies_v2
+	UPDATE emp.technologies
 		SET emptech_priority = 2
 		WHERE technology_name_id = _get_string( 'tech1' );
 
@@ -71,7 +71,7 @@ BEGIN;
 	SELECT diag_test_name( 'emp.resprio_update_apply() - Applying an invalid update - Table contents' );
 	SELECT set_eq( $$
 		SELECT empire_id , emptech_priority
-			FROM emp.technologies_v2
+			FROM emp.technologies
 	$$ , $$ VALUES(
 		_get_emp_name( 'emp1' ) , 2
 	) , (
@@ -79,4 +79,4 @@ BEGIN;
 	) $$ );
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/060-technology-visibility-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/060-technology-visibility-view.sql
index d5b22ac..1f0ab1a 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/060-technology-visibility-view.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/060-technology-visibility-view.sql
@@ -39,7 +39,7 @@ BEGIN;
 			( _get_string( 'tech6' ) , 1000 );
 
 	/* Insert empire state */
-	INSERT INTO emp.technologies_v2 (
+	INSERT INTO emp.technologies (
 			empire_id , technology_name_id ,
 			emptech_state , emptech_points , emptech_priority
 		) VALUES (
@@ -108,4 +108,4 @@ BEGIN;
 
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-research-weights-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-research-weights-view.sql
index 37f9dac..442c361 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-research-weights-view.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/070-research-weights-view.sql
@@ -3,13 +3,13 @@
  */
 BEGIN;
 	/* Remove foreign keys from the empire research table */
-	ALTER TABLE emp.technologies_v2
+	ALTER TABLE emp.technologies
 		DROP CONSTRAINT fk_emptech_empire ,
 		DROP CONSTRAINT fk_emptech_technology;
 
 	/* Insert a few records */
-	DELETE FROM emp.technologies_v2;
-	INSERT INTO emp.technologies_v2 (
+	DELETE FROM emp.technologies;
+	INSERT INTO emp.technologies (
 			empire_id , technology_name_id ,
 			emptech_state , emptech_points , emptech_priority
 		) VALUES
@@ -39,4 +39,4 @@ BEGIN;
 	$$ );
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/090-technologies-view.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/090-technologies-view.sql
index f2dd122..109f0be 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/090-technologies-view.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/045-empire-research/090-technologies-view.sql
@@ -1,5 +1,5 @@
 /*
- * Unit tests for emp.technologies_v2_view
+ * Unit tests for emp.technologies_view
  */
 BEGIN;
 	\i utils/strings.sql
@@ -54,7 +54,7 @@ BEGIN;
 		AS SELECT * FROM _fake_deps;
 
 	/* Insert empire states and data for fake views */
-	INSERT INTO emp.technologies_v2 (
+	INSERT INTO emp.technologies (
 			empire_id , technology_name_id ,
 			emptech_state , emptech_points , emptech_priority
 		) VALUES (
@@ -79,14 +79,14 @@ BEGIN;
 	-- ***** TESTS BEGIN HERE *****
 	SELECT plan( 3 );
 	
-	SELECT diag_test_name( 'emp.technologies_v2_view - Known technology' );
+	SELECT diag_test_name( 'emp.technologies_view - Known technology' );
 	SELECT set_eq( $$
 		SELECT emptech_id , emptech_state::TEXT , emptech_visible ,
 				technology_category , technology_name , technology_description ,
 				emptech_points , emptech_priority IS NULL AS ep_null ,
 				emptech_ratio IS NULL AS er_null ,
 				technology_price , technology_dependencies
-			FROM emp.technologies_v2_view
+			FROM emp.technologies_view
 			WHERE empire_id = _get_emp_name( 'emp1' )
 	$$ , $$ VALUES(
 		_get_emp_name( 'emp1' ) || ',tech1,true' , 'KNOWN' , TRUE ,
@@ -94,13 +94,13 @@ BEGIN;
 		456 ,  TRUE , TRUE , 123 , 'deps are here'
 	) $$ );
 	
-	SELECT diag_test_name( 'emp.technologies_v2_view - In-progress, visible technology' );
+	SELECT diag_test_name( 'emp.technologies_view - In-progress, visible technology' );
 	SELECT set_eq( $$
 		SELECT emptech_id , emptech_state::TEXT , emptech_visible ,
 				technology_category , technology_name , technology_description ,
 				emptech_points , emptech_priority , emptech_ratio ,
 				technology_price , technology_dependencies
-			FROM emp.technologies_v2_view
+			FROM emp.technologies_view
 			WHERE empire_id = _get_emp_name( 'emp2' )
 	$$ , $$ VALUES(
 		_get_emp_name( 'emp2' ) || ',tech1,true' , 'RESEARCH' , TRUE ,
@@ -108,13 +108,13 @@ BEGIN;
 		228 ,  0 , 50 , 123 , 'deps are here'
 	) $$ );
 
-	SELECT diag_test_name( 'emp.technologies_v2_view - In-progress, unknown technology' );
+	SELECT diag_test_name( 'emp.technologies_view - In-progress, unknown technology' );
 	SELECT set_eq( $$
 		SELECT emptech_id , emptech_state::TEXT , emptech_visible ,
 				technology_category , technology_name IS NULL AS n1 , technology_description IS NULL AS n2 ,
 				emptech_points IS NULL AS n3 , emptech_priority , emptech_ratio ,
 				technology_price IS NULL AS n4, technology_dependencies
-			FROM emp.technologies_v2_view
+			FROM emp.technologies_view
 			WHERE empire_id = _get_emp_name( 'emp3' )
 	$$ , $$ VALUES(
 		_get_emp_name( 'emp3' ) || ',tech1,false' , 'RESEARCH' , FALSE ,
@@ -122,4 +122,4 @@ BEGIN;
 		TRUE , 1 , 25 , TRUE , 'deps are here'
 	) $$ );
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/020-technology-make-identifier.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/020-technology-make-identifier.sql
index 4f33b2e..92cd282 100644
--- a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/020-technology-make-identifier.sql
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/020-technology-make-identifier.sql
@@ -5,10 +5,10 @@ BEGIN;
 
 	SELECT plan( 1 );
 	
-	SELECT diag_test_name( 'emp.technology_make_identifier() - No EXECUTE privilege' );
-	SELECT throws_ok( $$
+	SELECT diag_test_name( 'emp.technology_make_identifier() - EXECUTE privilege' );
+	SELECT lives_ok( $$
 		SELECT emp.technology_make_identifier( 1 , '' , FALSE );
-	$$ , 42501 );
+	$$ );
 	
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/090-technologies-view.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/090-technologies-view.sql
index a869fd4..cf52fd8 100644
--- a/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/090-technologies-view.sql
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/045-empire-research/090-technologies-view.sql
@@ -1,15 +1,15 @@
 /*
- * Test privileges on emp.technologies_v2_view
+ * Test privileges on emp.technologies_view
  */
 BEGIN;
 	\i utils/strings.sql
 
 	SELECT plan( 1 );
 
-	SELECT diag_test_name( 'emp.technologies_v2_view - SELECT privilege' );
+	SELECT diag_test_name( 'emp.technologies_view - SELECT privilege' );
 	SELECT lives_ok( $$
-		SELECT * FROM emp.technologies_v2_view;
+		SELECT * FROM emp.technologies_view;
 	$$ );
 
 	SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
+ROLLBACK;
diff --git a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-research-get-empires-test.sql b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-research-get-empires-test.sql
index 104a22e..9c22e59 100644
--- a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-research-get-empires-test.sql
+++ b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-research-get-empires-test.sql
@@ -47,7 +47,7 @@ INSERT INTO verse.planet_happiness( planet_id , target , current )
  */
 INSERT INTO emp.empires( name_id , cash )
 	VALUES( _get_emp_name( 'emp1' ) , 100.0 );
-INSERT INTO emp.technologies_v2 (
+INSERT INTO emp.technologies (
 		empire_id , technology_name_id ,
 		emptech_state , emptech_points , emptech_priority )
 	SELECT _get_emp_name( 'emp1' ) , technology_name_id , 'KNOWN' , NULL , NULL
@@ -59,7 +59,7 @@ INSERT INTO emp.technologies_v2 (
  */
 INSERT INTO emp.empires( name_id , cash )
 	VALUES( _get_emp_name( 'emp2' ) , 100.0 );
-INSERT INTO emp.technologies_v2 (
+INSERT INTO emp.technologies (
 		empire_id , technology_name_id ,
 		emptech_state , emptech_points , emptech_priority )
 	SELECT _get_emp_name( 'emp2' ) , technology_name_id , 'KNOWN' , NULL , NULL
@@ -72,7 +72,7 @@ INSERT INTO emp.planets( empire_id , planet_id )
  */
 INSERT INTO emp.empires( name_id , cash )
 	VALUES( _get_emp_name( 'emp3' ) , 100.0 );
-INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+INSERT INTO emp.technologies ( empire_id , technology_name_id )
 	SELECT _get_emp_name( 'emp3' ) , technology_name_id
 		FROM defs.technologies;
 
@@ -82,7 +82,7 @@ INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
  */
 INSERT INTO emp.empires( name_id , cash )
 	VALUES( _get_emp_name( 'emp4' ) , 100.0 );
-INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+INSERT INTO emp.technologies ( empire_id , technology_name_id )
 	SELECT _get_emp_name( 'emp4' ) , technology_name_id
 		FROM defs.technologies;
 INSERT INTO emp.planets( empire_id , planet_id )
@@ -97,7 +97,7 @@ INSERT INTO emp.planets( empire_id , planet_id )
  */
 INSERT INTO emp.empires( name_id , cash )
 	VALUES( _get_emp_name( 'emp5' ) , 100.0 );
-INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+INSERT INTO emp.technologies ( empire_id , technology_name_id )
 	SELECT _get_emp_name( 'emp5' ) , technology_name_id
 		FROM defs.technologies;
 INSERT INTO emp.planets( empire_id , planet_id )
@@ -117,7 +117,7 @@ INSERT INTO users.vacations ( account_id , since , status )
  */
 INSERT INTO emp.empires( name_id , cash )
 	VALUES( _get_emp_name( 'emp6' ) , 100.0 );
-INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
+INSERT INTO emp.technologies ( empire_id , technology_name_id )
 	SELECT _get_emp_name( 'emp6' ) , technology_name_id
 		FROM defs.technologies;
 INSERT INTO emp.planets( empire_id , planet_id )
@@ -134,4 +134,4 @@ UPDATE sys.updates su
 	SET update_state = 'PROCESSED' , update_last = 0
 	FROM emp.empires_updates eu
 	WHERE eu.update_id = su.update_id
-		AND eu.name_id =  _get_emp_name( 'emp6' );
\ No newline at end of file
+		AND eu.name_id =  _get_emp_name( 'emp6' );

From ff78c6a2d6c69a0f5a24e7a531d14f69754308c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Thu, 28 Jun 2012 12:40:47 +0200
Subject: [PATCH 78/94] Added event definition tables

* New events.event_definitions table
* New events.field_definitions table
* New types for event field type specification
---
 .../parts/030-data/170-events.sql             | 162 +++++++++++++++++-
 1 file changed, 160 insertions(+), 2 deletions(-)

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
index 87ce1fc..121b26a 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
@@ -1,12 +1,170 @@
 -- LegacyWorlds Beta 6
 -- PostgreSQL database scripts
 --
--- Storage of events (internal messages)
+-- Storage of events
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2012, DeepClone Development
 -- --------------------------------------------------------
 
 
+/*
+ * Event type definitions
+ * -----------------------
+ * 
+ * This table stores the "root" of event type definitions. Each definition is
+ * uniquely identifier by a short string.
+ */
+DROP TABLE IF EXISTS events.event_definitions CASCADE;
+CREATE TABLE events.event_definitions(
+	/* The event definition identifier */
+	evdef_id			VARCHAR( 48 ) NOT NULL PRIMARY KEY ,
+
+	/* The default priority of the events of this type */
+	evdef_priority		INT NOT NULL
+							CHECK( evdef_priority BETWEEN 0 AND 4 ) ,
+
+	/* Whether the priority for this type of event may be adjusted */
+	evdef_adjustable	BOOLEAN NOT NULL
+);
+
+GRANT SELECT ON events.event_definitions TO :dbuser;
+
+
+/*
+ * General event field types
+ * --------------------------
+ */
+DROP TYPE IF EXISTS events.field_type CASCADE;
+CREATE TYPE events.field_type AS ENUM(
+		/* A numeric event field */
+		'NUMERIC' ,
+		/* A raw text event field */
+		'TEXT' ,
+		/* A boolean event field */
+		'BOOL' ,
+		/* An internationalised text field */
+		'I18N' ,
+		/* A field which links to another game entity */
+		'ENTITY'
+	);
+
+
+/*
+ * Entity field sub-types
+ * -----------------------
+ * 
+ * FIXME: no message sub-type for now
+ */
+DROP TYPE IF EXISTS events.entity_field_type CASCADE;
+CREATE TYPE events.entity_field_type AS ENUM(
+		/* An empire */
+		'EMP' ,
+		/* A planet */
+		'PLN' ,
+		/* A fleet */
+		'FLT' ,
+		/* A battle */
+		'BAT' ,
+		/* An administrator */
+		'ADM' ,
+		/* A bug report */
+		'BUG'
+	);
+	
+
+/*
+ * Event field definition
+ * -----------------------
+ * 
+ * An event field definition can be associated with an event definition to
+ * specify which fields an event will possess.
+ */
+DROP TABLE IF EXISTS events.field_definitions CASCADE;
+CREATE TABLE events.field_definitions (
+	/* Identifier of the event type definition */
+	evdef_id		VARCHAR( 48 ) NOT NULL ,
+	
+	/* Identifier of the field itself */
+	efdef_id		VARCHAR( 48 ) NOT NULL ,
+	
+	/* Whether the field is optional or mandatory */
+	efdef_optional	BOOLEAN NOT NULL ,
+	
+	/* General type of the event field */
+	efdef_type		events.field_type NOT NULL ,
+
+	/* Entity type if the field is an entity */
+	efdef_entity	events.entity_field_type ,
+	
+	/* Whether the field should contain an integer or a real number, if the
+	 * field is numeric.
+	 */
+	efdef_integer	BOOLEAN ,
+	
+	/* Minimal value or length of the field if it is either a numeric field or
+	 * a text field.
+	 */
+	efdef_min		NUMERIC ,
+	
+	/* Maximal value or length of the field if it is either a numeric field or
+	 * a text field.
+	 */
+	efdef_max		NUMERIC ,
+
+	/* Use both the event type identifier and the field identifier as
+	 * the primary key
+	 */
+	PRIMARY KEY( evdef_id , efdef_id ) ,
+	
+	/* Entity type is NULL unless the field is an entity field */
+	CONSTRAINT ck_efdef_entity CHECK( CASE
+		WHEN efdef_type = 'ENTITY' THEN
+			efdef_entity IS NOT NULL
+		ELSE
+			efdef_entity IS NULL
+	END ) ,
+
+	/* Make sure the integer flag is set only when the field is numeric */
+	CONSTRAINT ck_efdef_numtype CHECK( CASE
+		WHEN efdef_type = 'NUMERIC' THEN
+			efdef_integer IS NOT NULL
+		ELSE
+			efdef_integer IS NULL
+	END ) ,
+
+	/* Minimal / maximal values only allowed for numerics and raw strings */
+	CONSTRAINT ck_efdef_hasminmax CHECK( CASE
+		WHEN efdef_type IN ( 'NUMERIC' , 'TEXT' ) THEN
+			TRUE
+		ELSE
+			efdef_min IS NULL AND efdef_max IS NULL
+	END ) ,
+
+	/* If both a minimal and maximal values are present, minimal must be lower
+	 * than maximal.
+	 */
+	CONSTRAINT ck_efdef_minmaxvalues CHECK( CASE
+		WHEN efdef_min IS NULL OR efdef_max IS NULL THEN
+			TRUE
+		ELSE
+			efdef_min < efdef_max
+	END )
+);
+
+ALTER TABLE events.field_definitions
+	ADD CONSTRAINT fk_efdef_evdef
+		FOREIGN KEY ( evdef_id ) REFERENCES events.event_definitions
+			ON DELETE CASCADE;
+
+GRANT SELECT ON events.field_definitions TO :dbuser;
+
+
+
+/*
+ * OLD B6M1 CODE BELOW!
+ */
+
+
 CREATE TYPE event_type
 	AS ENUM ( 'QUEUE' , 'EMPIRE' , 'FLEETS' , 'PLANET', 'ALLIANCE', 'ADMIN' , 'BUGS' );
 

From 0c67d1e79983dad2c7cc2a0269d01c81a663bc87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Fri, 29 Jun 2012 15:18:18 +0200
Subject: [PATCH 79/94] Event definition functions

Added functions that allow new event types to be defined. The functions
are:
 * events.evdef_start() which starts recording a definition,
 * events.evdef_addfld_*(), a set of functions to add fields to
the current definition,
 * events.evdef_finalise() which adds the definition records and creates
the event queue table.

It is not possible to modify or delete event definitions at this time.
They will be added if and when they become necessary.
---
 .../parts/030-data/170-events.sql             |  14 +
 .../parts/040-functions/170-events.sql        | 828 +++++++++++++++++-
 2 files changed, 841 insertions(+), 1 deletion(-)

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
index 121b26a..40350e8 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
@@ -63,6 +63,8 @@ CREATE TYPE events.entity_field_type AS ENUM(
 		'PLN' ,
 		/* A fleet */
 		'FLT' ,
+		/* An alliance */
+		'ALL' ,
 		/* A battle */
 		'BAT' ,
 		/* An administrator */
@@ -159,6 +161,18 @@ ALTER TABLE events.field_definitions
 GRANT SELECT ON events.field_definitions TO :dbuser;
 
 
+/*
+ * Event identifier sequence
+ * -------------------------
+ * 
+ * This sequence is used by the various type-specific event queues to
+ * generate the events' identifiers.
+ */
+DROP SEQUENCE IF EXISTS events.event_id_sequence CASCADE;
+CREATE SEQUENCE events.event_id_sequence;
+GRANT SELECT,UPDATE ON events.event_id_sequence TO :dbuser;
+
+
 
 /*
  * OLD B6M1 CODE BELOW!
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
index 9e63f90..472eb73 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
@@ -3,10 +3,836 @@
 --
 -- Functions and views to create and manipulate events
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2012, DeepClone Development
 -- --------------------------------------------------------
 
 
+/*
+ * Return values for event definition functions
+ * ---------------------------------------------
+ */
+DROP TYPE IF EXISTS events.evdef_create_result CASCADE;
+CREATE TYPE events.evdef_create_result AS ENUM(
+	/* The event type definition function succeeded. If it was the initial
+	 * creation function or a field creation function, this means the creation
+	 * can continue. If it was the finalisation function, this means the event
+	 * definition has been added. 
+	 */
+	'OK' ,
+	
+	/* A bad identifier was specified. This value is returned by both the
+	 * initial creation function and event field definition functions.
+	 */
+	'BAD_ID' ,
+
+	/* Duplicate event or field identifier found. This value is returned by
+	 * the finalisation function, with its "_fld" set to NULL if the duplicate
+	 * identifier is the event's.
+	 */
+	'DUPLICATE' ,
+
+	/* Bad event or field specification found. This value is returned by the
+	 * finalisation function when an event or field cannot be inserted due to
+	 * check failures.
+	 */
+	'BAD_SPEC'
+);
+
+
+
+/*
+ * Start defining a new event type
+ * -------------------------------
+ * 
+ * This function creates a temporary table used to defgine a new type of
+ * event. No checks beyond the length and contents of the event type's
+ * identifier are made at this point.
+ * 
+ * Note: the temporary table will be created in all cases, even when the
+ *		identifier is invalid.
+ * 
+ * Parameters:
+ *		_id			The identifier of the event type
+ *		_prio		The event type's default priority
+ *		_adj		Whether the player may adjust the priority for this type
+ *						of events.
+ *
+ * Returns:
+ * 		???			An error code: OK or BAD_ID
+ *						(see events.evdef_create_result)
+ */
+DROP FUNCTION IF EXISTS events.evdef_start( TEXT , INT , BOOLEAN );
+CREATE FUNCTION events.evdef_start( _id TEXT , _prio INT , _adj BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $evdef_start$
+BEGIN
+	CREATE TEMPORARY TABLE evdef_temp(
+		evdef_id		TEXT ,
+		evfld_id		TEXT ,
+		evfld_typespec	TEXT
+	) ON COMMIT DROP;
+	
+	IF LENGTH( _id ) < 2 OR LENGTH( _id ) > 48
+			OR NOT _id ~ '^[a-z\-]+$'
+	THEN
+		RETURN 'BAD_ID'::events.evdef_create_result;
+	END IF;
+	
+	INSERT INTO evdef_temp( evdef_id , evfld_typespec )
+		VALUES( _id , _prio::TEXT || ' , ' || _adj::TEXT );
+	RETURN 'OK'::events.evdef_create_result;
+END;
+$evdef_start$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN )
+	TO :dbuser;
+
+
+
+/*
+ * Create a new field
+ * ------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function adds data about a new field to the temporary event
+ * definition table.
+ * 
+ * Parameters:
+ *		_id			The field's identifier
+ *		_opt		Whether the field is optional
+ *		_type		The field's type
+ *		_etype		The field's entity type (may be NULL)
+ *		_is_int		Whether the field is an integer (may be NULL)
+ *		_min_val	Minimal value/length (may be NULL)
+ *		_max_val	Maximal value/length (may be NULL)
+ *
+ * Returns:
+ * 		???			An error code: OK or BAD_ID
+ *						(see events.evdef_create_result)
+ */
+DROP FUNCTION IF EXISTS events.evdef_addfld_internal(
+	TEXT , BOOLEAN , events.field_type , events.entity_field_type ,
+	BOOLEAN , NUMERIC , NUMERIC ) CASCADE;
+CREATE FUNCTION events.evdef_addfld_internal(
+			_id			TEXT ,
+			_opt		BOOLEAN ,
+			_type		events.field_type ,
+			_etype		events.entity_field_type ,
+			_is_int		BOOLEAN ,
+			_min_val	NUMERIC ,
+			_max_val	NUMERIC
+		) RETURNS events.evdef_create_result
+		LANGUAGE PLPGSQL
+		CALLED ON NULL INPUT
+		VOLATILE SECURITY INVOKER
+	AS $evdef_addfld_internal$
+
+DECLARE
+	_iq_string	TEXT;
+
+BEGIN
+	IF LENGTH( _id ) < 2 OR LENGTH( _id ) > 48
+			OR NOT _id ~ '^[a-z\-]+$'
+	THEN
+		RETURN 'BAD_ID'::events.evdef_create_result;
+	END IF;
+
+	_iq_string := _opt::TEXT || ' , ''' || _type::TEXT || ''' , ';
+	IF _etype IS NULL
+	THEN
+		_iq_string := _iq_string || 'NULL';
+	ELSE
+		_iq_string := _iq_string || '''' || _etype::TEXT || '''';
+	END IF;
+	_iq_string := _iq_string || ' , ';
+	IF _is_int IS NULL
+	THEN
+		_iq_string := _iq_string || 'NULL';
+	ELSE
+		_iq_string := _iq_string || _is_int::TEXT;
+	END IF;
+	_iq_string := _iq_string || ' , ';
+	IF _min_val IS NULL
+	THEN
+		_iq_string := _iq_string || 'NULL';
+	ELSE
+		_iq_string := _iq_string || _min_val::TEXT;
+	END IF;
+	_iq_string := _iq_string || ' , ';
+	IF _max_val IS NULL
+	THEN
+		_iq_string := _iq_string || 'NULL';
+	ELSE
+		_iq_string := _iq_string || _max_val::TEXT;
+	END IF;
+
+	INSERT INTO evdef_temp( evdef_id , evfld_id , evfld_typespec )
+		SELECT evdef_id , _id , _iq_string
+			FROM evdef_temp
+			WHERE evfld_id IS NULL;
+	RETURN 'OK'::events.evdef_create_result;
+END;
+$evdef_addfld_internal$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_internal(
+		TEXT , BOOLEAN , events.field_type , events.entity_field_type ,
+		BOOLEAN , NUMERIC , NUMERIC ) 
+	FROM PUBLIC;
+
+
+/*
+ * Actual field creation functions
+ * --------------------------------
+ * 
+ * Bunch of functions that create the field records in the temporary table
+ * through events.evdef_addfld_internal().
+ * 
+ * Note:
+ *	These functions are not dropped manually here, as it is assumed that
+ *		dropping events.evdef_addfld_internal() will have cascaded.
+ */
+
+-- INTEGER FIELD, NO BOUNDS
+CREATE FUNCTION events.evdef_addfld_int( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_int( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_int( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- INTEGER FIELD W/ LOWER BOUND
+CREATE FUNCTION events.evdef_addfld_int_min( _id TEXT , _opt BOOLEAN , _min INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,$3::NUMERIC,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_int_min( TEXT , BOOLEAN , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_int_min( TEXT , BOOLEAN , INT )
+	TO :dbuser;
+
+-- INTEGER FIELD W/ HIGHER BOUND
+CREATE FUNCTION events.evdef_addfld_int_max( _id TEXT , _opt BOOLEAN , _max INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,NULL,$3::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_int_max( TEXT , BOOLEAN , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_int_max( TEXT , BOOLEAN , INT )
+	TO :dbuser;
+
+-- INTEGER FIELD W/ LIMITED RANGE
+CREATE FUNCTION events.evdef_addfld_int_range( _id TEXT , _opt BOOLEAN , _min INT , _max INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,$3::NUMERIC,$4::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_int_range( TEXT , BOOLEAN , INT , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_int_range( TEXT , BOOLEAN , INT , INT )
+	TO :dbuser;
+
+-- REAL FIELD, NO BOUNDS
+CREATE FUNCTION events.evdef_addfld_real( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_real( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_real( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- REAL FIELD W/ LOWER BOUND
+CREATE FUNCTION events.evdef_addfld_real_min( _id TEXT , _opt BOOLEAN , _min DOUBLE PRECISION )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,$3::NUMERIC,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_real_min( TEXT , BOOLEAN , DOUBLE PRECISION ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_real_min( TEXT , BOOLEAN , DOUBLE PRECISION )
+	TO :dbuser;
+
+-- REAL FIELD W/ HIGHER BOUND
+CREATE FUNCTION events.evdef_addfld_real_max( _id TEXT , _opt BOOLEAN , _max DOUBLE PRECISION )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,NULL,$3::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_real_max( TEXT , BOOLEAN , DOUBLE PRECISION ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_real_max( TEXT , BOOLEAN , DOUBLE PRECISION )
+	TO :dbuser;
+
+-- REAL FIELD W/ LIMITED RANGE
+CREATE FUNCTION events.evdef_addfld_real_range( _id TEXT , _opt BOOLEAN , _min DOUBLE PRECISION , _max DOUBLE PRECISION )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,$3::NUMERIC,$4::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_real_range( TEXT , BOOLEAN , DOUBLE PRECISION , DOUBLE PRECISION ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_real_range( TEXT , BOOLEAN , DOUBLE PRECISION , DOUBLE PRECISION )
+	TO :dbuser;
+
+-- TEXT FIELD, NO BOUNDS
+CREATE FUNCTION events.evdef_addfld_text( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'TEXT'::events.field_type,NULL,NULL,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_text( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_text( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- TEXT FIELD W/ LOWER BOUND
+CREATE FUNCTION events.evdef_addfld_text_min( _id TEXT , _opt BOOLEAN , _min INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'TEXT'::events.field_type,NULL,NULL,$3::NUMERIC,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_text_min( TEXT , BOOLEAN , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_text_min( TEXT , BOOLEAN , INT )
+	TO :dbuser;
+
+-- TEXT FIELD W/ HIGHER BOUND
+CREATE FUNCTION events.evdef_addfld_text_max( _id TEXT , _opt BOOLEAN , _max INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'TEXT'::events.field_type,NULL,NULL,NULL,$3::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_text_max( TEXT , BOOLEAN , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_text_max( TEXT , BOOLEAN , INT )
+	TO :dbuser;
+
+-- TEXT FIELD W/ LIMITED RANGE
+CREATE FUNCTION events.evdef_addfld_text_range( _id TEXT , _opt BOOLEAN , _min INT , _max INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'TEXT'::events.field_type,NULL,NULL,$3::NUMERIC,$4::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_text_range( TEXT , BOOLEAN , INT , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_text_range( TEXT , BOOLEAN , INT , INT )
+	TO :dbuser;
+
+-- BOOLEAN FIELD
+CREATE FUNCTION events.evdef_addfld_bool( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'BOOL'::events.field_type,NULL,NULL,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_bool( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_bool( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- I18N TEXT FIELD
+CREATE FUNCTION events.evdef_addfld_i18n( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'I18N'::events.field_type,NULL,NULL,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_i18n( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_i18n( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- ENTITY LINK FIELD
+CREATE FUNCTION events.evdef_addfld_entity( _id TEXT , _opt BOOLEAN , _etype events.entity_field_type )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'ENTITY'::events.field_type,$3,NULL,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_entity( TEXT , BOOLEAN , events.entity_field_type ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_entity( TEXT , BOOLEAN , events.entity_field_type ) 
+	TO :dbuser;
+
+
+/*
+ * Create an event definition record
+ * ---------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function inserts the record for an event definition into the
+ * appropriate table.
+ * 
+ * Returns:
+ *		???		An error code
+ */
+DROP FUNCTION IF EXISTS events.evdef_create_record( );
+CREATE FUNCTION events.evdef_create_record( )
+		RETURNS events.evdef_create_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY INVOKER
+	AS $evdef_create_record$
+
+DECLARE
+	_rec	RECORD;
+	_qstr	TEXT;
+
+BEGIN
+	SELECT INTO _rec *
+		FROM evdef_temp
+		WHERE evfld_id IS NULL;
+
+	_qstr := 'INSERT INTO events.event_definitions (' ||
+				'evdef_id , evdef_priority , evdef_adjustable' ||
+			') VALUES ( $1 , ' || _rec.evfld_typespec || ');';
+
+	BEGIN
+		EXECUTE _qstr USING _rec.evdef_id;
+	EXCEPTION
+		WHEN unique_violation THEN
+			RETURN 'DUPLICATE';
+		WHEN check_violation THEN
+			RETURN 'BAD_SPEC';
+	END;
+
+	RETURN 'OK';
+END;
+$evdef_create_record$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_create_record( )
+	FROM PUBLIC;
+
+
+/*
+ * Create an event field definition record
+ * ---------------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function inserts a new event field definition record into the
+ * appropriate table.
+ *
+ * Parameters:
+ * 		_evdef_id	The event definition's identifier
+ *		_efdef_id	The field's identifier
+ *		_typespec	The type specification from the temporary table
+ *
+ * Returns:
+ *		???			An error code
+ */
+DROP FUNCTION IF EXISTS events.evdef_create_field_record(
+		TEXT , TEXT , TEXT );
+CREATE FUNCTION events.evdef_create_field_record(
+			_evdef_id	TEXT ,
+			_efdef_id	TEXT ,
+			_typespec	TEXT )
+		RETURNS events.evdef_create_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY INVOKER
+	AS $evdef_create_field_record$
+
+DECLARE
+	_qstr	TEXT;
+
+BEGIN
+	_qstr := 'INSERT INTO events.field_definitions (' ||
+			'evdef_id , efdef_id , efdef_optional , efdef_type , ' ||
+			'efdef_entity , efdef_integer , efdef_min , efdef_max ' ||
+		') VALUES ( $1 , $2 , ' || _typespec || ');';
+	BEGIN
+		EXECUTE _qstr USING _evdef_id , _efdef_id;
+	EXCEPTION
+		WHEN unique_violation THEN
+			RETURN 'DUPLICATE';
+		WHEN check_violation THEN
+			RETURN 'BAD_SPEC';
+	END;
+	RETURN 'OK';
+
+END;
+$evdef_create_field_record$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_create_field_record( TEXT , TEXT , TEXT )
+	FROM PUBLIC;
+
+
+/*
+ * Generate the DDL for a numeric field
+ * ------------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function generates the DDL for a numeric event field in an event queue
+ * table.
+ * 
+ * Parameters:
+ *		_fname		The field's table name
+ *		_opt		Whether the field is optional
+ *		_is_int		Whether the field is an integer
+ *		_min		Minimal value of the field (may be NULL)
+ *		_max		Maximal value of the field (may be NULL)
+ *
+ * Returns:
+ * 		???			The field's DDL
+ */
+DROP FUNCTION IF EXISTS events.efdef_ddl_numeric(
+		TEXT , BOOLEAN , BOOLEAN , NUMERIC , NUMERIC );
+CREATE FUNCTION events.efdef_ddl_numeric(
+		_fname	TEXT ,
+		_opt	BOOLEAN ,
+		_is_int	BOOLEAN ,
+		_min	NUMERIC ,
+		_max	NUMERIC )
+	RETURNS TEXT
+	LANGUAGE SQL
+	IMMUTABLE SECURITY INVOKER
+	CALLED ON NULL INPUT
+AS $efdef_ddl_numeric$
+	SELECT ',' || $1 || ' '
+		|| ( CASE WHEN $3 THEN 'BIGINT' ELSE 'DOUBLE PRECISION' END )
+		|| ( CASE WHEN $2 THEN '' ELSE ' NOT NULL' END )
+		|| ( CASE
+			WHEN $4 IS NULL AND $5 IS NULL
+				THEN ''
+			ELSE
+				' CHECK(' || $1 || ( CASE
+					WHEN $5 IS NULL
+						THEN '>=' || $4::TEXT
+					WHEN $4 IS NULL
+						THEN '<=' || $5::TEXT
+					ELSE
+						' BETWEEN ' || $4::TEXT || ' AND ' || $5::TEXT
+				END ) || ')'
+		END );
+$efdef_ddl_numeric$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.efdef_ddl_numeric(
+		TEXT , BOOLEAN , BOOLEAN , NUMERIC , NUMERIC )
+	FROM PUBLIC;
+
+
+
+/*
+ * Generate the DDL for a text field
+ * ---------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function generates the DDL for a text event field in an event queue
+ * table.
+ * 
+ * Parameters:
+ *		_fname		The field's table name
+ *		_opt		Whether the field is optional
+ *		_min		Minimal length of the field (may be NULL)
+ *		_max		Maximal length of the field (may be NULL)
+ *
+ * Returns:
+ * 		???			The field's DDL
+ */
+DROP FUNCTION IF EXISTS events.efdef_ddl_text(
+		TEXT , BOOLEAN , NUMERIC , NUMERIC );
+CREATE FUNCTION events.efdef_ddl_text(
+		_fname	TEXT ,
+		_opt	BOOLEAN ,
+		_min	NUMERIC ,
+		_max	NUMERIC )
+	RETURNS TEXT
+	LANGUAGE SQL
+	IMMUTABLE SECURITY INVOKER
+	CALLED ON NULL INPUT
+AS $efdef_ddl_text$
+	SELECT ',' || $1 || ' '
+		|| ( CASE WHEN $4 IS NULL THEN 'TEXT' ELSE 'VARCHAR(' || $4::TEXT || ')' END )
+		|| ( CASE WHEN $2 THEN '' ELSE ' NOT NULL' END )
+		|| ( CASE
+			WHEN $3 IS NULL
+				THEN ''
+			ELSE
+				' CHECK(LENGTH(' || $1 || ')>=' || $3::TEXT || ')'
+		END );
+$efdef_ddl_text$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.efdef_ddl_text(
+		TEXT , BOOLEAN , NUMERIC , NUMERIC )
+	FROM PUBLIC;
+
+
+
+/*
+ * Generate the DDL for an entity field
+ * ------------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function generates the DDL for an entity event field in an event queue
+ * table.
+ * 
+ * Parameters:
+ *		_fname		The field's table name
+ *		_opt		Whether the field is optional
+ *		_etype		Type of entity to reference
+ *
+ * Returns:
+ * 		???			The field's DDL
+ */
+DROP FUNCTION IF EXISTS events.efdef_ddl_entity(
+		TEXT , BOOLEAN , events.entity_field_type );
+CREATE FUNCTION events.efdef_ddl_entity(
+		_fname	TEXT ,
+		_opt	BOOLEAN ,
+		_etype	events.entity_field_type )
+	RETURNS TEXT
+	LANGUAGE SQL
+	STRICT IMMUTABLE SECURITY INVOKER
+AS $efdef_ddl_entity$
+	SELECT ',' || $1 || ' '
+		|| ( CASE 
+			WHEN $3 IN ( 'EMP' , 'PLN', 'ALL' , 'ADM' ) THEN 'INT'
+			ELSE 'BIGINT'
+		END ) || ( CASE WHEN $2 THEN ' NOT NULL' ELSE '' END )
+		|| ' REFERENCES ' || ( CASE $3
+			WHEN 'EMP' THEN 'emp.empires(name_id)'
+			WHEN 'PLN' THEN 'verse.planets(name_id)'
+			WHEN 'FLT' THEN 'fleets.fleets(id)'
+			WHEN 'ALL' THEN 'emp.alliances(id)'
+			WHEN 'BAT' THEN 'battles.battles(id)'
+			WHEN 'ADM' THEN 'admin.administrators(id)'
+			WHEN 'BUG' THEN 'bugs.initial_report_events(event_id)'
+		END ) || ' ON DELETE CASCADE';
+$efdef_ddl_entity$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.efdef_ddl_entity(
+		TEXT , BOOLEAN , events.entity_field_type )
+	FROM PUBLIC;
+
+
+	
+/*
+ * Create the event queuing table for a new definition
+ * ---------------------------------------------------
+ *
+ * /!\ INTERNAL FUNCTION /!\
+ *
+ * This function generates the event queuing table from the list of fields
+ * that compose an event definition. The queuing table's name will always be
+ * "events.evq_<type identifier>"; it will contain an event identifier
+ * generated from the event ID sequence (events.event_id_sequence), an empire
+ * identifier, a timestamp, the tick identifier, and rows corresponding to the
+ * event definition's fields.
+ * 
+ * Parameters:
+ *		_evdef_id		The event definition's identifier   
+ */
+DROP FUNCTION IF EXISTS events.evdef_create_queue_table( TEXT );
+CREATE FUNCTION _temp_init_func(_user TEXT)  RETURNS VOID LANGUAGE PLPGSQL AS $init_func$
+BEGIN EXECUTE $init_code$
+CREATE FUNCTION events.evdef_create_queue_table( _evdef_id TEXT )
+		RETURNS VOID
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY INVOKER
+	AS $evdef_create_queue_table$
+
+DECLARE
+	_rec	RECORD;
+	_qstr	TEXT;
+	_fname	TEXT;
+
+BEGIN
+	
+	_qstr := 'CREATE TABLE events.eq_' || replace( _evdef_id , '-' , '_' )
+		|| $tbl_def$ (
+			event_id BIGINT NOT NULL PRIMARY KEY
+					DEFAULT nextval('events.event_id_sequence'::regclass) ,
+			event_rtime TIMESTAMP WITHOUT TIME ZONE NOT NULL
+					DEFAULT now( ) ,
+			event_gtime BIGINT NOT NULL ,
+			empire_id INT NOT NULL REFERENCES emp.empires ( name_id )
+					ON DELETE CASCADE
+	$tbl_def$;
+	
+	FOR _rec IN SELECT *
+			FROM events.field_definitions
+			WHERE evdef_id = _evdef_id
+	LOOP
+		_fname := 'ef_' || replace( _rec.efdef_id , '-' , '_' );
+		_qstr := _qstr || ( CASE _rec.efdef_type
+				WHEN 'NUMERIC' THEN
+					events.efdef_ddl_numeric( _fname , _rec.efdef_optional ,
+						_rec.efdef_integer , _rec.efdef_min , _rec.efdef_max )
+				WHEN 'TEXT' THEN
+					events.efdef_ddl_text( _fname , _rec.efdef_optional ,
+						_rec.efdef_min , _rec.efdef_max )
+				WHEN 'BOOL' THEN
+					',' || _fname || ' BOOLEAN' || ( CASE
+						WHEN NOT _rec.efdef_optional THEN ' NOT NULL'
+						ELSE ''
+					END )
+				WHEN 'I18N' THEN
+					',' || _fname || ' VARCHAR(64)' || ( CASE
+						WHEN NOT _rec.efdef_optional THEN ' NOT NULL'
+						ELSE ''
+					END ) || ' REFERENCES defs.strings(name)'
+				WHEN 'ENTITY' THEN
+					events.efdef_ddl_entity( _fname , _rec.efdef_optional ,
+						_rec.efdef_entity )
+			END );
+	END LOOP;
+
+	_qstr := _qstr || ');';
+	EXECUTE _qstr;
+	
+	_qstr := 'GRANT INSERT ON TABLE events.eq_' || replace( _evdef_id , '-' , '_' )
+		|| ' TO $init_code$ || $1 || $init_code$;';
+	EXECUTE _qstr;
+END;
+$evdef_create_queue_table$; $init_code$ USING $1; END; $init_func$;
+SELECT _temp_init_func(:dbuser_string);
+DROP FUNCTION _temp_init_func(TEXT);
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_create_queue_table( TEXT )
+	FROM PUBLIC;
+
+
+
+/*
+ * Validate and finalise an event definition
+ * -----------------------------------------
+ * 
+ * This function inserts the contents of the temporary event definition table
+ * into the main table. If everything goes well, the event queuing table is
+ * created and the temporary table is destroyed.
+ * 
+ * Returns:
+ *		_error		An error code (see events.evdef_create_result)
+ *		_fld		The name of the field which caused the error, or NULL
+ *						if the error was table-related or if there was no
+ *						error.
+ */
+DROP FUNCTION IF EXISTS events.evdef_finalise( );
+CREATE FUNCTION events.evdef_finalise(
+			OUT _error events.evdef_create_result ,
+			OUT _fld TEXT )
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $evdef_finalise$
+
+DECLARE
+	_eid	TEXT;
+	_rec	RECORD;
+
+BEGIN
+	-- First create the event definition record
+	_fld := NULL;
+	_error := events.evdef_create_record( );
+	IF _error = 'OK'
+	THEN
+		-- Then create records for all fields
+		FOR _rec IN SELECT * FROM evdef_temp WHERE evfld_id IS NOT NULL
+		LOOP
+			_error := events.evdef_create_field_record(
+					_rec.evdef_id , _rec.evfld_id , _rec.evfld_typespec );
+
+			IF _error <> 'OK'
+			THEN
+				-- Destroy the definition record on failure
+				_fld := _rec.evfld_id;
+				DELETE FROM events.event_definitions
+					WHERE evdef_id = _rec.evdef_id;
+				EXIT;
+			END IF;
+
+		END LOOP;
+	END IF;
+
+	-- If everything went well so far, create the queueing table
+	IF _error = 'OK'
+	THEN
+		SELECT INTO _eid evdef_id FROM evdef_temp LIMIT 1;
+		PERFORM events.evdef_create_queue_table( _eid );
+	END IF;
+
+	DROP TABLE evdef_temp;
+END;
+$evdef_finalise$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_finalise( )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_finalise( )
+	TO :dbuser;
+
+
+/*
+ * OLD B6M1 CODE BELOW!
+ */
+
+
+
 -- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
 -- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
 -- EVENT CREATION FUNCTIONS                                                                                                                                        -- 

From 8f2fd29c71924118646aefff1b9e2c739f45cb66 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Fri, 29 Jun 2012 17:19:23 +0200
Subject: [PATCH 80/94] Player-defined priority storage

Added the events.custom_priorities that ought to store player-defined
priorities for event types which support it.
---
 .../parts/030-data/170-events.sql             | 45 +++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
index 40350e8..dd76220 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
@@ -27,6 +27,12 @@ CREATE TABLE events.event_definitions(
 	evdef_adjustable	BOOLEAN NOT NULL
 );
 
+/* Unique index allowing the custom priorities table to reference only
+ * adjustable event types.
+ */
+CREATE UNIQUE INDEX idx_evdef_adjustables
+	ON events.event_definitions( evdef_id , evdef_adjustable );
+
 GRANT SELECT ON events.event_definitions TO :dbuser;
 
 
@@ -161,6 +167,45 @@ ALTER TABLE events.field_definitions
 GRANT SELECT ON events.field_definitions TO :dbuser;
 
 
+/*
+ * Custom event priorities
+ * -----------------------
+ * 
+ * This table stores player-defined event priority overrides. Only events
+ * with custom priorities may be present in this table.
+ */
+DROP TABLE IF EXISTS events.custom_priorities CASCADE;
+CREATE TABLE events.custom_priorities(
+	/* Identifier of the event type */
+	evdef_id			VARCHAR(48) NOT NULL ,
+
+	/* Used in reference to the event types - forces this table to
+	 * reference only events that *can* be customised.
+	 */
+	evdef_adjustable	BOOLEAN NOT NULL DEFAULT TRUE
+							CHECK( evdef_adjustable ) ,
+
+	/* Account identifier */
+	address_id			INT NOT NULL ,
+	
+	/* Custom priority */
+	evcp_priority		INT NOT NULL
+							CHECK( evcp_priority BETWEEN 0 AND 4 ) ,
+
+	/* Use the event identifier and "adjustable" set value as the primary
+	 * key. The advantage is that we get an index on both fields.
+	 */
+	PRIMARY KEY( evdef_id , evdef_adjustable )
+);
+
+ALTER TABLE events.custom_priorities
+	ADD CONSTRAINT fk_evcp_evdef
+		FOREIGN KEY ( evdef_id , evdef_adjustable )
+			REFERENCES events.event_definitions ( evdef_id , evdef_adjustable );
+
+GRANT SELECT ON events.custom_priorities TO :dbuser;
+
+
 /*
  * Event identifier sequence
  * -------------------------

From a6562052d31d9db8e73f0f99165d276a59e12601 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Fri, 29 Jun 2012 17:31:14 +0200
Subject: [PATCH 81/94] Event definitions - missing I18N properties

Added the display name and template I18N properties to event
definitions. Modified events.evdef_start() accordingly.
---
 .../parts/030-data/170-events.sql             | 23 +++++++++++-
 .../parts/040-functions/170-events.sql        | 37 ++++++++++++++++---
 2 files changed, 53 insertions(+), 7 deletions(-)

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
index dd76220..d571ee3 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
@@ -24,7 +24,17 @@ CREATE TABLE events.event_definitions(
 							CHECK( evdef_priority BETWEEN 0 AND 4 ) ,
 
 	/* Whether the priority for this type of event may be adjusted */
-	evdef_adjustable	BOOLEAN NOT NULL
+	evdef_adjustable	BOOLEAN NOT NULL ,
+
+	/* Internationalised string that contains the name of the event type;
+	 * used when displaying priority settings.
+	 */
+	evdef_name_id		INT NOT NULL ,
+	
+	/* Internationalised string that contains the template to use when
+	 * generating the output for a single event.
+	 */
+	evdef_template_id	INT NOT NULL
 );
 
 /* Unique index allowing the custom priorities table to reference only
@@ -32,6 +42,17 @@ CREATE TABLE events.event_definitions(
  */
 CREATE UNIQUE INDEX idx_evdef_adjustables
 	ON events.event_definitions( evdef_id , evdef_adjustable );
+/* Foreign key indexes */
+CREATE UNIQUE INDEX idx_evdef_name
+	ON events.event_definitions( evdef_name_id );
+CREATE UNIQUE INDEX idx_evdef_template
+	ON events.event_definitions( evdef_template_id );
+
+ALTER TABLE events.event_definitions
+	ADD CONSTRAINT fk_evdef_name
+		FOREIGN KEY ( evdef_name_id ) REFERENCES defs.strings ,
+	ADD CONSTRAINT fk_evdef_template
+		FOREIGN KEY ( evdef_template_id ) REFERENCES defs.strings;
 
 GRANT SELECT ON events.event_definitions TO :dbuser;
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
index 472eb73..cbcd8e0 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
@@ -24,6 +24,11 @@ CREATE TYPE events.evdef_create_result AS ENUM(
 	 * initial creation function and event field definition functions.
 	 */
 	'BAD_ID' ,
+	
+	/* An internationalised string (either an event type's name or template)
+	 * could not be found.
+	 */
+	'BAD_STRINGS' ,
 
 	/* Duplicate event or field identifier found. This value is returned by
 	 * the finalisation function, with its "_fld" set to NULL if the duplicate
@@ -56,17 +61,29 @@ CREATE TYPE events.evdef_create_result AS ENUM(
  *		_prio		The event type's default priority
  *		_adj		Whether the player may adjust the priority for this type
  *						of events.
+ *		_i18n		The "base" of the I18N strings identifiers; the function
+ *						will attempt to use "<_i18n>Name" as the name's
+ *						identifier and "<_i18n>Template" as the template's.
  *
  * Returns:
  * 		???			An error code: OK or BAD_ID
  *						(see events.evdef_create_result)
  */
-DROP FUNCTION IF EXISTS events.evdef_start( TEXT , INT , BOOLEAN );
-CREATE FUNCTION events.evdef_start( _id TEXT , _prio INT , _adj BOOLEAN )
+DROP FUNCTION IF EXISTS events.evdef_start( TEXT , INT , BOOLEAN , TEXT );
+CREATE FUNCTION events.evdef_start(
+			_id		TEXT ,
+			_prio	INT ,
+			_adj	BOOLEAN ,
+			_i18n	TEXT )
 		RETURNS events.evdef_create_result
 		LANGUAGE PLPGSQL
 		STRICT VOLATILE SECURITY DEFINER
 	AS $evdef_start$
+
+DECLARE
+	_name_id		INT;
+	_template_id	INT;
+
 BEGIN
 	CREATE TEMPORARY TABLE evdef_temp(
 		evdef_id		TEXT ,
@@ -77,20 +94,28 @@ BEGIN
 	IF LENGTH( _id ) < 2 OR LENGTH( _id ) > 48
 			OR NOT _id ~ '^[a-z\-]+$'
 	THEN
-		RETURN 'BAD_ID'::events.evdef_create_result;
+		RETURN 'BAD_ID';
 	END IF;
 	
+	SELECT INTO _name_id id FROM defs.strings
+		WHERE name = _i18n || 'Name';
+	SELECT INTO _template_id id FROM defs.strings
+		WHERE name = _i18n || 'Template';
+	IF _name_id IS NULL OR _template_id IS NULL THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+
 	INSERT INTO evdef_temp( evdef_id , evfld_typespec )
 		VALUES( _id , _prio::TEXT || ' , ' || _adj::TEXT );
-	RETURN 'OK'::events.evdef_create_result;
+	RETURN 'OK';
 END;
 $evdef_start$;
 
 REVOKE EXECUTE
-	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN )
+	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN , TEXT )
 	FROM PUBLIC;
 GRANT EXECUTE
-	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN )
+	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN , TEXT )
 	TO :dbuser;
 
 

From aaec3459578d41d5862c72f3f12f936a602d36fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 30 Jun 2012 12:43:29 +0200
Subject: [PATCH 82/94] Event definition stored procedures bugfix

Fixed a bug which caused events.evdef_finalise() to fail with an
exception when the event type identifier was wrong, and to register the
event type event if error had occurred on field definitions.
---
 .../db-structure/parts/040-functions/170-events.sql  | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
index cbcd8e0..8cdba58 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
@@ -40,7 +40,12 @@ CREATE TYPE events.evdef_create_result AS ENUM(
 	 * finalisation function when an event or field cannot be inserted due to
 	 * check failures.
 	 */
-	'BAD_SPEC'
+	'BAD_SPEC' ,
+	
+	/* This error code is returned when trying to finalise an event after one
+	 * of the definition calls failed. 
+	 */
+	'NO_DATA'
 );
 
 
@@ -166,6 +171,7 @@ BEGIN
 	IF LENGTH( _id ) < 2 OR LENGTH( _id ) > 48
 			OR NOT _id ~ '^[a-z\-]+$'
 	THEN
+		DELETE FROM evdef_temp;
 		RETURN 'BAD_ID'::events.evdef_create_result;
 	END IF;
 
@@ -463,6 +469,10 @@ BEGIN
 	SELECT INTO _rec *
 		FROM evdef_temp
 		WHERE evfld_id IS NULL;
+	IF NOT FOUND
+	THEN
+		RETURN 'NO_DATA';
+	END IF;
 
 	_qstr := 'INSERT INTO events.event_definitions (' ||
 				'evdef_id , evdef_priority , evdef_adjustable' ||

From 7e3d8a558a4094733658ff5fb16c20c2773ac58f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 30 Jun 2012 14:30:46 +0200
Subject: [PATCH 83/94] XML event definitions

Added a dummy event definitions file, and more importantly the
associated XML schema.
---
 .../data/event-definitions.xml                |  27 +++++
 .../data/event-definitions.xsd                | 110 ++++++++++++++++++
 legacyworlds-server-main/data/i18n-text.xml   |  41 +++++++
 3 files changed, 178 insertions(+)
 create mode 100644 legacyworlds-server-main/data/event-definitions.xml
 create mode 100644 legacyworlds-server-main/data/event-definitions.xsd

diff --git a/legacyworlds-server-main/data/event-definitions.xml b/legacyworlds-server-main/data/event-definitions.xml
new file mode 100644
index 0000000..6cfb70a
--- /dev/null
+++ b/legacyworlds-server-main/data/event-definitions.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-event-definitions xmlns="http://www.deepclone.com/lw/b6/m2/event-definitions"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/event-definitions event-definitions.xsd">
+
+	<!-- An item in one of the build queues has been completed -->
+	<evdef id="bq-progress" priority="0" i18n-strings="evtBQProgress">
+		<boolean id="is-military" />
+		<boolean id="is-construction" />
+		<entity id="planet" entity-type="PLN" required="0" /> 
+		<text id="planet-name" max-length="20" />
+		<i18n id="nature" />
+	</evdef>
+
+	<!-- A build queue is empty -->
+	<evdef id="bq-empty" priority="1" i18n-strings="evtBQEmpty">
+		<boolean id="is-military" />
+		<entity id="planet" entity-type="PLN" required="0" /> 
+		<text id="planet-name" max-length="20" />
+	</evdef>
+	
+	<!-- A research is ready to be implemented -->
+	<evdef id="research-complete" i18n-strings="evtResearch">
+		<i18n id="technology" />
+	</evdef>
+
+</lw-event-definitions>
diff --git a/legacyworlds-server-main/data/event-definitions.xsd b/legacyworlds-server-main/data/event-definitions.xsd
new file mode 100644
index 0000000..417a9f8
--- /dev/null
+++ b/legacyworlds-server-main/data/event-definitions.xsd
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns="http://www.deepclone.com/lw/b6/m2/event-definitions"
+	xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.deepclone.com/lw/b6/m2/event-definitions"
+	xmlns:tns="http://www.deepclone.com/lw/b6/m2/event-definitions"
+	elementFormDefault="qualified" attributeFormDefault="unqualified">
+
+	<!-- Root element, contains a sequence of event definitions -->
+	<xs:element name="lw-event-definitions">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="evdef" type="evdef" minOccurs="0"
+					maxOccurs="unbounded" />
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+
+	<!-- Event definitions. Needs an ID and an I18N base, may also provide information 
+		about the event's priority. May contain a list of field definitions. -->
+	<xs:complexType name="evdef">
+		<xs:sequence>
+			<xs:group ref="evdef-field" minOccurs="0" maxOccurs="unbounded" />
+		</xs:sequence>
+		<xs:attribute name="id" use="required" type="xs:token" />
+		<xs:attribute name="priority" use="optional" default="2"
+			type="priority-value" />
+		<xs:attribute name="adjustable" use="optional" type="xs:boolean"
+			default="true" />
+		<xs:attribute name="i18n-strings" use="required" type="xs:token" />
+	</xs:complexType>
+
+	<!-- Priority values are integers in [0;4] -->
+	<xs:simpleType name="priority-value">
+		<xs:restriction base="xs:integer">
+			<xs:minInclusive value="0" />
+			<xs:maxInclusive value="4" />
+		</xs:restriction>
+	</xs:simpleType>
+
+	<!-- Field definitions -->
+	<xs:group name="evdef-field">
+		<xs:choice>
+			<xs:element name="boolean" type="field-base" />
+			<xs:element name="i18n" type="field-base" />
+			<xs:element name="integer" type="field-integer" />
+			<xs:element name="real" type="field-real" />
+			<xs:element name="text" type="field-text" />
+			<xs:element name="entity" type="field-entity" />
+		</xs:choice>
+	</xs:group>
+
+	<!-- Common parts for all field definitions -->
+	<xs:complexType name="field-base">
+		<xs:attribute name="id" use="required" type="xs:token" />
+		<xs:attribute name="required" use="optional" type="xs:boolean"
+			default="true" />
+	</xs:complexType>
+
+	<!-- Integer field definition -->
+	<xs:complexType name="field-integer">
+		<xs:complexContent>
+			<xs:extension base="field-base">
+				<xs:attribute name="min" use="optional" type="xs:integer" />
+				<xs:attribute name="max" use="optional" type="xs:integer" />
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+
+	<!-- Real field definition -->
+	<xs:complexType name="field-real">
+		<xs:complexContent>
+			<xs:extension base="field-base">
+				<xs:attribute name="min" use="optional" type="xs:double" />
+				<xs:attribute name="max" use="optional" type="xs:double" />
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+
+	<!-- Text field definition -->
+	<xs:complexType name="field-text">
+		<xs:complexContent>
+			<xs:extension base="field-base">
+				<xs:attribute name="min-length" use="optional" type="xs:integer" />
+				<xs:attribute name="max-length" use="optional" type="xs:integer" />
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+
+	<!-- Entity field definition -->
+	<xs:complexType name="field-entity">
+		<xs:complexContent>
+			<xs:extension base="field-base">
+				<xs:attribute name="entity-type" use="required" type="entity-types" />
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+
+	<!-- Supported entity types -->
+	<xs:simpleType name="entity-types">
+		<xs:restriction base="xs:string">
+			<xs:enumeration value="EMP" />
+			<xs:enumeration value="PLN" />
+			<xs:enumeration value="FLT" />
+			<xs:enumeration value="ALL" />
+			<xs:enumeration value="BAT" />
+			<xs:enumeration value="ADM" />
+			<xs:enumeration value="BUG" />
+		</xs:restriction>
+	</xs:simpleType>
+
+</xs:schema>
\ No newline at end of file
diff --git a/legacyworlds-server-main/data/i18n-text.xml b/legacyworlds-server-main/data/i18n-text.xml
index 0e54e2e..0403da4 100644
--- a/legacyworlds-server-main/data/i18n-text.xml
+++ b/legacyworlds-server-main/data/i18n-text.xml
@@ -569,6 +569,26 @@ It was disbanded.</value>
 		<inline-string id="rdNothentium">
 			<value>(Get Doug or Rendesh to write this)</value>
 		</inline-string>
+		
+		<!-- Event names and templates -->
+		<inline-string id="evtBQProgressName">
+			<value>Build queue progress</value>
+		</inline-string>
+		<inline-string id="evtBQProgressTemplate">
+			<value>FIXME: this should be some FreeMarker code in a file</value>
+		</inline-string>
+		<inline-string id="evtBQEmptyName">
+			<value>Empty build queue</value>
+		</inline-string>
+		<inline-string id="evtBQEmptyTemplate">
+			<value>FIXME: this should be some FreeMarker code in a file</value>
+		</inline-string>
+		<inline-string id="evtResearchName">
+			<value>Technological breakthrough</value>
+		</inline-string>
+		<inline-string id="evtResearchTemplate">
+			<value>FIXME: this should be some FreeMarker code in a file</value>
+		</inline-string>
 
 	</language>
 
@@ -1139,6 +1159,27 @@ Elle a été dissoute.</value>
 			<value>(À traduire quand il y aura une version anglaise)</value>
 		</inline-string>
 
+		
+		<!-- Event names and templates -->
+		<inline-string id="evtBQProgressName">
+			<value>Avancement des constructions</value>
+		</inline-string>
+		<inline-string id="evtBQProgressTemplate">
+			<value>FIXME: this should be some FreeMarker code in a file</value>
+		</inline-string>
+		<inline-string id="evtBQEmptyName">
+			<value>File de construction vide</value>
+		</inline-string>
+		<inline-string id="evtBQEmptyTemplate">
+			<value>FIXME: this should be some FreeMarker code in a file</value>
+		</inline-string>
+		<inline-string id="evtResearchName">
+			<value>Découverte scientifique</value>
+		</inline-string>
+		<inline-string id="evtResearchTemplate">
+			<value>FIXME: this should be some FreeMarker code in a file</value>
+		</inline-string>
+
 	</language>
 
 </lw-text-data>

From c8f19a4c06cc3b5a077d44be58c66e91aa96bf8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 30 Jun 2012 14:47:02 +0200
Subject: [PATCH 84/94] Event record creation bugfix

When I18N strings (name and template) were added to the event definition
table, some of the code was not modified.
---
 .../db-structure/parts/040-functions/170-events.sql         | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
index 8cdba58..f545d8c 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
@@ -111,7 +111,8 @@ BEGIN
 	END IF;
 
 	INSERT INTO evdef_temp( evdef_id , evfld_typespec )
-		VALUES( _id , _prio::TEXT || ' , ' || _adj::TEXT );
+		VALUES( _id , _prio::TEXT || ',' || _adj::TEXT
+					|| ',' || _name_id || ',' || _template_id );
 	RETURN 'OK';
 END;
 $evdef_start$;
@@ -475,7 +476,8 @@ BEGIN
 	END IF;
 
 	_qstr := 'INSERT INTO events.event_definitions (' ||
-				'evdef_id , evdef_priority , evdef_adjustable' ||
+				'evdef_id , evdef_priority , evdef_adjustable , ' ||
+				'evdef_name_id , evdef_template_id' ||
 			') VALUES ( $1 , ' || _rec.evfld_typespec || ');';
 
 	BEGIN

From 75c5245764e97cbe09632d8309b9f7b96a939cba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 30 Jun 2012 14:52:55 +0200
Subject: [PATCH 85/94] Event definition loader

Implemented the ImportEvents command line tool, which allows event
definitions to be imported. In all cases the tool will try to import all
definitions; if an error occurs, the process continues but the
transaction is rolled back. It skips existing definitions rather than
taking the risk of doing something inappropriate (e.g. deletion of
existing events).
---
 build/post-build.d/20-import-tools.sh         |   1 +
 .../com/deepclone/lw/cli/ImportEvents.java    | 715 ++++++++++++++++++
 .../cli/xmlimport/EventDefinitionLoader.java  | 123 +++
 .../data/evdef/BooleanEventField.java         |  26 +
 .../data/evdef/EntityEventField.java          |  39 +
 .../xmlimport/data/evdef/EventDefinition.java | 101 +++
 .../data/evdef/EventDefinitions.java          |  58 ++
 .../xmlimport/data/evdef/EventFieldBase.java  |  52 ++
 .../data/evdef/EventFieldEntityType.java      |  21 +
 .../xmlimport/data/evdef/EventFieldType.java  |  25 +
 .../xmlimport/data/evdef/I18NEventField.java  |  26 +
 .../data/evdef/IntegerEventField.java         |  47 ++
 .../xmlimport/data/evdef/RealEventField.java  |  48 ++
 .../xmlimport/data/evdef/TextEventField.java  |  50 ++
 legacyworlds/doc/local-deployment.txt         |   2 +
 15 files changed, 1334 insertions(+)
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportEvents.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/EventDefinitionLoader.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/BooleanEventField.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/I18NEventField.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java
 create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java

diff --git a/build/post-build.d/20-import-tools.sh b/build/post-build.d/20-import-tools.sh
index de69c46..5a09127 100644
--- a/build/post-build.d/20-import-tools.sh
+++ b/build/post-build.d/20-import-tools.sh
@@ -31,6 +31,7 @@ cat > data-source.xml <<EOF
 EOF
 
 java legacyworlds-server-main-*.jar --run-tool ImportText data/i18n-text.xml || exit 1
+java legacyworlds-server-main-*.jar --run-tool ImportEvents data/event-definitions.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportResources data/resources.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportTechs data/techs.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportTechGraph data/tech-graph.xml || exit 1
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportEvents.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportEvents.java
new file mode 100644
index 0000000..92401fd
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportEvents.java
@@ -0,0 +1,715 @@
+package com.deepclone.lw.cli;
+
+
+import java.io.File;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.log4j.Logger;
+import org.postgresql.util.PGobject;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.FileSystemXmlApplicationContext;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.cli.xmlimport.EventDefinitionLoader;
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.evdef.EntityEventField;
+import com.deepclone.lw.cli.xmlimport.data.evdef.EventDefinition;
+import com.deepclone.lw.cli.xmlimport.data.evdef.EventDefinitions;
+import com.deepclone.lw.cli.xmlimport.data.evdef.EventFieldBase;
+import com.deepclone.lw.cli.xmlimport.data.evdef.IntegerEventField;
+import com.deepclone.lw.cli.xmlimport.data.evdef.RealEventField;
+import com.deepclone.lw.cli.xmlimport.data.evdef.TextEventField;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+public class ImportEvents
+		extends CLITool
+{
+
+	/**
+	 * Error codes that may be returned by the various import stored procedures; see the
+	 * corresponding SQL code for details about each value.
+	 */
+	private static enum SPErrorCode {
+		OK ,
+		BAD_ID ,
+		BAD_STRINGS ,
+		DUPLICATE ,
+		BAD_SPEC ,
+		NO_DATA
+	};
+
+	/** Logging system */
+	private final Logger logger = Logger.getLogger( ImportResources.class );
+
+	/** File to read the definitions from */
+	private File file;
+
+	/** Spring transaction template */
+	private TransactionTemplate tTemplate;
+
+	/** Error counter */
+	private int errors = 0;
+
+	/** Skipped definitions counter */
+	private int skipped = 0;
+
+	/** Successful imports counter */
+	private int successful = 0;
+
+	/** Stored procedure that starts recording an event definition */
+	private StoredProc fEvdefStart;
+
+	/** Stored procedure that finalises an event definition's recording */
+	private StoredProc fEvdefFinalise;
+
+	/** Stored procedure that defines a boolean field */
+	private StoredProc fEvdefAddfldBool;
+
+	/** Stored procedure that defines an I18N reference field */
+	private StoredProc fEvdefAddfldI18N;
+
+	/** Stored procedure that defines an entity reference field */
+	private StoredProc fEvdefAddfldEntity;
+
+	/** Unbounded integer field definition procedure */
+	private StoredProc fEvdefAddfldInt;
+
+	/** Stored procedure that defines an integer field with a lower bound */
+	private StoredProc fEvdefAddfldIntMin;
+
+	/** Stored procedure that defines an integer field with a higher bound */
+	private StoredProc fEvdefAddfldIntMax;
+
+	/** Stored procedure that defines a bounded integer field */
+	private StoredProc fEvdefAddfldIntRange;
+
+	/** Unbounded double precision field definition procedure */
+	private StoredProc fEvdefAddfldReal;
+
+	/** Stored procedure that defines a double precision field with a lower bound */
+	private StoredProc fEvdefAddfldRealMin;
+
+	/** Stored procedure that defines a double precision field with a higher bound */
+	private StoredProc fEvdefAddfldRealMax;
+
+	/** Stored procedure that defines a bounded double precision field */
+	private StoredProc fEvdefAddfldRealRange;
+
+	/** Unbounded text field definition procedure */
+	private StoredProc fEvdefAddfldText;
+
+	/** Stored procedure that defines a text field with a minimum length */
+	private StoredProc fEvdefAddfldTextMin;
+
+	/** Stored procedure that defines a text field with a maximal length */
+	private StoredProc fEvdefAddfldTextMax;
+
+	/** Stored procedure that defines a bounded text field */
+	private StoredProc fEvdefAddfldTextRange;
+
+
+	/**
+	 * Obtain the name of the definitions file
+	 * 
+	 * <p>
+	 * Check the command line options, setting the definitions file accordingly.
+	 */
+	@Override
+	public boolean setOptions( String... options )
+	{
+		if ( options.length != 1 ) {
+			return false;
+		}
+		this.file = new File( options[ 0 ] );
+		if ( ! ( this.file.isFile( ) && this.file.canRead( ) ) ) {
+			return false;
+		}
+		return true;
+	}
+
+
+	/**
+	 * Run the event definitions import tool
+	 * 
+	 * <p>
+	 * Loads the data file, then connects to the database and creates all missing definitions.
+	 */
+	@Override
+	public void run( )
+	{
+		EventDefinitions data;
+		try {
+			data = new EventDefinitionLoader( this.file ).load( );
+		} catch ( DataImportException e ) {
+			this.logger.error( "Error while loading event definitions" , e );
+			return;
+		}
+
+		AbstractApplicationContext ctx = this.createContext( );
+		this.createTemplates( ctx );
+		this.executeImportTransaction( data );
+		ToolBase.destroyContext( ctx );
+	}
+
+
+	/**
+	 * Create the Spring context
+	 * 
+	 * <p>
+	 * Load the definition of the data source as a Spring context, then adds the transaction
+	 * management component.
+	 * 
+	 * @return the Spring application context
+	 */
+	private ClassPathXmlApplicationContext createContext( )
+	{
+		FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( new String[] {
+			this.getDataSource( )
+		} );
+		ctx.refresh( );
+
+		return new ClassPathXmlApplicationContext( new String[] {
+			"configuration/transactions.xml"
+		} , true , ctx );
+	}
+
+
+	/**
+	 * Create database access templates
+	 * 
+	 * <p>
+	 * Initialise the transaction template and the four stored procedure definitions.
+	 * 
+	 * @param ctx
+	 *            the Spring application context
+	 */
+	private void createTemplates( ApplicationContext ctx )
+	{
+		DataSource dSource = ctx.getBean( DataSource.class );
+		PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
+
+		this.fEvdefStart = new StoredProc( dSource , "events" , "evdef_start" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_prio" , java.sql.Types.INTEGER )
+				.addParameter( "_adj" , java.sql.Types.BOOLEAN ).addParameter( "_i18n" , java.sql.Types.VARCHAR )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+
+		this.fEvdefFinalise = new StoredProc( dSource , "events" , "evdef_finalise" ).addOutput( "_error" ,
+				java.sql.Types.OTHER ).addOutput( "_fld" , java.sql.Types.VARCHAR );
+
+		this.fEvdefAddfldBool = new StoredProc( dSource , "events" , "evdef_addfld_bool" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+
+		this.fEvdefAddfldI18N = new StoredProc( dSource , "events" , "evdef_addfld_i18n" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+
+		this.fEvdefAddfldEntity = new StoredProc( dSource , "events" , "evdef_addfld_entity" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_etype" , "events.entity_field_type" ).addOutput( "_error" , java.sql.Types.OTHER );
+
+		this.fEvdefAddfldInt = new StoredProc( dSource , "events" , "evdef_addfld_int" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldIntMin = new StoredProc( dSource , "events" , "evdef_addfld_int_min" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_min" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldIntMax = new StoredProc( dSource , "events" , "evdef_addfld_int_max" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_max" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldIntRange = new StoredProc( dSource , "events" , "evdef_addfld_int_range" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_min" , java.sql.Types.INTEGER ).addParameter( "_max" , java.sql.Types.INTEGER )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+
+		this.fEvdefAddfldReal = new StoredProc( dSource , "events" , "evdef_addfld_real" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldRealMin = new StoredProc( dSource , "events" , "evdef_addfld_real_min" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_min" , java.sql.Types.DOUBLE ).addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldRealMax = new StoredProc( dSource , "events" , "evdef_addfld_real_max" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_max" , java.sql.Types.DOUBLE ).addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldRealRange = new StoredProc( dSource , "events" , "evdef_addfld_real_range" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_min" , java.sql.Types.DOUBLE ).addParameter( "_max" , java.sql.Types.DOUBLE )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+
+		this.fEvdefAddfldText = new StoredProc( dSource , "events" , "evdef_addfld_text" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldTextMin = new StoredProc( dSource , "events" , "evdef_addfld_text_min" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_min" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldTextMax = new StoredProc( dSource , "events" , "evdef_addfld_text_max" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_max" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER );
+		this.fEvdefAddfldTextRange = new StoredProc( dSource , "events" , "evdef_addfld_text_range" )
+				.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
+				.addParameter( "_min" , java.sql.Types.INTEGER ).addParameter( "_max" , java.sql.Types.INTEGER )
+				.addOutput( "_error" , java.sql.Types.OTHER );
+
+		this.tTemplate = new TransactionTemplate( tManager );
+	}
+
+
+	/**
+	 * Execute the event definitions importation transaction
+	 * 
+	 * <p>
+	 * Run a transaction and execute the importation code inside it. Roll back if anything goes
+	 * wrong.
+	 * 
+	 * @param data
+	 *            the event definitions top-level instance
+	 */
+	private void executeImportTransaction( final EventDefinitions data )
+	{
+		boolean rv = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
+			@Override
+			public Boolean doInTransaction( TransactionStatus status )
+			{
+				boolean rv = ImportEvents.this.doTransaction( data );
+				if ( !rv ) {
+					status.setRollbackOnly( );
+				}
+				return rv;
+			}
+		} );
+
+		if ( rv ) {
+			this.logger.info( "Import complete; " + this.successful + " new type(s), " + this.skipped + " skipped" );
+		} else {
+			this.logger.error( this.errors + " error(s) occurred, no changes were made" );
+		}
+	}
+
+
+	/**
+	 * Import transaction body
+	 * 
+	 * <p>
+	 * Import all definitions and handle exceptions.
+	 * 
+	 * @param data
+	 *            the event definitions top-level instance
+	 * @return <code>true</code> on success, <code>false</code> otherwise
+	 */
+	private boolean doTransaction( EventDefinitions data )
+	{
+		try {
+			this.importDefinitions( data );
+			return this.errors == 0;
+		} catch ( RuntimeException e ) {
+			this.logger.error( "Caught runtime exception" , e );
+			this.errors ++;
+		}
+		return false;
+	}
+
+
+	/**
+	 * Extract the error code from a stored procedure's return value
+	 * 
+	 * <p>
+	 * This method is used to extract the error code from the maps returned by a definition stored
+	 * procedure's execution.
+	 * 
+	 * @param result
+	 *            the map returned by the stored procedure's execution
+	 * 
+	 * @return the error code
+	 */
+	private SPErrorCode getSPResult( Map< String , Object > result )
+	{
+		return SPErrorCode.valueOf( ( (PGobject) result.get( "_error" ) ).getValue( ) );
+	}
+
+
+	/**
+	 * Import all event definitions from the top-level importable instance
+	 * 
+	 * <p>
+	 * This method iterates through all of the definitions, trying to import each of them.
+	 * 
+	 * @param data
+	 *            the top-level importable instance
+	 */
+	private void importDefinitions( EventDefinitions data )
+	{
+		for ( EventDefinition evdef : data ) {
+			this.importDefinition( evdef );
+		}
+	}
+
+
+	/**
+	 * Try to import an event definition
+	 * 
+	 * <p>
+	 * This method will attempt to import an event definition. It will increase the state counters (
+	 * {@link #errors}, {@link #skipped} and {@link #successful}) depending on the results.
+	 * 
+	 * @param evdef
+	 *            the event definition to import
+	 */
+	private void importDefinition( EventDefinition evdef )
+	{
+		boolean ok;
+
+		// Start the import
+		try {
+			this.startImport( evdef );
+			ok = true;
+		} catch ( DataImportException e ) {
+			this.logger.error( e.getMessage( ) );
+			ok = false;
+		}
+
+		// Import all fields
+		if ( ok ) {
+			for ( EventFieldBase field : evdef ) {
+				ok = ok && this.importField( evdef , field );
+				if ( !ok ) {
+					break;
+				}
+			}
+		}
+
+		// Finalise the operation
+		boolean wasNew = false;
+		try {
+			wasNew = this.endImport( evdef );
+		} catch ( DataImportException e ) {
+			if ( ok ) {
+				this.logger.error( e.getMessage( ) );
+				ok = false;
+			}
+		}
+
+		// Update counters and print to log
+		if ( ok ) {
+			if ( wasNew ) {
+				this.logger.info( evdef.getIdentifier( ) + ": new event definition imported" );
+				this.successful++;
+			} else {
+				this.logger.warn( evdef.getIdentifier( ) + ": event definition exists (skipped)" );
+				this.skipped++;
+			}
+		} else {
+			this.errors++;
+		}
+	}
+
+
+	/**
+	 * Start importing an event definition
+	 * 
+	 * <p>
+	 * This method calls the <code>events.evdef_start()</code> stored procedure to initialise the
+	 * event definition registration, and handles its return value.
+	 * 
+	 * @param evdef
+	 *            the event definition instance from the XML definition
+	 * 
+	 * @throws DataImportException
+	 *             if the definition's identifier is invalid or if one of the internationalised
+	 *             strings is missing
+	 */
+	private void startImport( EventDefinition evdef )
+			throws DataImportException
+	{
+		SPErrorCode ec;
+		ec = this.getSPResult( this.fEvdefStart.execute( evdef.getIdentifier( ) , evdef.getPriority( ) ,
+				evdef.isAdjustable( ) , evdef.getI18NStrings( ) ) );
+
+		switch ( ec ) {
+			case OK:
+				break;
+
+			case BAD_ID:
+				throw new DataImportException( evdef.getIdentifier( ) + ": bad event type identifier" );
+
+			case BAD_STRINGS:
+				throw new DataImportException( evdef.getIdentifier( ) + ": I18N strings not found" );
+
+			default:
+				throw new RuntimeException( "Unsupported return value for events.evdef_start(): " + ec.toString( ) );
+		}
+	}
+
+
+	/**
+	 * Import a field definition
+	 * 
+	 * <p>
+	 * Call the appropriate field definition importation method depending on the field's type.
+	 * 
+	 * @param evdef
+	 *            the event definition
+	 * @param field
+	 *            the field definition
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> if something went wrong
+	 */
+	private boolean importField( EventDefinition evdef , EventFieldBase field )
+	{
+		SPErrorCode ec;
+		switch ( field.getType( ) ) {
+			case BOOLEAN:
+				ec = this.importBooleanField( field );
+				break;
+
+			case I18N:
+				ec = this.importI18NField( field );
+				break;
+
+			case ENTITY:
+				ec = this.importEntityField( (EntityEventField) field );
+				break;
+
+			case INTEGER:
+				ec = this.importIntegerField( (IntegerEventField) field );
+				break;
+
+			case REAL:
+				ec = this.importRealField( (RealEventField) field );
+				break;
+
+			case TEXT:
+				ec = this.importTextField( (TextEventField) field );
+				break;
+
+			default:
+				throw new RuntimeException( "Unsupported field type " + field.getType( ).toString( ) );
+		}
+
+		switch ( ec ) {
+			case OK:
+				return true;
+
+			case BAD_ID:
+				this.logger.error( evdef.getIdentifier( ) + ": bad field identifier '" + field.getIdentifier( ) + "'" );
+				break;
+
+			default:
+				throw new RuntimeException( "Unsupported return value for field definition procedure: " + ec.toString( ) );
+		}
+		return false;
+	}
+
+
+	/**
+	 * Import a boolean field
+	 * 
+	 * <p>
+	 * This method calls <code>events.evdef_addfld_bool</code> to register a new boolean field.
+	 * 
+	 * @param field
+	 *            the field's definition
+	 * 
+	 * @return the stored procedure's error code
+	 */
+	private SPErrorCode importBooleanField( EventFieldBase field )
+	{
+		return this.getSPResult( this.fEvdefAddfldBool.execute( field.getIdentifier( ) , !field.isRequired( ) ) );
+	}
+
+
+	/**
+	 * Import an I18N reference field
+	 * 
+	 * <p>
+	 * This method calls <code>events.evdef_addfld_i18n</code> to register a new I18N reference
+	 * field.
+	 * 
+	 * @param field
+	 *            the field's definition
+	 * 
+	 * @return the stored procedure's error code
+	 */
+	private SPErrorCode importI18NField( EventFieldBase field )
+	{
+		return this.getSPResult( this.fEvdefAddfldI18N.execute( field.getIdentifier( ) , !field.isRequired( ) ) );
+	}
+
+
+	/**
+	 * Import an entity reference field
+	 * 
+	 * <p>
+	 * This method calls <code>events.evdef_addfld_entity</code> to register a new entity reference
+	 * field.
+	 * 
+	 * @param field
+	 *            the field's definition
+	 * 
+	 * @return the stored procedure's error code
+	 */
+	private SPErrorCode importEntityField( EntityEventField field )
+	{
+		return this.getSPResult( this.fEvdefAddfldEntity.execute( field.getIdentifier( ) , !field.isRequired( ) ,
+				field.getEntityType( ) ) );
+	}
+
+
+	/**
+	 * Import an integer field definition
+	 * 
+	 * <p>
+	 * This method calls one of the integer field definitions stored procedures, depending on the
+	 * definition's parameters.
+	 * 
+	 * @param field
+	 *            the field's definition
+	 * 
+	 * @return the stored procedure's error code
+	 */
+	private SPErrorCode importIntegerField( IntegerEventField field )
+	{
+		String id = field.getIdentifier( );
+		boolean opt = !field.isRequired( );
+		Integer min = field.getMin( );
+		Integer max = field.getMax( );
+
+		Map< String , Object > result;
+		if ( min == null && max == null ) {
+			result = this.fEvdefAddfldInt.execute( id , opt );
+		} else if ( min == null ) {
+			result = this.fEvdefAddfldIntMax.execute( id , opt , max );
+		} else if ( max == null ) {
+			result = this.fEvdefAddfldIntMin.execute( id , opt , min );
+		} else {
+			result = this.fEvdefAddfldIntRange.execute( id , opt , min , max );
+		}
+
+		return this.getSPResult( result );
+	}
+
+
+	/**
+	 * Import a double precision field definition
+	 * 
+	 * <p>
+	 * This method calls one of the double precision field definitions stored procedures, depending
+	 * on the definition's parameters.
+	 * 
+	 * @param field
+	 *            the field's definition
+	 * 
+	 * @return the stored procedure's error code
+	 */
+	private SPErrorCode importRealField( RealEventField field )
+	{
+		String id = field.getIdentifier( );
+		boolean opt = !field.isRequired( );
+		Double min = field.getMin( );
+		Double max = field.getMax( );
+
+		Map< String , Object > result;
+		if ( min == null && max == null ) {
+			result = this.fEvdefAddfldReal.execute( id , opt );
+		} else if ( min == null ) {
+			result = this.fEvdefAddfldRealMax.execute( id , opt , max );
+		} else if ( max == null ) {
+			result = this.fEvdefAddfldRealMin.execute( id , opt , min );
+		} else {
+			result = this.fEvdefAddfldRealRange.execute( id , opt , min , max );
+		}
+
+		return this.getSPResult( result );
+	}
+
+
+	/**
+	 * Import a text field definition
+	 * 
+	 * <p>
+	 * This method calls one of the text field definitions stored procedures, depending on the
+	 * definition's parameters.
+	 * 
+	 * @param field
+	 *            the field's definition
+	 * 
+	 * @return the stored procedure's error code
+	 */
+	private SPErrorCode importTextField( TextEventField field )
+	{
+		String id = field.getIdentifier( );
+		boolean opt = !field.isRequired( );
+		Integer min = field.getMinLength( );
+		Integer max = field.getMaxLength( );
+
+		Map< String , Object > result;
+		if ( min == null && max == null ) {
+			result = this.fEvdefAddfldText.execute( id , opt );
+		} else if ( min == null ) {
+			result = this.fEvdefAddfldTextMax.execute( id , opt , max );
+		} else if ( max == null ) {
+			result = this.fEvdefAddfldTextMin.execute( id , opt , min );
+		} else {
+			result = this.fEvdefAddfldTextRange.execute( id , opt , min , max );
+		}
+
+		return this.getSPResult( result );
+	}
+
+
+	/**
+	 * Finalise the current event definition
+	 * 
+	 * <p>
+	 * This method calls the <code>events.evdef_finalise()</code> stored procedure, and handles its
+	 * return values.
+	 * 
+	 * @param evdef
+	 *            the event definition from the XML file
+	 * 
+	 * @return <code>true</code> if the definition was finalised or if it had no effect (except for
+	 *         cleanup) due to a previous error, <code>false</code> if the definition already
+	 *         existed and was skipped.
+	 * 
+	 * @throws DataImportException
+	 *             if the event's specification is invalid, if a field name is duplicated, or if a
+	 *             field's specification is invalid.
+	 */
+	private boolean endImport( EventDefinition evdef )
+			throws DataImportException
+	{
+		Map< String , Object > result = this.fEvdefFinalise.execute( );
+		SPErrorCode ec = this.getSPResult( result );
+		String field = (String) result.get( "_fld" );
+
+		switch ( ec ) {
+			case OK:
+			case NO_DATA:
+				return true;
+
+			case BAD_SPEC:
+				if ( field == null ) {
+					throw new DataImportException( evdef.getIdentifier( ) + ": bad event type specification" );
+				}
+				throw new DataImportException( evdef.getIdentifier( ) + ": bad field specification (" + field + ")" );
+
+			case DUPLICATE:
+				if ( field == null ) {
+					return false;
+				}
+				throw new DataImportException( evdef.getIdentifier( ) + ": duplicate field name " + field );
+
+			default:
+				throw new RuntimeException( "Unsupported return value for events.evdef_finalise(): " + ec.toString( ) );
+		}
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/EventDefinitionLoader.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/EventDefinitionLoader.java
new file mode 100644
index 0000000..69ff4d7
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/EventDefinitionLoader.java
@@ -0,0 +1,123 @@
+package com.deepclone.lw.cli.xmlimport;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.evdef.*;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.XStreamException;
+
+
+
+/**
+ * Event definitions loader
+ * 
+ * <p>
+ * This class implements loading event definitions from XML files. The definitions will be extracted
+ * and verified.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+public class EventDefinitionLoader
+{
+	/** The file to read the XML from */
+	private final File file;
+
+
+	/**
+	 * Initialise the loader
+	 * 
+	 * @param file
+	 *            the XML file that contains the definitions
+	 */
+	public EventDefinitionLoader( File file )
+	{
+		this.file = file.getAbsoluteFile( );
+	}
+
+
+	/**
+	 * Initialise the necessary XStream instance
+	 * 
+	 * <p>
+	 * Initialise the XStream instance by processing annotations in all event definition importable
+	 * data classes.
+	 * 
+	 * @return the XStream instance to use when loading the data
+	 */
+	private XStream initXStream( )
+	{
+		XStream xstream = new XStream( );
+		xstream.processAnnotations( EventDefinitions.class );
+		xstream.processAnnotations( BooleanEventField.class );
+		xstream.processAnnotations( IntegerEventField.class );
+		xstream.processAnnotations( RealEventField.class );
+		xstream.processAnnotations( TextEventField.class );
+		xstream.processAnnotations( I18NEventField.class );
+		xstream.processAnnotations( EntityEventField.class );
+		return xstream;
+	}
+
+
+	/**
+	 * Load the event definitions
+	 * 
+	 * <p>
+	 * Load the XML file and process the definitions file using XStream.
+	 * 
+	 * @return the top-level importable data instance
+	 * 
+	 * @throws DataImportException
+	 *             if reading from the file or parsing its contents fail
+	 */
+	private EventDefinitions loadXMLFile( )
+			throws DataImportException
+	{
+		FileInputStream fis;
+		try {
+			fis = new FileInputStream( this.file );
+		} catch ( FileNotFoundException e ) {
+			throw new DataImportException( "Unable to load event definitions" , e );
+		}
+
+		try {
+			try {
+				XStream xstream = this.initXStream( );
+				return (EventDefinitions) xstream.fromXML( fis );
+			} finally {
+				fis.close( );
+			}
+		} catch ( IOException e ) {
+			throw new DataImportException( "Input error while loading event definitions" , e );
+		} catch ( XStreamException e ) {
+			throw new DataImportException( "XML error while loading event definitions" , e );
+		}
+	}
+
+
+	/**
+	 * Load and process I18N definition
+	 * 
+	 * <p>
+	 * Attempt to load all I18N definitions, make sure they are valid, then set the original file's
+	 * path.
+	 * 
+	 * @return the top-level importable data instance
+	 * 
+	 * @throws DataImportException
+	 *             if loading or verifying the data fails
+	 */
+	public EventDefinitions load( )
+			throws DataImportException
+	{
+		EventDefinitions events = this.loadXMLFile( );
+		events.verifyData( );
+		events.setReadFrom( this.file );
+		return events;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/BooleanEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/BooleanEventField.java
new file mode 100644
index 0000000..24d134b
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/BooleanEventField.java
@@ -0,0 +1,26 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+
+
+/**
+ * A boolean field definition
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@XStreamAlias( "boolean" )
+@SuppressWarnings( "serial" )
+public class BooleanEventField
+		extends EventFieldBase
+{
+
+	/** @return {@link EventFieldType#BOOLEAN} */
+	@Override
+	public EventFieldType getType( )
+	{
+		return EventFieldType.BOOLEAN;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java
new file mode 100644
index 0000000..3cb893a
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java
@@ -0,0 +1,39 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * An entity reference field definition
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@XStreamAlias( "entity" )
+@SuppressWarnings( "serial" )
+public class EntityEventField
+		extends EventFieldBase
+{
+
+	/** The type of entities this field references */
+	@XStreamAsAttribute
+	@XStreamAlias( "entity-type" )
+	private EventFieldEntityType entityType;
+
+
+	public EventFieldEntityType getEntityType( )
+	{
+		return this.entityType;
+	}
+
+
+	/** @return {@link EntityFieldType#ENTITY} */
+	@Override
+	public EventFieldType getType( )
+	{
+		return EventFieldType.ENTITY;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java
new file mode 100644
index 0000000..f2d6148
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java
@@ -0,0 +1,101 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+/**
+ * Event definition
+ * 
+ * <p>
+ * This class represents an event definition entry from the data file. An event definition consists
+ * in a few properties, and a potentially empty list of event field definitions.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+@SuppressWarnings( "serial" )
+public class EventDefinition
+		extends ImportableData
+		implements Iterable< EventFieldBase >
+{
+
+	/** The event type's identifier */
+	@XStreamAsAttribute
+	@XStreamAlias( "id" )
+	private String identifier;
+
+	/** The default priority */
+	@XStreamAsAttribute
+	private Integer priority;
+
+	/** Whether users can override the events' priority */
+	@XStreamAsAttribute
+	private Boolean adjustable;
+
+	/** Prefix of the name and template strings */
+	@XStreamAsAttribute
+	@XStreamAlias( "i18n-strings" )
+	private String i18nStrings;
+
+	/** The list of fields */
+	@XStreamImplicit
+	private List< EventFieldBase > fields = new ArrayList< EventFieldBase >( );
+
+
+	public String getIdentifier( )
+	{
+		return this.identifier;
+	}
+
+
+	public int getPriority( )
+	{
+		return ( this.priority == null ) ? 2 : this.priority;
+	}
+
+
+	public boolean isAdjustable( )
+	{
+		return ( this.adjustable == null ) ? true : this.adjustable;
+	}
+
+
+	public String getI18NStrings( )
+	{
+		return this.i18nStrings;
+	}
+
+
+	@Override
+	public Iterator< EventFieldBase > iterator( )
+	{
+		return Collections.unmodifiableList( this.fields ).iterator( );
+	}
+
+
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		super.verifyData( );
+		if ( this.fields == null ) {
+			this.fields = new ArrayList< EventFieldBase >( );
+		} else {
+			for ( EventFieldBase f : this.fields ) {
+				f.verifyData( );
+			}
+		}
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java
new file mode 100644
index 0000000..9e67ce4
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java
@@ -0,0 +1,58 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+/**
+ * Event type definitions
+ * 
+ * <p>
+ * This class represents the list of event type definitions as it is found in the appropriate data
+ * file.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+@XStreamAlias( "lw-event-definitions" )
+public class EventDefinitions
+		extends ImportableData
+		implements Iterable< EventDefinition >
+{
+
+	/** The list of event definitions */
+	@XStreamImplicit( itemFieldName = "evdef" )
+	private List< EventDefinition > definitions = new LinkedList< EventDefinition >( );
+
+
+	/** Iterate over event definitions */
+	@Override
+	public Iterator< EventDefinition > iterator( )
+	{
+		return Collections.unmodifiableList( this.definitions ).iterator( );
+	}
+
+
+	@Override
+	public void verifyData( )
+			throws DataImportException
+	{
+		super.verifyData( );
+		if ( this.definitions == null ) {
+			throw new DataImportException( "No definitions in this file" );
+		}
+		for ( EventDefinition def : this.definitions ) {
+			def.verifyData( );
+		}
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java
new file mode 100644
index 0000000..ea6ac1b
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java
@@ -0,0 +1,52 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import com.deepclone.lw.cli.xmlimport.data.ImportableData;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * Common event field definition
+ * 
+ * <p>
+ * This abstract class is used as the base for event field definitions.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@SuppressWarnings( "serial" )
+public abstract class EventFieldBase
+		extends ImportableData
+{
+
+	/** The field's identifier */
+	@XStreamAsAttribute
+	@XStreamAlias( "id" )
+	private String identifier;
+
+	/** Whether the field is required or optional */
+	@XStreamAsAttribute
+	private Boolean required;
+
+
+	/**
+	 * Obtain the type of the field
+	 * 
+	 * @return the field's type.
+	 */
+	public abstract EventFieldType getType( );
+
+
+	public String getIdentifier( )
+	{
+		return this.identifier;
+	}
+
+
+	public boolean isRequired( )
+	{
+		return this.required == null ? true : this.required;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java
new file mode 100644
index 0000000..362e02f
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java
@@ -0,0 +1,21 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+/** Types of entities which can be referenced through entity fields */
+public enum EventFieldEntityType {
+
+	/** Empires */
+	EMP ,
+	/** Planets */
+	PLN ,
+	/** Fleets */
+	FLT ,
+	/** Alliances */
+	ALL ,
+	/** Battles */
+	BAT ,
+	/** Administrators */
+	ADM ,
+	/** Bug reports */
+	BUG
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java
new file mode 100644
index 0000000..0eb450e
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java
@@ -0,0 +1,25 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+/** Types of event fields as they are stored in the definition file */
+public enum EventFieldType {
+
+	/** An integer field */
+	INTEGER ,
+
+	/** An double precision real number field */
+	REAL ,
+
+	/** A string field */
+	TEXT ,
+
+	/** A boolean field */
+	BOOLEAN ,
+
+	/** A field that references an internationalised string */
+	I18N ,
+
+	/** A field that references some game entity */
+	ENTITY
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/I18NEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/I18NEventField.java
new file mode 100644
index 0000000..689eb67
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/I18NEventField.java
@@ -0,0 +1,26 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+
+
+/**
+ * Definition of a field containing an I18N reference
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@XStreamAlias( "i18n" )
+@SuppressWarnings( "serial" )
+public class I18NEventField
+		extends EventFieldBase
+{
+
+	/** @return {@link EventFieldType#I18N} */
+	@Override
+	public EventFieldType getType( )
+	{
+		return EventFieldType.I18N;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java
new file mode 100644
index 0000000..9e89c7d
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java
@@ -0,0 +1,47 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * An integer event field definition
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@XStreamAlias( "integer" )
+@SuppressWarnings( "serial" )
+public class IntegerEventField
+		extends EventFieldBase
+{
+	/** Minimal value allowed for the field */
+	@XStreamAsAttribute
+	private Integer min;
+
+	/** Maximal value allowed for the field */
+	@XStreamAsAttribute
+	private Integer max;
+
+
+	public Integer getMin( )
+	{
+		return this.min;
+	}
+
+
+	public Integer getMax( )
+	{
+		return this.max;
+	}
+
+
+	/** @return {@link EventFieldType#INTEGER} */
+	@Override
+	public EventFieldType getType( )
+	{
+		return EventFieldType.INTEGER;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java
new file mode 100644
index 0000000..da3bf10
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java
@@ -0,0 +1,48 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * A real field definition
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@XStreamAlias( "real" )
+@SuppressWarnings( "serial" )
+public class RealEventField
+		extends EventFieldBase
+{
+
+	/** Minimal value for the field */
+	@XStreamAsAttribute
+	private Double min;
+
+	/** Maximal value for the field */
+	@XStreamAsAttribute
+	private Double max;
+
+
+	public Double getMin( )
+	{
+		return this.min;
+	}
+
+
+	public Double getMax( )
+	{
+		return this.max;
+	}
+
+
+	/** @return {@link EventFieldType#REAL} */
+	@Override
+	public EventFieldType getType( )
+	{
+		return EventFieldType.REAL;
+	}
+
+}
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java
new file mode 100644
index 0000000..80f6577
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java
@@ -0,0 +1,50 @@
+package com.deepclone.lw.cli.xmlimport.data.evdef;
+
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+/**
+ * A text event field definition
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@XStreamAlias( "text" )
+@SuppressWarnings( "serial" )
+public class TextEventField
+		extends EventFieldBase
+{
+
+	/** The field's minimum length */
+	@XStreamAsAttribute
+	@XStreamAlias( "min-length" )
+	private Integer minLength;
+
+	/** The field's maximum length */
+	@XStreamAsAttribute
+	@XStreamAlias( "max-length" )
+	private Integer maxLength;
+
+
+	public Integer getMinLength( )
+	{
+		return this.minLength;
+	}
+
+
+	public Integer getMaxLength( )
+	{
+		return this.maxLength;
+	}
+
+
+	/** @return {@link EventFieldType#TEXT} */
+	@Override
+	public EventFieldType getType( )
+	{
+		return EventFieldType.TEXT;
+	}
+
+}
diff --git a/legacyworlds/doc/local-deployment.txt b/legacyworlds/doc/local-deployment.txt
index a2a278d..245e281 100644
--- a/legacyworlds/doc/local-deployment.txt
+++ b/legacyworlds/doc/local-deployment.txt
@@ -39,6 +39,8 @@ from the root of the server's distribution:
 
 	java -jar legacyworlds-server-main-1.0.0-0.jar \
 		--run-tool ImportText data/i18n-text.xml
+	java -jar legacyworlds-server-main-1.0.0-0.jar \
+		--run-tool ImportEvents data/event-definitions.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \
 		--run-tool ImportTechGraph data/tech-graph.xml
 	java -jar legacyworlds-server-main-1.0.0-0.jar \

From 1dd1da5ae3f9125eb9088a95d33446c4e8edeb8a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 30 Jun 2012 15:43:26 +0200
Subject: [PATCH 86/94] Comments in event definition data classes

Some comments were missing in the classes used to load event definitions
from the XML file. They have been added.
---
 .../data/evdef/EntityEventField.java          |  5 +++
 .../xmlimport/data/evdef/EventDefinition.java | 32 +++++++++++++++++++
 .../data/evdef/EventDefinitions.java          |  6 ++++
 .../xmlimport/data/evdef/EventFieldBase.java  | 10 ++++++
 .../data/evdef/EventFieldEntityType.java      |  5 ++-
 .../xmlimport/data/evdef/EventFieldType.java  |  5 ++-
 .../data/evdef/IntegerEventField.java         | 10 ++++++
 .../xmlimport/data/evdef/RealEventField.java  | 10 ++++++
 .../xmlimport/data/evdef/TextEventField.java  | 10 ++++++
 9 files changed, 91 insertions(+), 2 deletions(-)

diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java
index 3cb893a..60fa881 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java
@@ -23,6 +23,11 @@ public class EntityEventField
 	private EventFieldEntityType entityType;
 
 
+	/**
+	 * Gets the type of entities this field references.
+	 * 
+	 * @return the type of entities this field references
+	 */
 	public EventFieldEntityType getEntityType( )
 	{
 		return this.entityType;
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java
index f2d6148..e68f1c2 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java
@@ -53,30 +53,55 @@ public class EventDefinition
 	private List< EventFieldBase > fields = new ArrayList< EventFieldBase >( );
 
 
+	/**
+	 * Gets the identifier.
+	 * 
+	 * @return the identifier
+	 */
 	public String getIdentifier( )
 	{
 		return this.identifier;
 	}
 
 
+	/**
+	 * Gets the priority.
+	 * 
+	 * @return the priority
+	 */
 	public int getPriority( )
 	{
 		return ( this.priority == null ) ? 2 : this.priority;
 	}
 
 
+	/**
+	 * Checks if the priority is adjustable.
+	 * 
+	 * @return <code>true</code> if the priority is adjustable
+	 */
 	public boolean isAdjustable( )
 	{
 		return ( this.adjustable == null ) ? true : this.adjustable;
 	}
 
 
+	/**
+	 * Gets the prefix of the name and template strings.
+	 * 
+	 * @return the prefix of the name and template strings.
+	 */
 	public String getI18NStrings( )
 	{
 		return this.i18nStrings;
 	}
 
 
+	/**
+	 * Allows iteration over the event definition's fields
+	 * 
+	 * @return an iterator on the event definition's fields
+	 */
 	@Override
 	public Iterator< EventFieldBase > iterator( )
 	{
@@ -84,6 +109,13 @@ public class EventDefinition
 	}
 
 
+	/**
+	 * Verify fields data
+	 * 
+	 * <p>
+	 * If no fields are defined, make sure the list is not <code>null</code>; otherwise iterate over
+	 * the fields to call their own data checks methods.
+	 */
 	@Override
 	public void verifyData( )
 			throws DataImportException
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java
index 9e67ce4..f5037e0 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java
@@ -42,6 +42,12 @@ public class EventDefinitions
 	}
 
 
+	/**
+	 * Verify the list of definitions
+	 * 
+	 * <p>
+	 * Make sure the list of definitions is not empty, then call the definitions' data check method.
+	 */
 	@Override
 	public void verifyData( )
 			throws DataImportException
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java
index ea6ac1b..3cec5f5 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java
@@ -38,12 +38,22 @@ public abstract class EventFieldBase
 	public abstract EventFieldType getType( );
 
 
+	/**
+	 * Gets the field's identifier.
+	 * 
+	 * @return the field's identifier
+	 */
 	public String getIdentifier( )
 	{
 		return this.identifier;
 	}
 
 
+	/**
+	 * Checks if the field is required or optional.
+	 * 
+	 * @return <code>true</code> if the field is required
+	 */
 	public boolean isRequired( )
 	{
 		return this.required == null ? true : this.required;
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java
index 362e02f..52889af 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java
@@ -1,7 +1,10 @@
 package com.deepclone.lw.cli.xmlimport.data.evdef;
 
 
-/** Types of entities which can be referenced through entity fields */
+/** Types of entities which can be referenced through entity fields
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
 public enum EventFieldEntityType {
 
 	/** Empires */
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java
index 0eb450e..1b33be3 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java
@@ -1,7 +1,10 @@
 package com.deepclone.lw.cli.xmlimport.data.evdef;
 
 
-/** Types of event fields as they are stored in the definition file */
+/** Types of event fields as they are stored in the definition file
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
 public enum EventFieldType {
 
 	/** An integer field */
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java
index 9e89c7d..1a8c1ec 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java
@@ -25,12 +25,22 @@ public class IntegerEventField
 	private Integer max;
 
 
+	/**
+	 * Gets the minimal value allowed for the field.
+	 * 
+	 * @return the minimal value allowed for the field
+	 */
 	public Integer getMin( )
 	{
 		return this.min;
 	}
 
 
+	/**
+	 * Gets the maximal value allowed for the field.
+	 * 
+	 * @return the maximal value allowed for the field
+	 */
 	public Integer getMax( )
 	{
 		return this.max;
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java
index da3bf10..2b68d23 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java
@@ -26,12 +26,22 @@ public class RealEventField
 	private Double max;
 
 
+	/**
+	 * Gets the minimal value for the field.
+	 * 
+	 * @return the minimal value for the field
+	 */
 	public Double getMin( )
 	{
 		return this.min;
 	}
 
 
+	/**
+	 * Gets the maximal value for the field.
+	 * 
+	 * @return the maximal value for the field
+	 */
 	public Double getMax( )
 	{
 		return this.max;
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java
index 80f6577..3e2ccd8 100644
--- a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java
@@ -28,12 +28,22 @@ public class TextEventField
 	private Integer maxLength;
 
 
+	/**
+	 * Gets the field's minimum length.
+	 * 
+	 * @return the field's minimum length
+	 */
 	public Integer getMinLength( )
 	{
 		return this.minLength;
 	}
 
 
+	/**
+	 * Gets the field's maximum length.
+	 * 
+	 * @return the field's maximum length
+	 */
 	public Integer getMaxLength( )
 	{
 		return this.maxLength;

From cf8dee6ec91bc016b06892a20f54eb3b6f6d412e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 30 Jun 2012 16:25:50 +0200
Subject: [PATCH 87/94] Priority settings update procedures

Added stored procedures which can be used to update customisable event
priorities:
 * events.evcp_set() adds or modifies an override,
 * events.clear() can be used to remove a specific override or all
overrides for a given player.
---
 .../parts/040-functions/170-events.sql        | 136 ++++++++++++++++++
 1 file changed, 136 insertions(+)

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
index f545d8c..db7be76 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
@@ -864,6 +864,142 @@ GRANT EXECUTE
 	TO :dbuser;
 
 
+
+/*
+ * Set an event priority override
+ * ------------------------------
+ * 
+ * Adds a new event priority override or modify its value. If the event type
+ * does not exist, does not allow overrides or if the specified priority is
+ * invalid, the error will be ignored silently.
+ * 
+ * Parameters:
+ *		_empire_id		The identifier of the player's empire
+ *		_evdef_id		The identifier of the event type
+ *		_priority		The custom priority value
+ */
+DROP FUNCTION IF EXISTS events.evcp_set( INT , TEXT , INT );
+CREATE FUNCTION events.evcp_set(
+			_empire_id	INT ,
+			_evdef_id	TEXT ,
+			_priority	INT )
+		RETURNS VOID
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $evcp_set$
+
+DECLARE
+	_user_id	INT;
+
+BEGIN
+	BEGIN
+		INSERT INTO events.custom_priorities(
+				evdef_id , address_id , evcp_priority )
+			SELECT _evdef_id , owner_id , _priority
+				FROM naming.empire_names
+				WHERE id = _empire_id;
+		IF NOT FOUND
+		THEN
+			RAISE EXCEPTION 'missing empire';
+		END IF;
+	EXCEPTION
+		WHEN unique_violation THEN
+			UPDATE events.custom_priorities
+				SET evcp_priority = _priority
+				WHERE evdef_id = _evdef_id
+					AND address_id = (
+						SELECT owner_id
+							FROM naming.empire_names
+							WHERE id = _empire_id );
+	END;
+
+EXCEPTION
+	WHEN raise_exception THEN
+		PERFORM sys.write_sql_log( 'Events' , 'WARNING'::log_level ,
+			'Call to events.evcp_set() from missing empire #' || _empire_id );
+
+	WHEN foreign_key_violation OR check_violation THEN
+		PERFORM sys.write_sql_log( 'Events' , 'WARNING'::log_level ,
+			'Bad call to events.evcp_set() from empire #' || _empire_id );
+END;
+$evcp_set$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evcp_set( INT , TEXT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evcp_set( INT , TEXT , INT )
+	TO :dbuser;
+
+
+
+/*
+ * Clear an event priority override
+ * --------------------------------
+ * 
+ * Remove an event priority override from the custom priorities table. If
+ * there was no override for the specified event type and empire, do nothing.
+ * 
+ * Parameters:
+ *		_empire_id		The identifier of the player's empire
+ *		_evdef_id		The identifier of the event type
+ */
+DROP FUNCTION IF EXISTS events.evcp_clear( INT , TEXT );
+CREATE FUNCTION events.evcp_clear(
+		_empire_id	INT ,
+		_evdef_id	TEXT )
+	RETURNS VOID
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $evcp_clear$
+	DELETE FROM events.custom_priorities
+		WHERE evdef_id = $2
+			AND address_id = (
+				SELECT owner_id
+					FROM naming.empire_names
+					WHERE id = $1 )
+$evcp_clear$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evcp_clear( INT , TEXT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evcp_clear( INT , TEXT )
+	TO :dbuser;
+
+
+
+/*
+ * Clear all event priority overrides
+ * ----------------------------------
+ * 
+ * Remove all event priority overrides set by some player.
+ * 
+ * Parameters:
+ *		_empire_id		The identifier of the player's empire
+ */
+DROP FUNCTION IF EXISTS events.evcp_clear( INT );
+CREATE FUNCTION events.evcp_clear( _empire_id INT )
+	RETURNS VOID
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $evcp_clear$
+	DELETE FROM events.custom_priorities
+		WHERE address_id = (
+				SELECT owner_id
+					FROM naming.empire_names
+					WHERE id = $1 )
+$evcp_clear$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evcp_clear( INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evcp_clear( INT )
+	TO :dbuser;
+
+
+
 /*
  * OLD B6M1 CODE BELOW!
  */

From 9e84f333e72e2b879b4677ba66326f8768063afa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sat, 30 Jun 2012 18:47:33 +0200
Subject: [PATCH 88/94] Events database structure

Added tables allowing event data to be stored on the long term:
 * events.events_v2 (which will need to be renamed) contains the events'
main entry
 * events.field_values stores field values for all events.
---
 .../parts/030-data/170-events.sql             | 141 ++++++++++++++++++
 1 file changed, 141 insertions(+)

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
index d571ee3..36bd27f 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
@@ -239,6 +239,147 @@ CREATE SEQUENCE events.event_id_sequence;
 GRANT SELECT,UPDATE ON events.event_id_sequence TO :dbuser;
 
 
+/*
+ * Event storage table
+ * -------------------
+ * 
+ * This table stores the main entries for actual events. It is fed using the
+ * various type-specific event queues.
+ * 
+ * FIXME: this table should be renamed once the old internal messages are
+ * gone.
+ */
+DROP TABLE IF EXISTS events.events_v2 CASCADE;
+CREATE TABLE events.events_v2(
+	/* Numeric identifier of the event */
+	event_id		BIGINT NOT NULL PRIMARY KEY ,
+	
+	/* Type of the event */
+	evdef_id		VARCHAR( 48 ) NOT NULL ,
+
+	/* Real time at which the event occurred */
+	event_rtime		TIMESTAMP WITHOUT TIME ZONE
+						NOT NULL ,
+
+	/* Game time (tick identifier) at which the event occurred */
+	event_gtime		BIGINT NOT NULL ,
+	
+	/* Identifier of the empire to which the event applies */
+	empire_id		INT NOT NULL
+);
+
+CREATE UNIQUE INDEX idx_events_iddef
+	ON events.events_v2 ( event_id , evdef_id );
+CREATE INDEX idx_events_selector
+	ON events.events_v2 ( empire_id , event_rtime );
+
+ALTER TABLE events.events_v2
+	ADD CONSTRAINT fk_events_evdef
+		FOREIGN KEY ( evdef_id ) REFERENCES events.event_definitions ( evdef_id ) ,
+	ADD CONSTRAINT fk_events_empire
+		FOREIGN KEY ( empire_id ) REFERENCES emp.empires ( name_id )
+			ON DELETE CASCADE;
+
+GRANT SELECT ON events.events_v2 TO :dbuser;
+
+
+/*
+ * Event field values storage table
+ * --------------------------------
+ * 
+ * This table stores the values of the events' fields.
+ */
+DROP TABLE IF EXISTS events.field_values CASCADE;
+CREATE TABLE events.field_values(
+	/* The event's identifier */
+	event_id		BIGINT NOT NULL ,
+	
+	/* The event's type */
+	evdef_id		VARCHAR(48) NOT NULL ,
+	
+	/* The field's identifier */
+	efdef_id		VARCHAR(48) NOT NULL ,
+	
+	/* Literal field value */ 
+	efval_litteral	TEXT NOT NULL ,
+	
+	/* I18N string reference */
+	string_id		INT ,
+
+	/* Empire reference */
+	empire_id		INT ,
+	
+	/* Planet reference */
+	planet_id		INT ,
+	
+	/* Fleet reference */
+	fleet_id		BIGINT ,
+	
+	/* Alliance reference */
+	alliance_id		INT ,
+
+	/* Battle reference */
+	battle_id		BIGINT ,
+
+	/* Administrator reference */
+	admin_id		INT ,
+
+	/* Bug report reference */
+	bug_report_id	BIGINT ,
+	
+	PRIMARY KEY( event_id , evdef_id , efdef_id )
+);
+
+CREATE INDEX idx_fvalues_string
+	ON events.field_values ( string_id );
+CREATE INDEX idx_fvalues_empire
+	ON events.field_values ( empire_id );
+CREATE INDEX idx_fvalues_planet
+	ON events.field_values ( planet_id );
+CREATE INDEX idx_fvalues_fleet
+	ON events.field_values ( fleet_id );
+CREATE INDEX idx_fvalues_alliance
+	ON events.field_values ( alliance_id );
+CREATE INDEX idx_fvalues_battle
+	ON events.field_values ( battle_id );
+CREATE INDEX idx_fvalues_admin
+	ON events.field_values ( admin_id );
+CREATE INDEX idx_fvalues_bugreport
+	ON events.field_values ( bug_report_id );
+
+ALTER TABLE events.field_values
+	ADD CONSTRAINT fk_fvalues_event
+		FOREIGN KEY ( event_id , evdef_id ) REFERENCES events.events_v2 ( event_id , evdef_id )
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_fvalues_field
+		FOREIGN KEY ( evdef_id , efdef_id ) REFERENCES events.field_definitions ,
+	ADD CONSTRAINT fk_fvalues_string
+		FOREIGN KEY ( string_id ) REFERENCES defs.strings ,
+	ADD CONSTRAINT fk_fvalues_empire
+		FOREIGN KEY ( empire_id ) REFERENCES emp.empires
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_fvalues_planet
+		FOREIGN KEY ( planet_id ) REFERENCES verse.planets
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_fvalues_fleet
+		FOREIGN KEY ( fleet_id ) REFERENCES fleets.fleets
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_fvalues_alliance
+		FOREIGN KEY ( alliance_id ) REFERENCES emp.alliances
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_fvalues_battle
+		FOREIGN KEY ( battle_id ) REFERENCES battles.battles
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_fvalues_admin
+		FOREIGN KEY ( admin_id ) REFERENCES admin.administrators
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_fvalues_bugreport
+		FOREIGN KEY ( bug_report_id ) REFERENCES bugs.initial_report_events
+			ON DELETE CASCADE;
+
+GRANT SELECT ON events.field_values TO :dbuser;
+
+
 
 /*
  * OLD B6M1 CODE BELOW!

From dc9ef2292d82437ceb209264ce76bb4f033ce07b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sun, 1 Jul 2012 01:04:26 +0200
Subject: [PATCH 89/94] Event-related functions split

Because the events management functions are definitely going to be quite
big, I thought it would be better to split them into multiple SQL files.
For now, there's a file for event definition functions and one for
functions that manipulate priority overrides. The old file containing
the old code stays around for now.
---
 .../040-functions/170-event-definitions.sql   | 865 +++++++++++++++
 .../parts/040-functions/170-events.sql        | 998 ------------------
 .../040-functions/171-event-priorities.sql    | 143 +++
 3 files changed, 1008 insertions(+), 998 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql
 create mode 100644 legacyworlds-server-data/db-structure/parts/040-functions/171-event-priorities.sql

diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql
new file mode 100644
index 0000000..564c971
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql
@@ -0,0 +1,865 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Functions allowing event definitions to be added to the
+-- database
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Return values for event definition functions
+ * ---------------------------------------------
+ */
+DROP TYPE IF EXISTS events.evdef_create_result CASCADE;
+CREATE TYPE events.evdef_create_result AS ENUM(
+	/* The event type definition function succeeded. If it was the initial
+	 * creation function or a field creation function, this means the creation
+	 * can continue. If it was the finalisation function, this means the event
+	 * definition has been added. 
+	 */
+	'OK' ,
+	
+	/* A bad identifier was specified. This value is returned by both the
+	 * initial creation function and event field definition functions.
+	 */
+	'BAD_ID' ,
+	
+	/* An internationalised string (either an event type's name or template)
+	 * could not be found.
+	 */
+	'BAD_STRINGS' ,
+
+	/* Duplicate event or field identifier found. This value is returned by
+	 * the finalisation function, with its "_fld" set to NULL if the duplicate
+	 * identifier is the event's.
+	 */
+	'DUPLICATE' ,
+
+	/* Bad event or field specification found. This value is returned by the
+	 * finalisation function when an event or field cannot be inserted due to
+	 * check failures.
+	 */
+	'BAD_SPEC' ,
+	
+	/* This error code is returned when trying to finalise an event after one
+	 * of the definition calls failed. 
+	 */
+	'NO_DATA'
+);
+
+
+
+/*
+ * Start defining a new event type
+ * -------------------------------
+ * 
+ * This function creates a temporary table used to defgine a new type of
+ * event. No checks beyond the length and contents of the event type's
+ * identifier are made at this point.
+ * 
+ * Note: the temporary table will be created in all cases, even when the
+ *		identifier is invalid.
+ * 
+ * Parameters:
+ *		_id			The identifier of the event type
+ *		_prio		The event type's default priority
+ *		_adj		Whether the player may adjust the priority for this type
+ *						of events.
+ *		_i18n		The "base" of the I18N strings identifiers; the function
+ *						will attempt to use "<_i18n>Name" as the name's
+ *						identifier and "<_i18n>Template" as the template's.
+ *
+ * Returns:
+ * 		???			An error code: OK or BAD_ID
+ *						(see events.evdef_create_result)
+ */
+DROP FUNCTION IF EXISTS events.evdef_start( TEXT , INT , BOOLEAN , TEXT );
+CREATE FUNCTION events.evdef_start(
+			_id		TEXT ,
+			_prio	INT ,
+			_adj	BOOLEAN ,
+			_i18n	TEXT )
+		RETURNS events.evdef_create_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $evdef_start$
+
+DECLARE
+	_name_id		INT;
+	_template_id	INT;
+
+BEGIN
+	CREATE TEMPORARY TABLE evdef_temp(
+		evdef_id		TEXT ,
+		evfld_id		TEXT ,
+		evfld_typespec	TEXT
+	) ON COMMIT DROP;
+	
+	IF LENGTH( _id ) < 2 OR LENGTH( _id ) > 48
+			OR NOT _id ~ '^[a-z\-]+$'
+	THEN
+		RETURN 'BAD_ID';
+	END IF;
+	
+	SELECT INTO _name_id id FROM defs.strings
+		WHERE name = _i18n || 'Name';
+	SELECT INTO _template_id id FROM defs.strings
+		WHERE name = _i18n || 'Template';
+	IF _name_id IS NULL OR _template_id IS NULL THEN
+		RETURN 'BAD_STRINGS';
+	END IF;
+
+	INSERT INTO evdef_temp( evdef_id , evfld_typespec )
+		VALUES( _id , _prio::TEXT || ',' || _adj::TEXT
+					|| ',' || _name_id || ',' || _template_id );
+	RETURN 'OK';
+END;
+$evdef_start$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN , TEXT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN , TEXT )
+	TO :dbuser;
+
+
+
+/*
+ * Create a new field
+ * ------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function adds data about a new field to the temporary event
+ * definition table.
+ * 
+ * Parameters:
+ *		_id			The field's identifier
+ *		_opt		Whether the field is optional
+ *		_type		The field's type
+ *		_etype		The field's entity type (may be NULL)
+ *		_is_int		Whether the field is an integer (may be NULL)
+ *		_min_val	Minimal value/length (may be NULL)
+ *		_max_val	Maximal value/length (may be NULL)
+ *
+ * Returns:
+ * 		???			An error code: OK or BAD_ID
+ *						(see events.evdef_create_result)
+ */
+DROP FUNCTION IF EXISTS events.evdef_addfld_internal(
+	TEXT , BOOLEAN , events.field_type , events.entity_field_type ,
+	BOOLEAN , NUMERIC , NUMERIC ) CASCADE;
+CREATE FUNCTION events.evdef_addfld_internal(
+			_id			TEXT ,
+			_opt		BOOLEAN ,
+			_type		events.field_type ,
+			_etype		events.entity_field_type ,
+			_is_int		BOOLEAN ,
+			_min_val	NUMERIC ,
+			_max_val	NUMERIC
+		) RETURNS events.evdef_create_result
+		LANGUAGE PLPGSQL
+		CALLED ON NULL INPUT
+		VOLATILE SECURITY INVOKER
+	AS $evdef_addfld_internal$
+
+DECLARE
+	_iq_string	TEXT;
+
+BEGIN
+	IF LENGTH( _id ) < 2 OR LENGTH( _id ) > 48
+			OR NOT _id ~ '^[a-z\-]+$'
+	THEN
+		DELETE FROM evdef_temp;
+		RETURN 'BAD_ID'::events.evdef_create_result;
+	END IF;
+
+	_iq_string := _opt::TEXT || ' , ''' || _type::TEXT || ''' , ';
+	IF _etype IS NULL
+	THEN
+		_iq_string := _iq_string || 'NULL';
+	ELSE
+		_iq_string := _iq_string || '''' || _etype::TEXT || '''';
+	END IF;
+	_iq_string := _iq_string || ' , ';
+	IF _is_int IS NULL
+	THEN
+		_iq_string := _iq_string || 'NULL';
+	ELSE
+		_iq_string := _iq_string || _is_int::TEXT;
+	END IF;
+	_iq_string := _iq_string || ' , ';
+	IF _min_val IS NULL
+	THEN
+		_iq_string := _iq_string || 'NULL';
+	ELSE
+		_iq_string := _iq_string || _min_val::TEXT;
+	END IF;
+	_iq_string := _iq_string || ' , ';
+	IF _max_val IS NULL
+	THEN
+		_iq_string := _iq_string || 'NULL';
+	ELSE
+		_iq_string := _iq_string || _max_val::TEXT;
+	END IF;
+
+	INSERT INTO evdef_temp( evdef_id , evfld_id , evfld_typespec )
+		SELECT evdef_id , _id , _iq_string
+			FROM evdef_temp
+			WHERE evfld_id IS NULL;
+	RETURN 'OK'::events.evdef_create_result;
+END;
+$evdef_addfld_internal$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_internal(
+		TEXT , BOOLEAN , events.field_type , events.entity_field_type ,
+		BOOLEAN , NUMERIC , NUMERIC ) 
+	FROM PUBLIC;
+
+
+/*
+ * Actual field creation functions
+ * --------------------------------
+ * 
+ * Bunch of functions that create the field records in the temporary table
+ * through events.evdef_addfld_internal().
+ * 
+ * Note:
+ *	These functions are not dropped manually here, as it is assumed that
+ *		dropping events.evdef_addfld_internal() will have cascaded.
+ */
+
+-- INTEGER FIELD, NO BOUNDS
+CREATE FUNCTION events.evdef_addfld_int( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_int( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_int( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- INTEGER FIELD W/ LOWER BOUND
+CREATE FUNCTION events.evdef_addfld_int_min( _id TEXT , _opt BOOLEAN , _min INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,$3::NUMERIC,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_int_min( TEXT , BOOLEAN , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_int_min( TEXT , BOOLEAN , INT )
+	TO :dbuser;
+
+-- INTEGER FIELD W/ HIGHER BOUND
+CREATE FUNCTION events.evdef_addfld_int_max( _id TEXT , _opt BOOLEAN , _max INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,NULL,$3::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_int_max( TEXT , BOOLEAN , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_int_max( TEXT , BOOLEAN , INT )
+	TO :dbuser;
+
+-- INTEGER FIELD W/ LIMITED RANGE
+CREATE FUNCTION events.evdef_addfld_int_range( _id TEXT , _opt BOOLEAN , _min INT , _max INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,$3::NUMERIC,$4::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_int_range( TEXT , BOOLEAN , INT , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_int_range( TEXT , BOOLEAN , INT , INT )
+	TO :dbuser;
+
+-- REAL FIELD, NO BOUNDS
+CREATE FUNCTION events.evdef_addfld_real( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_real( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_real( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- REAL FIELD W/ LOWER BOUND
+CREATE FUNCTION events.evdef_addfld_real_min( _id TEXT , _opt BOOLEAN , _min DOUBLE PRECISION )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,$3::NUMERIC,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_real_min( TEXT , BOOLEAN , DOUBLE PRECISION ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_real_min( TEXT , BOOLEAN , DOUBLE PRECISION )
+	TO :dbuser;
+
+-- REAL FIELD W/ HIGHER BOUND
+CREATE FUNCTION events.evdef_addfld_real_max( _id TEXT , _opt BOOLEAN , _max DOUBLE PRECISION )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,NULL,$3::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_real_max( TEXT , BOOLEAN , DOUBLE PRECISION ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_real_max( TEXT , BOOLEAN , DOUBLE PRECISION )
+	TO :dbuser;
+
+-- REAL FIELD W/ LIMITED RANGE
+CREATE FUNCTION events.evdef_addfld_real_range( _id TEXT , _opt BOOLEAN , _min DOUBLE PRECISION , _max DOUBLE PRECISION )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,$3::NUMERIC,$4::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_real_range( TEXT , BOOLEAN , DOUBLE PRECISION , DOUBLE PRECISION ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_real_range( TEXT , BOOLEAN , DOUBLE PRECISION , DOUBLE PRECISION )
+	TO :dbuser;
+
+-- TEXT FIELD, NO BOUNDS
+CREATE FUNCTION events.evdef_addfld_text( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'TEXT'::events.field_type,NULL,NULL,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_text( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_text( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- TEXT FIELD W/ LOWER BOUND
+CREATE FUNCTION events.evdef_addfld_text_min( _id TEXT , _opt BOOLEAN , _min INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'TEXT'::events.field_type,NULL,NULL,$3::NUMERIC,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_text_min( TEXT , BOOLEAN , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_text_min( TEXT , BOOLEAN , INT )
+	TO :dbuser;
+
+-- TEXT FIELD W/ HIGHER BOUND
+CREATE FUNCTION events.evdef_addfld_text_max( _id TEXT , _opt BOOLEAN , _max INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'TEXT'::events.field_type,NULL,NULL,NULL,$3::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_text_max( TEXT , BOOLEAN , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_text_max( TEXT , BOOLEAN , INT )
+	TO :dbuser;
+
+-- TEXT FIELD W/ LIMITED RANGE
+CREATE FUNCTION events.evdef_addfld_text_range( _id TEXT , _opt BOOLEAN , _min INT , _max INT )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'TEXT'::events.field_type,NULL,NULL,$3::NUMERIC,$4::NUMERIC); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_text_range( TEXT , BOOLEAN , INT , INT ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_text_range( TEXT , BOOLEAN , INT , INT )
+	TO :dbuser;
+
+-- BOOLEAN FIELD
+CREATE FUNCTION events.evdef_addfld_bool( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'BOOL'::events.field_type,NULL,NULL,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_bool( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_bool( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- I18N TEXT FIELD
+CREATE FUNCTION events.evdef_addfld_i18n( _id TEXT , _opt BOOLEAN )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'I18N'::events.field_type,NULL,NULL,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_i18n( TEXT , BOOLEAN ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_i18n( TEXT , BOOLEAN ) 
+	TO :dbuser;
+
+-- ENTITY LINK FIELD
+CREATE FUNCTION events.evdef_addfld_entity( _id TEXT , _opt BOOLEAN , _etype events.entity_field_type )
+		RETURNS events.evdef_create_result
+		LANGUAGE SQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $$ SELECT events.evdef_addfld_internal(
+			$1,$2,'ENTITY'::events.field_type,$3,NULL,NULL,NULL); $$;
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_addfld_entity( TEXT , BOOLEAN , events.entity_field_type ) 
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_addfld_entity( TEXT , BOOLEAN , events.entity_field_type ) 
+	TO :dbuser;
+
+
+/*
+ * Create an event definition record
+ * ---------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function inserts the record for an event definition into the
+ * appropriate table.
+ * 
+ * Returns:
+ *		???		An error code
+ */
+DROP FUNCTION IF EXISTS events.evdef_create_record( );
+CREATE FUNCTION events.evdef_create_record( )
+		RETURNS events.evdef_create_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY INVOKER
+	AS $evdef_create_record$
+
+DECLARE
+	_rec	RECORD;
+	_qstr	TEXT;
+
+BEGIN
+	SELECT INTO _rec *
+		FROM evdef_temp
+		WHERE evfld_id IS NULL;
+	IF NOT FOUND
+	THEN
+		RETURN 'NO_DATA';
+	END IF;
+
+	_qstr := 'INSERT INTO events.event_definitions (' ||
+				'evdef_id , evdef_priority , evdef_adjustable , ' ||
+				'evdef_name_id , evdef_template_id' ||
+			') VALUES ( $1 , ' || _rec.evfld_typespec || ');';
+
+	BEGIN
+		EXECUTE _qstr USING _rec.evdef_id;
+	EXCEPTION
+		WHEN unique_violation THEN
+			RETURN 'DUPLICATE';
+		WHEN check_violation THEN
+			RETURN 'BAD_SPEC';
+	END;
+
+	RETURN 'OK';
+END;
+$evdef_create_record$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_create_record( )
+	FROM PUBLIC;
+
+
+/*
+ * Create an event field definition record
+ * ---------------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function inserts a new event field definition record into the
+ * appropriate table.
+ *
+ * Parameters:
+ * 		_evdef_id	The event definition's identifier
+ *		_efdef_id	The field's identifier
+ *		_typespec	The type specification from the temporary table
+ *
+ * Returns:
+ *		???			An error code
+ */
+DROP FUNCTION IF EXISTS events.evdef_create_field_record(
+		TEXT , TEXT , TEXT );
+CREATE FUNCTION events.evdef_create_field_record(
+			_evdef_id	TEXT ,
+			_efdef_id	TEXT ,
+			_typespec	TEXT )
+		RETURNS events.evdef_create_result
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY INVOKER
+	AS $evdef_create_field_record$
+
+DECLARE
+	_qstr	TEXT;
+
+BEGIN
+	_qstr := 'INSERT INTO events.field_definitions (' ||
+			'evdef_id , efdef_id , efdef_optional , efdef_type , ' ||
+			'efdef_entity , efdef_integer , efdef_min , efdef_max ' ||
+		') VALUES ( $1 , $2 , ' || _typespec || ');';
+	BEGIN
+		EXECUTE _qstr USING _evdef_id , _efdef_id;
+	EXCEPTION
+		WHEN unique_violation THEN
+			RETURN 'DUPLICATE';
+		WHEN check_violation THEN
+			RETURN 'BAD_SPEC';
+	END;
+	RETURN 'OK';
+
+END;
+$evdef_create_field_record$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_create_field_record( TEXT , TEXT , TEXT )
+	FROM PUBLIC;
+
+
+/*
+ * Generate the DDL for a numeric field
+ * ------------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function generates the DDL for a numeric event field in an event queue
+ * table.
+ * 
+ * Parameters:
+ *		_fname		The field's table name
+ *		_opt		Whether the field is optional
+ *		_is_int		Whether the field is an integer
+ *		_min		Minimal value of the field (may be NULL)
+ *		_max		Maximal value of the field (may be NULL)
+ *
+ * Returns:
+ * 		???			The field's DDL
+ */
+DROP FUNCTION IF EXISTS events.efdef_ddl_numeric(
+		TEXT , BOOLEAN , BOOLEAN , NUMERIC , NUMERIC );
+CREATE FUNCTION events.efdef_ddl_numeric(
+		_fname	TEXT ,
+		_opt	BOOLEAN ,
+		_is_int	BOOLEAN ,
+		_min	NUMERIC ,
+		_max	NUMERIC )
+	RETURNS TEXT
+	LANGUAGE SQL
+	IMMUTABLE SECURITY INVOKER
+	CALLED ON NULL INPUT
+AS $efdef_ddl_numeric$
+	SELECT ',' || $1 || ' '
+		|| ( CASE WHEN $3 THEN 'BIGINT' ELSE 'DOUBLE PRECISION' END )
+		|| ( CASE WHEN $2 THEN '' ELSE ' NOT NULL' END )
+		|| ( CASE
+			WHEN $4 IS NULL AND $5 IS NULL
+				THEN ''
+			ELSE
+				' CHECK(' || $1 || ( CASE
+					WHEN $5 IS NULL
+						THEN '>=' || $4::TEXT
+					WHEN $4 IS NULL
+						THEN '<=' || $5::TEXT
+					ELSE
+						' BETWEEN ' || $4::TEXT || ' AND ' || $5::TEXT
+				END ) || ')'
+		END );
+$efdef_ddl_numeric$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.efdef_ddl_numeric(
+		TEXT , BOOLEAN , BOOLEAN , NUMERIC , NUMERIC )
+	FROM PUBLIC;
+
+
+
+/*
+ * Generate the DDL for a text field
+ * ---------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function generates the DDL for a text event field in an event queue
+ * table.
+ * 
+ * Parameters:
+ *		_fname		The field's table name
+ *		_opt		Whether the field is optional
+ *		_min		Minimal length of the field (may be NULL)
+ *		_max		Maximal length of the field (may be NULL)
+ *
+ * Returns:
+ * 		???			The field's DDL
+ */
+DROP FUNCTION IF EXISTS events.efdef_ddl_text(
+		TEXT , BOOLEAN , NUMERIC , NUMERIC );
+CREATE FUNCTION events.efdef_ddl_text(
+		_fname	TEXT ,
+		_opt	BOOLEAN ,
+		_min	NUMERIC ,
+		_max	NUMERIC )
+	RETURNS TEXT
+	LANGUAGE SQL
+	IMMUTABLE SECURITY INVOKER
+	CALLED ON NULL INPUT
+AS $efdef_ddl_text$
+	SELECT ',' || $1 || ' '
+		|| ( CASE WHEN $4 IS NULL THEN 'TEXT' ELSE 'VARCHAR(' || $4::TEXT || ')' END )
+		|| ( CASE WHEN $2 THEN '' ELSE ' NOT NULL' END )
+		|| ( CASE
+			WHEN $3 IS NULL
+				THEN ''
+			ELSE
+				' CHECK(LENGTH(' || $1 || ')>=' || $3::TEXT || ')'
+		END );
+$efdef_ddl_text$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.efdef_ddl_text(
+		TEXT , BOOLEAN , NUMERIC , NUMERIC )
+	FROM PUBLIC;
+
+
+
+/*
+ * Generate the DDL for an entity field
+ * ------------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function generates the DDL for an entity event field in an event queue
+ * table.
+ * 
+ * Parameters:
+ *		_fname		The field's table name
+ *		_opt		Whether the field is optional
+ *		_etype		Type of entity to reference
+ *
+ * Returns:
+ * 		???			The field's DDL
+ */
+DROP FUNCTION IF EXISTS events.efdef_ddl_entity(
+		TEXT , BOOLEAN , events.entity_field_type );
+CREATE FUNCTION events.efdef_ddl_entity(
+		_fname	TEXT ,
+		_opt	BOOLEAN ,
+		_etype	events.entity_field_type )
+	RETURNS TEXT
+	LANGUAGE SQL
+	STRICT IMMUTABLE SECURITY INVOKER
+AS $efdef_ddl_entity$
+	SELECT ',' || $1 || ' '
+		|| ( CASE 
+			WHEN $3 IN ( 'EMP' , 'PLN', 'ALL' , 'ADM' ) THEN 'INT'
+			ELSE 'BIGINT'
+		END ) || ( CASE WHEN $2 THEN ' NOT NULL' ELSE '' END )
+		|| ' REFERENCES ' || ( CASE $3
+			WHEN 'EMP' THEN 'emp.empires(name_id)'
+			WHEN 'PLN' THEN 'verse.planets(name_id)'
+			WHEN 'FLT' THEN 'fleets.fleets(id)'
+			WHEN 'ALL' THEN 'emp.alliances(id)'
+			WHEN 'BAT' THEN 'battles.battles(id)'
+			WHEN 'ADM' THEN 'admin.administrators(id)'
+			WHEN 'BUG' THEN 'bugs.initial_report_events(event_id)'
+		END ) || ' ON DELETE CASCADE';
+$efdef_ddl_entity$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.efdef_ddl_entity(
+		TEXT , BOOLEAN , events.entity_field_type )
+	FROM PUBLIC;
+
+
+	
+/*
+ * Create the event queuing table for a new definition
+ * ---------------------------------------------------
+ *
+ * /!\ INTERNAL FUNCTION /!\
+ *
+ * This function generates the event queuing table from the list of fields
+ * that compose an event definition. The queuing table's name will always be
+ * "events.evq_<type identifier>"; it will contain an event identifier
+ * generated from the event ID sequence (events.event_id_sequence), an empire
+ * identifier, a timestamp, the tick identifier, and rows corresponding to the
+ * event definition's fields.
+ * 
+ * Parameters:
+ *		_evdef_id		The event definition's identifier   
+ */
+DROP FUNCTION IF EXISTS events.evdef_create_queue_table( TEXT );
+CREATE FUNCTION _temp_init_func(_user TEXT)  RETURNS VOID LANGUAGE PLPGSQL AS $init_func$
+BEGIN EXECUTE $init_code$
+CREATE FUNCTION events.evdef_create_queue_table( _evdef_id TEXT )
+		RETURNS VOID
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY INVOKER
+	AS $evdef_create_queue_table$
+
+DECLARE
+	_rec	RECORD;
+	_qstr	TEXT;
+	_fname	TEXT;
+
+BEGIN
+	
+	_qstr := 'CREATE TABLE events.eq_' || replace( _evdef_id , '-' , '_' )
+		|| $tbl_def$ (
+			event_id BIGINT NOT NULL PRIMARY KEY
+					DEFAULT nextval('events.event_id_sequence'::regclass) ,
+			event_rtime TIMESTAMP WITHOUT TIME ZONE NOT NULL
+					DEFAULT now( ) ,
+			event_gtime BIGINT NOT NULL ,
+			empire_id INT NOT NULL REFERENCES emp.empires ( name_id )
+					ON DELETE CASCADE
+	$tbl_def$;
+	
+	FOR _rec IN SELECT *
+			FROM events.field_definitions
+			WHERE evdef_id = _evdef_id
+	LOOP
+		_fname := 'ef_' || replace( _rec.efdef_id , '-' , '_' );
+		_qstr := _qstr || ( CASE _rec.efdef_type
+				WHEN 'NUMERIC' THEN
+					events.efdef_ddl_numeric( _fname , _rec.efdef_optional ,
+						_rec.efdef_integer , _rec.efdef_min , _rec.efdef_max )
+				WHEN 'TEXT' THEN
+					events.efdef_ddl_text( _fname , _rec.efdef_optional ,
+						_rec.efdef_min , _rec.efdef_max )
+				WHEN 'BOOL' THEN
+					',' || _fname || ' BOOLEAN' || ( CASE
+						WHEN NOT _rec.efdef_optional THEN ' NOT NULL'
+						ELSE ''
+					END )
+				WHEN 'I18N' THEN
+					',' || _fname || ' VARCHAR(64)' || ( CASE
+						WHEN NOT _rec.efdef_optional THEN ' NOT NULL'
+						ELSE ''
+					END ) || ' REFERENCES defs.strings(name)'
+				WHEN 'ENTITY' THEN
+					events.efdef_ddl_entity( _fname , _rec.efdef_optional ,
+						_rec.efdef_entity )
+			END );
+	END LOOP;
+
+	_qstr := _qstr || ');';
+	EXECUTE _qstr;
+	
+	_qstr := 'GRANT INSERT ON TABLE events.eq_' || replace( _evdef_id , '-' , '_' )
+		|| ' TO $init_code$ || $1 || $init_code$;';
+	EXECUTE _qstr;
+END;
+$evdef_create_queue_table$; $init_code$ USING $1; END; $init_func$;
+SELECT _temp_init_func(:dbuser_string);
+DROP FUNCTION _temp_init_func(TEXT);
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_create_queue_table( TEXT )
+	FROM PUBLIC;
+
+
+
+/*
+ * Validate and finalise an event definition
+ * -----------------------------------------
+ * 
+ * This function inserts the contents of the temporary event definition table
+ * into the main table. If everything goes well, the event queuing table is
+ * created and the temporary table is destroyed.
+ * 
+ * Returns:
+ *		_error		An error code (see events.evdef_create_result)
+ *		_fld		The name of the field which caused the error, or NULL
+ *						if the error was table-related or if there was no
+ *						error.
+ */
+DROP FUNCTION IF EXISTS events.evdef_finalise( );
+CREATE FUNCTION events.evdef_finalise(
+			OUT _error events.evdef_create_result ,
+			OUT _fld TEXT )
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $evdef_finalise$
+
+DECLARE
+	_eid	TEXT;
+	_rec	RECORD;
+
+BEGIN
+	-- First create the event definition record
+	_fld := NULL;
+	_error := events.evdef_create_record( );
+	IF _error = 'OK'
+	THEN
+		-- Then create records for all fields
+		FOR _rec IN SELECT * FROM evdef_temp WHERE evfld_id IS NOT NULL
+		LOOP
+			_error := events.evdef_create_field_record(
+					_rec.evdef_id , _rec.evfld_id , _rec.evfld_typespec );
+
+			IF _error <> 'OK'
+			THEN
+				-- Destroy the definition record on failure
+				_fld := _rec.evfld_id;
+				DELETE FROM events.event_definitions
+					WHERE evdef_id = _rec.evdef_id;
+				EXIT;
+			END IF;
+
+		END LOOP;
+	END IF;
+
+	-- If everything went well so far, create the queueing table
+	IF _error = 'OK'
+	THEN
+		SELECT INTO _eid evdef_id FROM evdef_temp LIMIT 1;
+		PERFORM events.evdef_create_queue_table( _eid );
+	END IF;
+
+	DROP TABLE evdef_temp;
+END;
+$evdef_finalise$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evdef_finalise( )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evdef_finalise( )
+	TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
index db7be76..75fe701 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-events.sql
@@ -7,1004 +7,6 @@
 -- --------------------------------------------------------
 
 
-/*
- * Return values for event definition functions
- * ---------------------------------------------
- */
-DROP TYPE IF EXISTS events.evdef_create_result CASCADE;
-CREATE TYPE events.evdef_create_result AS ENUM(
-	/* The event type definition function succeeded. If it was the initial
-	 * creation function or a field creation function, this means the creation
-	 * can continue. If it was the finalisation function, this means the event
-	 * definition has been added. 
-	 */
-	'OK' ,
-	
-	/* A bad identifier was specified. This value is returned by both the
-	 * initial creation function and event field definition functions.
-	 */
-	'BAD_ID' ,
-	
-	/* An internationalised string (either an event type's name or template)
-	 * could not be found.
-	 */
-	'BAD_STRINGS' ,
-
-	/* Duplicate event or field identifier found. This value is returned by
-	 * the finalisation function, with its "_fld" set to NULL if the duplicate
-	 * identifier is the event's.
-	 */
-	'DUPLICATE' ,
-
-	/* Bad event or field specification found. This value is returned by the
-	 * finalisation function when an event or field cannot be inserted due to
-	 * check failures.
-	 */
-	'BAD_SPEC' ,
-	
-	/* This error code is returned when trying to finalise an event after one
-	 * of the definition calls failed. 
-	 */
-	'NO_DATA'
-);
-
-
-
-/*
- * Start defining a new event type
- * -------------------------------
- * 
- * This function creates a temporary table used to defgine a new type of
- * event. No checks beyond the length and contents of the event type's
- * identifier are made at this point.
- * 
- * Note: the temporary table will be created in all cases, even when the
- *		identifier is invalid.
- * 
- * Parameters:
- *		_id			The identifier of the event type
- *		_prio		The event type's default priority
- *		_adj		Whether the player may adjust the priority for this type
- *						of events.
- *		_i18n		The "base" of the I18N strings identifiers; the function
- *						will attempt to use "<_i18n>Name" as the name's
- *						identifier and "<_i18n>Template" as the template's.
- *
- * Returns:
- * 		???			An error code: OK or BAD_ID
- *						(see events.evdef_create_result)
- */
-DROP FUNCTION IF EXISTS events.evdef_start( TEXT , INT , BOOLEAN , TEXT );
-CREATE FUNCTION events.evdef_start(
-			_id		TEXT ,
-			_prio	INT ,
-			_adj	BOOLEAN ,
-			_i18n	TEXT )
-		RETURNS events.evdef_create_result
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $evdef_start$
-
-DECLARE
-	_name_id		INT;
-	_template_id	INT;
-
-BEGIN
-	CREATE TEMPORARY TABLE evdef_temp(
-		evdef_id		TEXT ,
-		evfld_id		TEXT ,
-		evfld_typespec	TEXT
-	) ON COMMIT DROP;
-	
-	IF LENGTH( _id ) < 2 OR LENGTH( _id ) > 48
-			OR NOT _id ~ '^[a-z\-]+$'
-	THEN
-		RETURN 'BAD_ID';
-	END IF;
-	
-	SELECT INTO _name_id id FROM defs.strings
-		WHERE name = _i18n || 'Name';
-	SELECT INTO _template_id id FROM defs.strings
-		WHERE name = _i18n || 'Template';
-	IF _name_id IS NULL OR _template_id IS NULL THEN
-		RETURN 'BAD_STRINGS';
-	END IF;
-
-	INSERT INTO evdef_temp( evdef_id , evfld_typespec )
-		VALUES( _id , _prio::TEXT || ',' || _adj::TEXT
-					|| ',' || _name_id || ',' || _template_id );
-	RETURN 'OK';
-END;
-$evdef_start$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN , TEXT )
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_start( TEXT , INT , BOOLEAN , TEXT )
-	TO :dbuser;
-
-
-
-/*
- * Create a new field
- * ------------------
- * 
- * /!\ INTERNAL FUNCTION /!\
- * 
- * This function adds data about a new field to the temporary event
- * definition table.
- * 
- * Parameters:
- *		_id			The field's identifier
- *		_opt		Whether the field is optional
- *		_type		The field's type
- *		_etype		The field's entity type (may be NULL)
- *		_is_int		Whether the field is an integer (may be NULL)
- *		_min_val	Minimal value/length (may be NULL)
- *		_max_val	Maximal value/length (may be NULL)
- *
- * Returns:
- * 		???			An error code: OK or BAD_ID
- *						(see events.evdef_create_result)
- */
-DROP FUNCTION IF EXISTS events.evdef_addfld_internal(
-	TEXT , BOOLEAN , events.field_type , events.entity_field_type ,
-	BOOLEAN , NUMERIC , NUMERIC ) CASCADE;
-CREATE FUNCTION events.evdef_addfld_internal(
-			_id			TEXT ,
-			_opt		BOOLEAN ,
-			_type		events.field_type ,
-			_etype		events.entity_field_type ,
-			_is_int		BOOLEAN ,
-			_min_val	NUMERIC ,
-			_max_val	NUMERIC
-		) RETURNS events.evdef_create_result
-		LANGUAGE PLPGSQL
-		CALLED ON NULL INPUT
-		VOLATILE SECURITY INVOKER
-	AS $evdef_addfld_internal$
-
-DECLARE
-	_iq_string	TEXT;
-
-BEGIN
-	IF LENGTH( _id ) < 2 OR LENGTH( _id ) > 48
-			OR NOT _id ~ '^[a-z\-]+$'
-	THEN
-		DELETE FROM evdef_temp;
-		RETURN 'BAD_ID'::events.evdef_create_result;
-	END IF;
-
-	_iq_string := _opt::TEXT || ' , ''' || _type::TEXT || ''' , ';
-	IF _etype IS NULL
-	THEN
-		_iq_string := _iq_string || 'NULL';
-	ELSE
-		_iq_string := _iq_string || '''' || _etype::TEXT || '''';
-	END IF;
-	_iq_string := _iq_string || ' , ';
-	IF _is_int IS NULL
-	THEN
-		_iq_string := _iq_string || 'NULL';
-	ELSE
-		_iq_string := _iq_string || _is_int::TEXT;
-	END IF;
-	_iq_string := _iq_string || ' , ';
-	IF _min_val IS NULL
-	THEN
-		_iq_string := _iq_string || 'NULL';
-	ELSE
-		_iq_string := _iq_string || _min_val::TEXT;
-	END IF;
-	_iq_string := _iq_string || ' , ';
-	IF _max_val IS NULL
-	THEN
-		_iq_string := _iq_string || 'NULL';
-	ELSE
-		_iq_string := _iq_string || _max_val::TEXT;
-	END IF;
-
-	INSERT INTO evdef_temp( evdef_id , evfld_id , evfld_typespec )
-		SELECT evdef_id , _id , _iq_string
-			FROM evdef_temp
-			WHERE evfld_id IS NULL;
-	RETURN 'OK'::events.evdef_create_result;
-END;
-$evdef_addfld_internal$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_internal(
-		TEXT , BOOLEAN , events.field_type , events.entity_field_type ,
-		BOOLEAN , NUMERIC , NUMERIC ) 
-	FROM PUBLIC;
-
-
-/*
- * Actual field creation functions
- * --------------------------------
- * 
- * Bunch of functions that create the field records in the temporary table
- * through events.evdef_addfld_internal().
- * 
- * Note:
- *	These functions are not dropped manually here, as it is assumed that
- *		dropping events.evdef_addfld_internal() will have cascaded.
- */
-
--- INTEGER FIELD, NO BOUNDS
-CREATE FUNCTION events.evdef_addfld_int( _id TEXT , _opt BOOLEAN )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,NULL,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_int( TEXT , BOOLEAN ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_int( TEXT , BOOLEAN ) 
-	TO :dbuser;
-
--- INTEGER FIELD W/ LOWER BOUND
-CREATE FUNCTION events.evdef_addfld_int_min( _id TEXT , _opt BOOLEAN , _min INT )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,$3::NUMERIC,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_int_min( TEXT , BOOLEAN , INT ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_int_min( TEXT , BOOLEAN , INT )
-	TO :dbuser;
-
--- INTEGER FIELD W/ HIGHER BOUND
-CREATE FUNCTION events.evdef_addfld_int_max( _id TEXT , _opt BOOLEAN , _max INT )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,NULL,$3::NUMERIC); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_int_max( TEXT , BOOLEAN , INT ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_int_max( TEXT , BOOLEAN , INT )
-	TO :dbuser;
-
--- INTEGER FIELD W/ LIMITED RANGE
-CREATE FUNCTION events.evdef_addfld_int_range( _id TEXT , _opt BOOLEAN , _min INT , _max INT )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'NUMERIC'::events.field_type,NULL,TRUE,$3::NUMERIC,$4::NUMERIC); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_int_range( TEXT , BOOLEAN , INT , INT ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_int_range( TEXT , BOOLEAN , INT , INT )
-	TO :dbuser;
-
--- REAL FIELD, NO BOUNDS
-CREATE FUNCTION events.evdef_addfld_real( _id TEXT , _opt BOOLEAN )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,NULL,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_real( TEXT , BOOLEAN ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_real( TEXT , BOOLEAN ) 
-	TO :dbuser;
-
--- REAL FIELD W/ LOWER BOUND
-CREATE FUNCTION events.evdef_addfld_real_min( _id TEXT , _opt BOOLEAN , _min DOUBLE PRECISION )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,$3::NUMERIC,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_real_min( TEXT , BOOLEAN , DOUBLE PRECISION ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_real_min( TEXT , BOOLEAN , DOUBLE PRECISION )
-	TO :dbuser;
-
--- REAL FIELD W/ HIGHER BOUND
-CREATE FUNCTION events.evdef_addfld_real_max( _id TEXT , _opt BOOLEAN , _max DOUBLE PRECISION )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,NULL,$3::NUMERIC); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_real_max( TEXT , BOOLEAN , DOUBLE PRECISION ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_real_max( TEXT , BOOLEAN , DOUBLE PRECISION )
-	TO :dbuser;
-
--- REAL FIELD W/ LIMITED RANGE
-CREATE FUNCTION events.evdef_addfld_real_range( _id TEXT , _opt BOOLEAN , _min DOUBLE PRECISION , _max DOUBLE PRECISION )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'NUMERIC'::events.field_type,NULL,FALSE,$3::NUMERIC,$4::NUMERIC); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_real_range( TEXT , BOOLEAN , DOUBLE PRECISION , DOUBLE PRECISION ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_real_range( TEXT , BOOLEAN , DOUBLE PRECISION , DOUBLE PRECISION )
-	TO :dbuser;
-
--- TEXT FIELD, NO BOUNDS
-CREATE FUNCTION events.evdef_addfld_text( _id TEXT , _opt BOOLEAN )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'TEXT'::events.field_type,NULL,NULL,NULL,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_text( TEXT , BOOLEAN ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_text( TEXT , BOOLEAN ) 
-	TO :dbuser;
-
--- TEXT FIELD W/ LOWER BOUND
-CREATE FUNCTION events.evdef_addfld_text_min( _id TEXT , _opt BOOLEAN , _min INT )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'TEXT'::events.field_type,NULL,NULL,$3::NUMERIC,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_text_min( TEXT , BOOLEAN , INT ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_text_min( TEXT , BOOLEAN , INT )
-	TO :dbuser;
-
--- TEXT FIELD W/ HIGHER BOUND
-CREATE FUNCTION events.evdef_addfld_text_max( _id TEXT , _opt BOOLEAN , _max INT )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'TEXT'::events.field_type,NULL,NULL,NULL,$3::NUMERIC); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_text_max( TEXT , BOOLEAN , INT ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_text_max( TEXT , BOOLEAN , INT )
-	TO :dbuser;
-
--- TEXT FIELD W/ LIMITED RANGE
-CREATE FUNCTION events.evdef_addfld_text_range( _id TEXT , _opt BOOLEAN , _min INT , _max INT )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'TEXT'::events.field_type,NULL,NULL,$3::NUMERIC,$4::NUMERIC); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_text_range( TEXT , BOOLEAN , INT , INT ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_text_range( TEXT , BOOLEAN , INT , INT )
-	TO :dbuser;
-
--- BOOLEAN FIELD
-CREATE FUNCTION events.evdef_addfld_bool( _id TEXT , _opt BOOLEAN )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'BOOL'::events.field_type,NULL,NULL,NULL,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_bool( TEXT , BOOLEAN ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_bool( TEXT , BOOLEAN ) 
-	TO :dbuser;
-
--- I18N TEXT FIELD
-CREATE FUNCTION events.evdef_addfld_i18n( _id TEXT , _opt BOOLEAN )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'I18N'::events.field_type,NULL,NULL,NULL,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_i18n( TEXT , BOOLEAN ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_i18n( TEXT , BOOLEAN ) 
-	TO :dbuser;
-
--- ENTITY LINK FIELD
-CREATE FUNCTION events.evdef_addfld_entity( _id TEXT , _opt BOOLEAN , _etype events.entity_field_type )
-		RETURNS events.evdef_create_result
-		LANGUAGE SQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $$ SELECT events.evdef_addfld_internal(
-			$1,$2,'ENTITY'::events.field_type,$3,NULL,NULL,NULL); $$;
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_addfld_entity( TEXT , BOOLEAN , events.entity_field_type ) 
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_addfld_entity( TEXT , BOOLEAN , events.entity_field_type ) 
-	TO :dbuser;
-
-
-/*
- * Create an event definition record
- * ---------------------------------
- * 
- * /!\ INTERNAL FUNCTION /!\
- * 
- * This function inserts the record for an event definition into the
- * appropriate table.
- * 
- * Returns:
- *		???		An error code
- */
-DROP FUNCTION IF EXISTS events.evdef_create_record( );
-CREATE FUNCTION events.evdef_create_record( )
-		RETURNS events.evdef_create_result
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE SECURITY INVOKER
-	AS $evdef_create_record$
-
-DECLARE
-	_rec	RECORD;
-	_qstr	TEXT;
-
-BEGIN
-	SELECT INTO _rec *
-		FROM evdef_temp
-		WHERE evfld_id IS NULL;
-	IF NOT FOUND
-	THEN
-		RETURN 'NO_DATA';
-	END IF;
-
-	_qstr := 'INSERT INTO events.event_definitions (' ||
-				'evdef_id , evdef_priority , evdef_adjustable , ' ||
-				'evdef_name_id , evdef_template_id' ||
-			') VALUES ( $1 , ' || _rec.evfld_typespec || ');';
-
-	BEGIN
-		EXECUTE _qstr USING _rec.evdef_id;
-	EXCEPTION
-		WHEN unique_violation THEN
-			RETURN 'DUPLICATE';
-		WHEN check_violation THEN
-			RETURN 'BAD_SPEC';
-	END;
-
-	RETURN 'OK';
-END;
-$evdef_create_record$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_create_record( )
-	FROM PUBLIC;
-
-
-/*
- * Create an event field definition record
- * ---------------------------------------
- * 
- * /!\ INTERNAL FUNCTION /!\
- * 
- * This function inserts a new event field definition record into the
- * appropriate table.
- *
- * Parameters:
- * 		_evdef_id	The event definition's identifier
- *		_efdef_id	The field's identifier
- *		_typespec	The type specification from the temporary table
- *
- * Returns:
- *		???			An error code
- */
-DROP FUNCTION IF EXISTS events.evdef_create_field_record(
-		TEXT , TEXT , TEXT );
-CREATE FUNCTION events.evdef_create_field_record(
-			_evdef_id	TEXT ,
-			_efdef_id	TEXT ,
-			_typespec	TEXT )
-		RETURNS events.evdef_create_result
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE SECURITY INVOKER
-	AS $evdef_create_field_record$
-
-DECLARE
-	_qstr	TEXT;
-
-BEGIN
-	_qstr := 'INSERT INTO events.field_definitions (' ||
-			'evdef_id , efdef_id , efdef_optional , efdef_type , ' ||
-			'efdef_entity , efdef_integer , efdef_min , efdef_max ' ||
-		') VALUES ( $1 , $2 , ' || _typespec || ');';
-	BEGIN
-		EXECUTE _qstr USING _evdef_id , _efdef_id;
-	EXCEPTION
-		WHEN unique_violation THEN
-			RETURN 'DUPLICATE';
-		WHEN check_violation THEN
-			RETURN 'BAD_SPEC';
-	END;
-	RETURN 'OK';
-
-END;
-$evdef_create_field_record$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_create_field_record( TEXT , TEXT , TEXT )
-	FROM PUBLIC;
-
-
-/*
- * Generate the DDL for a numeric field
- * ------------------------------------
- * 
- * /!\ INTERNAL FUNCTION /!\
- * 
- * This function generates the DDL for a numeric event field in an event queue
- * table.
- * 
- * Parameters:
- *		_fname		The field's table name
- *		_opt		Whether the field is optional
- *		_is_int		Whether the field is an integer
- *		_min		Minimal value of the field (may be NULL)
- *		_max		Maximal value of the field (may be NULL)
- *
- * Returns:
- * 		???			The field's DDL
- */
-DROP FUNCTION IF EXISTS events.efdef_ddl_numeric(
-		TEXT , BOOLEAN , BOOLEAN , NUMERIC , NUMERIC );
-CREATE FUNCTION events.efdef_ddl_numeric(
-		_fname	TEXT ,
-		_opt	BOOLEAN ,
-		_is_int	BOOLEAN ,
-		_min	NUMERIC ,
-		_max	NUMERIC )
-	RETURNS TEXT
-	LANGUAGE SQL
-	IMMUTABLE SECURITY INVOKER
-	CALLED ON NULL INPUT
-AS $efdef_ddl_numeric$
-	SELECT ',' || $1 || ' '
-		|| ( CASE WHEN $3 THEN 'BIGINT' ELSE 'DOUBLE PRECISION' END )
-		|| ( CASE WHEN $2 THEN '' ELSE ' NOT NULL' END )
-		|| ( CASE
-			WHEN $4 IS NULL AND $5 IS NULL
-				THEN ''
-			ELSE
-				' CHECK(' || $1 || ( CASE
-					WHEN $5 IS NULL
-						THEN '>=' || $4::TEXT
-					WHEN $4 IS NULL
-						THEN '<=' || $5::TEXT
-					ELSE
-						' BETWEEN ' || $4::TEXT || ' AND ' || $5::TEXT
-				END ) || ')'
-		END );
-$efdef_ddl_numeric$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.efdef_ddl_numeric(
-		TEXT , BOOLEAN , BOOLEAN , NUMERIC , NUMERIC )
-	FROM PUBLIC;
-
-
-
-/*
- * Generate the DDL for a text field
- * ---------------------------------
- * 
- * /!\ INTERNAL FUNCTION /!\
- * 
- * This function generates the DDL for a text event field in an event queue
- * table.
- * 
- * Parameters:
- *		_fname		The field's table name
- *		_opt		Whether the field is optional
- *		_min		Minimal length of the field (may be NULL)
- *		_max		Maximal length of the field (may be NULL)
- *
- * Returns:
- * 		???			The field's DDL
- */
-DROP FUNCTION IF EXISTS events.efdef_ddl_text(
-		TEXT , BOOLEAN , NUMERIC , NUMERIC );
-CREATE FUNCTION events.efdef_ddl_text(
-		_fname	TEXT ,
-		_opt	BOOLEAN ,
-		_min	NUMERIC ,
-		_max	NUMERIC )
-	RETURNS TEXT
-	LANGUAGE SQL
-	IMMUTABLE SECURITY INVOKER
-	CALLED ON NULL INPUT
-AS $efdef_ddl_text$
-	SELECT ',' || $1 || ' '
-		|| ( CASE WHEN $4 IS NULL THEN 'TEXT' ELSE 'VARCHAR(' || $4::TEXT || ')' END )
-		|| ( CASE WHEN $2 THEN '' ELSE ' NOT NULL' END )
-		|| ( CASE
-			WHEN $3 IS NULL
-				THEN ''
-			ELSE
-				' CHECK(LENGTH(' || $1 || ')>=' || $3::TEXT || ')'
-		END );
-$efdef_ddl_text$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.efdef_ddl_text(
-		TEXT , BOOLEAN , NUMERIC , NUMERIC )
-	FROM PUBLIC;
-
-
-
-/*
- * Generate the DDL for an entity field
- * ------------------------------------
- * 
- * /!\ INTERNAL FUNCTION /!\
- * 
- * This function generates the DDL for an entity event field in an event queue
- * table.
- * 
- * Parameters:
- *		_fname		The field's table name
- *		_opt		Whether the field is optional
- *		_etype		Type of entity to reference
- *
- * Returns:
- * 		???			The field's DDL
- */
-DROP FUNCTION IF EXISTS events.efdef_ddl_entity(
-		TEXT , BOOLEAN , events.entity_field_type );
-CREATE FUNCTION events.efdef_ddl_entity(
-		_fname	TEXT ,
-		_opt	BOOLEAN ,
-		_etype	events.entity_field_type )
-	RETURNS TEXT
-	LANGUAGE SQL
-	STRICT IMMUTABLE SECURITY INVOKER
-AS $efdef_ddl_entity$
-	SELECT ',' || $1 || ' '
-		|| ( CASE 
-			WHEN $3 IN ( 'EMP' , 'PLN', 'ALL' , 'ADM' ) THEN 'INT'
-			ELSE 'BIGINT'
-		END ) || ( CASE WHEN $2 THEN ' NOT NULL' ELSE '' END )
-		|| ' REFERENCES ' || ( CASE $3
-			WHEN 'EMP' THEN 'emp.empires(name_id)'
-			WHEN 'PLN' THEN 'verse.planets(name_id)'
-			WHEN 'FLT' THEN 'fleets.fleets(id)'
-			WHEN 'ALL' THEN 'emp.alliances(id)'
-			WHEN 'BAT' THEN 'battles.battles(id)'
-			WHEN 'ADM' THEN 'admin.administrators(id)'
-			WHEN 'BUG' THEN 'bugs.initial_report_events(event_id)'
-		END ) || ' ON DELETE CASCADE';
-$efdef_ddl_entity$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.efdef_ddl_entity(
-		TEXT , BOOLEAN , events.entity_field_type )
-	FROM PUBLIC;
-
-
-	
-/*
- * Create the event queuing table for a new definition
- * ---------------------------------------------------
- *
- * /!\ INTERNAL FUNCTION /!\
- *
- * This function generates the event queuing table from the list of fields
- * that compose an event definition. The queuing table's name will always be
- * "events.evq_<type identifier>"; it will contain an event identifier
- * generated from the event ID sequence (events.event_id_sequence), an empire
- * identifier, a timestamp, the tick identifier, and rows corresponding to the
- * event definition's fields.
- * 
- * Parameters:
- *		_evdef_id		The event definition's identifier   
- */
-DROP FUNCTION IF EXISTS events.evdef_create_queue_table( TEXT );
-CREATE FUNCTION _temp_init_func(_user TEXT)  RETURNS VOID LANGUAGE PLPGSQL AS $init_func$
-BEGIN EXECUTE $init_code$
-CREATE FUNCTION events.evdef_create_queue_table( _evdef_id TEXT )
-		RETURNS VOID
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE SECURITY INVOKER
-	AS $evdef_create_queue_table$
-
-DECLARE
-	_rec	RECORD;
-	_qstr	TEXT;
-	_fname	TEXT;
-
-BEGIN
-	
-	_qstr := 'CREATE TABLE events.eq_' || replace( _evdef_id , '-' , '_' )
-		|| $tbl_def$ (
-			event_id BIGINT NOT NULL PRIMARY KEY
-					DEFAULT nextval('events.event_id_sequence'::regclass) ,
-			event_rtime TIMESTAMP WITHOUT TIME ZONE NOT NULL
-					DEFAULT now( ) ,
-			event_gtime BIGINT NOT NULL ,
-			empire_id INT NOT NULL REFERENCES emp.empires ( name_id )
-					ON DELETE CASCADE
-	$tbl_def$;
-	
-	FOR _rec IN SELECT *
-			FROM events.field_definitions
-			WHERE evdef_id = _evdef_id
-	LOOP
-		_fname := 'ef_' || replace( _rec.efdef_id , '-' , '_' );
-		_qstr := _qstr || ( CASE _rec.efdef_type
-				WHEN 'NUMERIC' THEN
-					events.efdef_ddl_numeric( _fname , _rec.efdef_optional ,
-						_rec.efdef_integer , _rec.efdef_min , _rec.efdef_max )
-				WHEN 'TEXT' THEN
-					events.efdef_ddl_text( _fname , _rec.efdef_optional ,
-						_rec.efdef_min , _rec.efdef_max )
-				WHEN 'BOOL' THEN
-					',' || _fname || ' BOOLEAN' || ( CASE
-						WHEN NOT _rec.efdef_optional THEN ' NOT NULL'
-						ELSE ''
-					END )
-				WHEN 'I18N' THEN
-					',' || _fname || ' VARCHAR(64)' || ( CASE
-						WHEN NOT _rec.efdef_optional THEN ' NOT NULL'
-						ELSE ''
-					END ) || ' REFERENCES defs.strings(name)'
-				WHEN 'ENTITY' THEN
-					events.efdef_ddl_entity( _fname , _rec.efdef_optional ,
-						_rec.efdef_entity )
-			END );
-	END LOOP;
-
-	_qstr := _qstr || ');';
-	EXECUTE _qstr;
-	
-	_qstr := 'GRANT INSERT ON TABLE events.eq_' || replace( _evdef_id , '-' , '_' )
-		|| ' TO $init_code$ || $1 || $init_code$;';
-	EXECUTE _qstr;
-END;
-$evdef_create_queue_table$; $init_code$ USING $1; END; $init_func$;
-SELECT _temp_init_func(:dbuser_string);
-DROP FUNCTION _temp_init_func(TEXT);
-
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_create_queue_table( TEXT )
-	FROM PUBLIC;
-
-
-
-/*
- * Validate and finalise an event definition
- * -----------------------------------------
- * 
- * This function inserts the contents of the temporary event definition table
- * into the main table. If everything goes well, the event queuing table is
- * created and the temporary table is destroyed.
- * 
- * Returns:
- *		_error		An error code (see events.evdef_create_result)
- *		_fld		The name of the field which caused the error, or NULL
- *						if the error was table-related or if there was no
- *						error.
- */
-DROP FUNCTION IF EXISTS events.evdef_finalise( );
-CREATE FUNCTION events.evdef_finalise(
-			OUT _error events.evdef_create_result ,
-			OUT _fld TEXT )
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $evdef_finalise$
-
-DECLARE
-	_eid	TEXT;
-	_rec	RECORD;
-
-BEGIN
-	-- First create the event definition record
-	_fld := NULL;
-	_error := events.evdef_create_record( );
-	IF _error = 'OK'
-	THEN
-		-- Then create records for all fields
-		FOR _rec IN SELECT * FROM evdef_temp WHERE evfld_id IS NOT NULL
-		LOOP
-			_error := events.evdef_create_field_record(
-					_rec.evdef_id , _rec.evfld_id , _rec.evfld_typespec );
-
-			IF _error <> 'OK'
-			THEN
-				-- Destroy the definition record on failure
-				_fld := _rec.evfld_id;
-				DELETE FROM events.event_definitions
-					WHERE evdef_id = _rec.evdef_id;
-				EXIT;
-			END IF;
-
-		END LOOP;
-	END IF;
-
-	-- If everything went well so far, create the queueing table
-	IF _error = 'OK'
-	THEN
-		SELECT INTO _eid evdef_id FROM evdef_temp LIMIT 1;
-		PERFORM events.evdef_create_queue_table( _eid );
-	END IF;
-
-	DROP TABLE evdef_temp;
-END;
-$evdef_finalise$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.evdef_finalise( )
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evdef_finalise( )
-	TO :dbuser;
-
-
-
-/*
- * Set an event priority override
- * ------------------------------
- * 
- * Adds a new event priority override or modify its value. If the event type
- * does not exist, does not allow overrides or if the specified priority is
- * invalid, the error will be ignored silently.
- * 
- * Parameters:
- *		_empire_id		The identifier of the player's empire
- *		_evdef_id		The identifier of the event type
- *		_priority		The custom priority value
- */
-DROP FUNCTION IF EXISTS events.evcp_set( INT , TEXT , INT );
-CREATE FUNCTION events.evcp_set(
-			_empire_id	INT ,
-			_evdef_id	TEXT ,
-			_priority	INT )
-		RETURNS VOID
-		LANGUAGE PLPGSQL
-		STRICT VOLATILE SECURITY DEFINER
-	AS $evcp_set$
-
-DECLARE
-	_user_id	INT;
-
-BEGIN
-	BEGIN
-		INSERT INTO events.custom_priorities(
-				evdef_id , address_id , evcp_priority )
-			SELECT _evdef_id , owner_id , _priority
-				FROM naming.empire_names
-				WHERE id = _empire_id;
-		IF NOT FOUND
-		THEN
-			RAISE EXCEPTION 'missing empire';
-		END IF;
-	EXCEPTION
-		WHEN unique_violation THEN
-			UPDATE events.custom_priorities
-				SET evcp_priority = _priority
-				WHERE evdef_id = _evdef_id
-					AND address_id = (
-						SELECT owner_id
-							FROM naming.empire_names
-							WHERE id = _empire_id );
-	END;
-
-EXCEPTION
-	WHEN raise_exception THEN
-		PERFORM sys.write_sql_log( 'Events' , 'WARNING'::log_level ,
-			'Call to events.evcp_set() from missing empire #' || _empire_id );
-
-	WHEN foreign_key_violation OR check_violation THEN
-		PERFORM sys.write_sql_log( 'Events' , 'WARNING'::log_level ,
-			'Bad call to events.evcp_set() from empire #' || _empire_id );
-END;
-$evcp_set$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.evcp_set( INT , TEXT , INT )
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evcp_set( INT , TEXT , INT )
-	TO :dbuser;
-
-
-
-/*
- * Clear an event priority override
- * --------------------------------
- * 
- * Remove an event priority override from the custom priorities table. If
- * there was no override for the specified event type and empire, do nothing.
- * 
- * Parameters:
- *		_empire_id		The identifier of the player's empire
- *		_evdef_id		The identifier of the event type
- */
-DROP FUNCTION IF EXISTS events.evcp_clear( INT , TEXT );
-CREATE FUNCTION events.evcp_clear(
-		_empire_id	INT ,
-		_evdef_id	TEXT )
-	RETURNS VOID
-	LANGUAGE SQL
-	STRICT VOLATILE SECURITY DEFINER
-AS $evcp_clear$
-	DELETE FROM events.custom_priorities
-		WHERE evdef_id = $2
-			AND address_id = (
-				SELECT owner_id
-					FROM naming.empire_names
-					WHERE id = $1 )
-$evcp_clear$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.evcp_clear( INT , TEXT )
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evcp_clear( INT , TEXT )
-	TO :dbuser;
-
-
-
-/*
- * Clear all event priority overrides
- * ----------------------------------
- * 
- * Remove all event priority overrides set by some player.
- * 
- * Parameters:
- *		_empire_id		The identifier of the player's empire
- */
-DROP FUNCTION IF EXISTS events.evcp_clear( INT );
-CREATE FUNCTION events.evcp_clear( _empire_id INT )
-	RETURNS VOID
-	LANGUAGE SQL
-	STRICT VOLATILE SECURITY DEFINER
-AS $evcp_clear$
-	DELETE FROM events.custom_priorities
-		WHERE address_id = (
-				SELECT owner_id
-					FROM naming.empire_names
-					WHERE id = $1 )
-$evcp_clear$;
-
-REVOKE EXECUTE
-	ON FUNCTION events.evcp_clear( INT )
-	FROM PUBLIC;
-GRANT EXECUTE
-	ON FUNCTION events.evcp_clear( INT )
-	TO :dbuser;
-
-
-
-/*
- * OLD B6M1 CODE BELOW!
- */
-
-
 
 -- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
 -- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/171-event-priorities.sql b/legacyworlds-server-data/db-structure/parts/040-functions/171-event-priorities.sql
new file mode 100644
index 0000000..ca67917
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/171-event-priorities.sql
@@ -0,0 +1,143 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Functions to manipulate player-defined event priority
+-- overrides.
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+
+/*
+ * Set an event priority override
+ * ------------------------------
+ * 
+ * Adds a new event priority override or modify its value. If the event type
+ * does not exist, does not allow overrides or if the specified priority is
+ * invalid, the error will be ignored silently.
+ * 
+ * Parameters:
+ *		_empire_id		The identifier of the player's empire
+ *		_evdef_id		The identifier of the event type
+ *		_priority		The custom priority value
+ */
+DROP FUNCTION IF EXISTS events.evcp_set( INT , TEXT , INT );
+CREATE FUNCTION events.evcp_set(
+			_empire_id	INT ,
+			_evdef_id	TEXT ,
+			_priority	INT )
+		RETURNS VOID
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $evcp_set$
+
+DECLARE
+	_user_id	INT;
+
+BEGIN
+	BEGIN
+		INSERT INTO events.custom_priorities(
+				evdef_id , address_id , evcp_priority )
+			SELECT _evdef_id , owner_id , _priority
+				FROM naming.empire_names
+				WHERE id = _empire_id;
+		IF NOT FOUND
+		THEN
+			RAISE EXCEPTION 'missing empire';
+		END IF;
+	EXCEPTION
+		WHEN unique_violation THEN
+			UPDATE events.custom_priorities
+				SET evcp_priority = _priority
+				WHERE evdef_id = _evdef_id
+					AND address_id = (
+						SELECT owner_id
+							FROM naming.empire_names
+							WHERE id = _empire_id );
+	END;
+
+EXCEPTION
+	WHEN raise_exception THEN
+		PERFORM sys.write_sql_log( 'Events' , 'WARNING'::log_level ,
+			'Call to events.evcp_set() from missing empire #' || _empire_id );
+
+	WHEN foreign_key_violation OR check_violation THEN
+		PERFORM sys.write_sql_log( 'Events' , 'WARNING'::log_level ,
+			'Bad call to events.evcp_set() from empire #' || _empire_id );
+END;
+$evcp_set$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evcp_set( INT , TEXT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evcp_set( INT , TEXT , INT )
+	TO :dbuser;
+
+
+
+/*
+ * Clear an event priority override
+ * --------------------------------
+ * 
+ * Remove an event priority override from the custom priorities table. If
+ * there was no override for the specified event type and empire, do nothing.
+ * 
+ * Parameters:
+ *		_empire_id		The identifier of the player's empire
+ *		_evdef_id		The identifier of the event type
+ */
+DROP FUNCTION IF EXISTS events.evcp_clear( INT , TEXT );
+CREATE FUNCTION events.evcp_clear(
+		_empire_id	INT ,
+		_evdef_id	TEXT )
+	RETURNS VOID
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $evcp_clear$
+	DELETE FROM events.custom_priorities
+		WHERE evdef_id = $2
+			AND address_id = (
+				SELECT owner_id
+					FROM naming.empire_names
+					WHERE id = $1 )
+$evcp_clear$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evcp_clear( INT , TEXT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evcp_clear( INT , TEXT )
+	TO :dbuser;
+
+
+
+/*
+ * Clear all event priority overrides
+ * ----------------------------------
+ * 
+ * Remove all event priority overrides set by some player.
+ * 
+ * Parameters:
+ *		_empire_id		The identifier of the player's empire
+ */
+DROP FUNCTION IF EXISTS events.evcp_clear( INT );
+CREATE FUNCTION events.evcp_clear( _empire_id INT )
+	RETURNS VOID
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $evcp_clear$
+	DELETE FROM events.custom_priorities
+		WHERE address_id = (
+				SELECT owner_id
+					FROM naming.empire_names
+					WHERE id = $1 )
+$evcp_clear$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.evcp_clear( INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.evcp_clear( INT )
+	TO :dbuser;

From 3a0f5bbb783020b7e4330aa6266d0727acb7da4e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sun, 1 Jul 2012 14:12:22 +0200
Subject: [PATCH 90/94] Events storage procedure

Added the necessary database code to convert the contents of event
queues into actual event records. The changes include:
 * a new table, events.pending_events, which is automatically filled by
a trigger when events are inserted into queue tables,
 * the game.events.batchSize constant which defines the maximal amount
of events to process in a single transaction,
 * the events.eq_process() stored procedure, which processes the events.
In addition, the "hstore" extension was added as it is the easiest way
to convert events from the queues' table model to the store's
meta-model.
---
 .../lw/beans/sys/ConstantsRegistrarBean.java  |   2 +
 .../db-structure/parts/020-extensions.sql     |   5 +
 .../parts/030-data/170-events.sql             |  22 +++
 .../040-functions/170-event-definitions.sql   |  40 +++-
 .../parts/040-functions/172-event-storage.sql | 183 ++++++++++++++++++
 5 files changed, 248 insertions(+), 4 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/parts/040-functions/172-event-storage.sql

diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 5a0cf11..414ba46 100644
--- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -32,6 +32,8 @@ public class ConstantsRegistrarBean
 		// Misc. game-related values
 		cDesc = "Game updates - batch size.";
 		defs.add( new ConstantDefinition( "game.batchSize" , "Game (misc)" , cDesc , 20.0 , 1.0 , true ) );
+		cDesc = "Event processing - batch size.";
+		defs.add( new ConstantDefinition( "game.events.batchSize" , "Game (misc)" , cDesc , 100.0 , 1.0 , true ) );
 		cDesc = "Population growth factor.";
 		defs.add( new ConstantDefinition( "game.growthFactor" , "Game (misc)" , cDesc , 50.0 , 1.0 , true ) );
 		cDesc = "Increase to the population growth factor caused by reanimation centres.";
diff --git a/legacyworlds-server-data/db-structure/parts/020-extensions.sql b/legacyworlds-server-data/db-structure/parts/020-extensions.sql
index 9adc475..50db2a8 100644
--- a/legacyworlds-server-data/db-structure/parts/020-extensions.sql
+++ b/legacyworlds-server-data/db-structure/parts/020-extensions.sql
@@ -28,3 +28,8 @@ CREATE USER MAPPING FOR :dbuser
 	OPTIONS ( user :dbuser_string , password :dbupass );
 
 GRANT USAGE ON FOREIGN SERVER srv_logging TO :dbuser;
+
+/* The events sytem uses the hash store extension to convert events from the
+ * queues to main storage.
+ */
+CREATE EXTENSION hstore;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
index 36bd27f..356d9e2 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
@@ -239,6 +239,28 @@ CREATE SEQUENCE events.event_id_sequence;
 GRANT SELECT,UPDATE ON events.event_id_sequence TO :dbuser;
 
 
+/*
+ * Pending events
+ * --------------
+ *
+ * This table is updated when events are inserted into queues and when they
+ * are converted. It tracks events that need to be converted.
+ * 
+ * Note: the table does not have a primary key or foreign keys; it is simply
+ * 		a cache which must not get in the way of inserts into queues.
+ *
+ * Warning: a record in this table may be orphaned, i.e. there could be no
+ *		corresponding entry in the queue table.
+ */
+DROP TABLE IF EXISTS events.pending_events CASCADE;
+CREATE TABLE events.pending_events(
+	/* Event identifier */
+	event_id		BIGINT NOT NULL ,
+	/* Event type */
+	evdef_id		VARCHAR(48) NOT NULL
+);
+
+
 /*
  * Event storage table
  * -------------------
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql
index 564c971..f593da8 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql
@@ -707,6 +707,33 @@ REVOKE EXECUTE
 	FROM PUBLIC;
 
 
+/*
+ * Trigger used to insert entries into pending_events
+ * --------------------------------------------------
+ * 
+ * This function is used as a trigger to insert queued events into the
+ * events.pending_events table.
+ * 
+ * Parameters:
+ *		TG_ARGV[0]	Event type identifier
+ */
+DROP FUNCTION IF EXISTS events.tgf_eq_insert( );
+CREATE FUNCTION events.tgf_eq_insert(  )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $tgf_eq_insert$
+BEGIN
+	INSERT INTO events.pending_events( event_id , evdef_id )
+		VALUES ( NEW.event_id , TG_ARGV[0] );
+	RETURN NEW;
+END;
+$tgf_eq_insert$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.tgf_eq_insert( )
+	FROM PUBLIC;
+
 	
 /*
  * Create the event queuing table for a new definition
@@ -734,14 +761,14 @@ CREATE FUNCTION events.evdef_create_queue_table( _evdef_id TEXT )
 	AS $evdef_create_queue_table$
 
 DECLARE
+	_tsfx	TEXT;
 	_rec	RECORD;
 	_qstr	TEXT;
 	_fname	TEXT;
 
 BEGIN
-	
-	_qstr := 'CREATE TABLE events.eq_' || replace( _evdef_id , '-' , '_' )
-		|| $tbl_def$ (
+	_tsfx := replace( _evdef_id , '-' , '_' );
+	_qstr := 'CREATE TABLE events.eq_' || _tsfx || $tbl_def$ (
 			event_id BIGINT NOT NULL PRIMARY KEY
 					DEFAULT nextval('events.event_id_sequence'::regclass) ,
 			event_rtime TIMESTAMP WITHOUT TIME ZONE NOT NULL
@@ -782,9 +809,14 @@ BEGIN
 	_qstr := _qstr || ');';
 	EXECUTE _qstr;
 	
-	_qstr := 'GRANT INSERT ON TABLE events.eq_' || replace( _evdef_id , '-' , '_' )
+	_qstr := 'GRANT INSERT ON TABLE events.eq_' || _tsfx
 		|| ' TO $init_code$ || $1 || $init_code$;';
 	EXECUTE _qstr;
+	
+	_qstr := 'CREATE TRIGGER tg_eqi_' || _tsfx || ' AFTER INSERT ON events.eq_'
+		|| _tsfx || ' FOR EACH ROW EXECUTE PROCEDURE events.tgf_eq_insert('''
+		|| _evdef_id || ''');';
+	EXECUTE _qstr;
 END;
 $evdef_create_queue_table$; $init_code$ USING $1; END; $init_func$;
 SELECT _temp_init_func(:dbuser_string);
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/172-event-storage.sql b/legacyworlds-server-data/db-structure/parts/040-functions/172-event-storage.sql
new file mode 100644
index 0000000..ed351fa
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/172-event-storage.sql
@@ -0,0 +1,183 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Functions that extract event data from the queue tables
+-- and store them in the long term storage tables.
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Entity fields table
+ * -------------------
+ * 
+ * This table, which is filled when the database is created, associates entity
+ * types with fields in the events.field_values table.
+ */
+DROP TABLE IF EXISTS events._entity_fields;
+CREATE TABLE events._entity_fields(
+	efdef_entity	events.entity_field_type NOT NULL PRIMARY KEY ,
+	field_name		NAME NOT NULL ,
+	field_type		NAME NOT NULL
+);
+INSERT INTO events._entity_fields VALUES
+	( 'EMP' , 'empire_id' , 'INT' ) ,
+	( 'PLN' , 'planet_id' , 'INT' ) ,
+	( 'FLT' , 'fleet_id' , 'BIGINT' ) ,
+	( 'ALL' , 'alliance_id' , 'INT' ) ,
+	( 'BAT' , 'battle_id' , 'BIGINT' ) ,
+	( 'ADM' , 'admin_id' , 'INT' ) ,
+	( 'BUG' , 'bug_report_id' , 'BIGINT' );
+
+
+
+/*
+ * Process a pending event
+ * -----------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This procedure processes a single row from an event queue.
+ * 
+ * If the specified event identifier actually exists in the queue, it is
+ * converted and stored in the main events tables, then the queue entry is
+ * removed.
+ * 
+ * In all cases, the entry is deleted from events.pending_events.
+ * 
+ * Parameters:
+ *		_event_id	The event's identifier
+ *		_evdef_id	The event's type
+ *
+ * Returns:
+ *		???			TRUE 
+ */
+DROP FUNCTION IF EXISTS events.eq_process_event( BIGINT , TEXT );
+CREATE FUNCTION events.eq_process_event(
+			_event_id	BIGINT ,
+			_evdef_id	TEXT )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY INVOKER
+	AS $eq_process_event$
+
+DECLARE
+	_tbl	TEXT;
+	_qstr	TEXT;
+	_qentry	RECORD;
+	_nfound	INT;
+	_efdef	RECORD;
+
+BEGIN
+	
+	_tbl := 'events.eq_' || replace( _evdef_id , '-' , '_' );
+	_qstr := 'SELECT event_rtime , event_gtime , empire_id FROM '
+		|| _tbl || ' WHERE event_id = $1';
+	EXECUTE _qstr INTO _qentry USING _event_id;
+	GET DIAGNOSTICS _nfound = ROW_COUNT;
+
+	IF _nfound > 0
+	THEN
+	
+		INSERT INTO events.events_v2 (
+				event_id , evdef_id , event_rtime , event_gtime , empire_id
+			) VALUES (
+				_event_id , _evdef_id , _qentry.event_rtime ,
+				_qentry.event_gtime , _qentry.empire_id
+			);
+
+		_qstr := format( $field_acquisition$
+			SELECT efdef_id , field_name , field_type ,
+					( efdef_type = 'I18N') AS i18n , _sq1.value 
+				FROM events.field_definitions
+					INNER JOIN (
+						SELECT (each(hstore(_tbl))).*
+							FROM %s _tbl
+							WHERE _tbl.event_id = $1
+						) _sq1 ON _sq1.key = 'ef_' || replace(efdef_id , '-','_')
+					LEFT OUTER JOIN events._entity_fields
+						USING ( efdef_entity )
+				WHERE evdef_id = $2
+		$field_acquisition$ , _tbl );
+
+		FOR _efdef IN EXECUTE _qstr USING _event_id , _evdef_id
+		LOOP
+			_qstr := 'INSERT INTO events.field_values(event_id,evdef_id,efdef_id,efval_litteral'
+				|| ( CASE 
+					WHEN _efdef.field_name IS NOT NULL
+						THEN ',' || _efdef.field_name
+					WHEN _efdef.i18n
+						THEN ',string_id'
+					ELSE
+						''
+				END ) || ') ';
+
+			IF _efdef.i18n
+			THEN
+				_qstr := _qstr || $i18n_query$
+					SELECT $1,$2,$3,_str.id::TEXT||' '||_str.name,_str.id
+						FROM defs.strings _str
+						WHERE _str.name = $4
+					$i18n_query$;
+			ELSIF _efdef.field_name IS NOT NULL
+			THEN
+				_qstr := _qstr || format( 'VALUES($1,$2,$3,$4,$4::%s)' , _efdef.field_type );
+			ELSE
+				_qstr := _qstr || 'VALUES($1,$2,$3,$4)';
+			END IF;
+
+			EXECUTE _qstr USING _event_id , _evdef_id , _efdef.efdef_id , _efdef.value;
+		END LOOP;
+
+	END IF;
+
+	DELETE FROM events.pending_events
+		WHERE event_id = _event_id;
+	RETURN TRUE;
+
+END;
+$eq_process_event$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.eq_process_event( BIGINT , TEXT )
+	FROM PUBLIC;
+
+
+
+/*
+ * Process pending events
+ * ----------------------
+ * 
+ * This procedure processes events from the queues. Each time the procedure is
+ * called, it will process at most "game.events.batchSize" events.
+ *
+ * Returns:
+ *		???			TRUE if events were processed, FALSE if the queues are
+ *						empty
+ */
+DROP FUNCTION IF EXISTS events.eq_process( );
+CREATE FUNCTION events.eq_process( )
+		RETURNS BOOLEAN
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $eq_process$
+
+DECLARE
+	_limit	INT;
+
+BEGIN
+	_limit := sys.get_constant( 'game.events.batchSize' );
+	PERFORM events.eq_process_event( event_id , evdef_id )
+		FROM events.pending_events
+		LIMIT _limit;
+	RETURN FOUND;
+END;
+$eq_process$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.eq_process( )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.eq_process( )
+	TO :dbuser;
\ No newline at end of file

From af33d44b2aa852533dfe8695e2f20611b9c547de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sun, 1 Jul 2012 15:23:53 +0200
Subject: [PATCH 91/94] Package for event handling components

Added new Maven module for the server's event handling components.
---
 legacyworlds-server-beans-events/pom.xml          | 15 +++++++++++++++
 .../src/main/java/.empty                          |  0
 .../src/main/resources/.empty                     |  0
 .../src/test/java/.empty                          |  0
 .../src/test/resources/.empty                     |  0
 legacyworlds-server-beans/pom.xml                 |  1 +
 legacyworlds-server-main/pom.xml                  |  4 ++++
 legacyworlds-server-tests/pom.xml                 |  4 ++++
 legacyworlds/pom.xml                              |  5 +++++
 9 files changed, 29 insertions(+)
 create mode 100644 legacyworlds-server-beans-events/pom.xml
 create mode 100644 legacyworlds-server-beans-events/src/main/java/.empty
 create mode 100644 legacyworlds-server-beans-events/src/main/resources/.empty
 create mode 100644 legacyworlds-server-beans-events/src/test/java/.empty
 create mode 100644 legacyworlds-server-beans-events/src/test/resources/.empty

diff --git a/legacyworlds-server-beans-events/pom.xml b/legacyworlds-server-beans-events/pom.xml
new file mode 100644
index 0000000..de11442
--- /dev/null
+++ b/legacyworlds-server-beans-events/pom.xml
@@ -0,0 +1,15 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds-server-beans</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>1.0.0</version>
+		<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
+	</parent>
+
+	<artifactId>legacyworlds-server-beans-events</artifactId>
+	<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+	<name>Legacy Worlds - Server - Components - Events</name>
+	<description>This package defines the components used to process and handle game events.</description>
+</project>
\ No newline at end of file
diff --git a/legacyworlds-server-beans-events/src/main/java/.empty b/legacyworlds-server-beans-events/src/main/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-events/src/main/resources/.empty b/legacyworlds-server-beans-events/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-events/src/test/java/.empty b/legacyworlds-server-beans-events/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-events/src/test/resources/.empty b/legacyworlds-server-beans-events/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans/pom.xml b/legacyworlds-server-beans/pom.xml
index d2c3b59..59c6c30 100644
--- a/legacyworlds-server-beans/pom.xml
+++ b/legacyworlds-server-beans/pom.xml
@@ -24,6 +24,7 @@
 		<module>../legacyworlds-server-beans-accounts</module>
 		<module>../legacyworlds-server-beans-bt</module>
 		<module>../legacyworlds-server-beans-eventlog</module>
+		<module>../legacyworlds-server-beans-events</module>
 		<module>../legacyworlds-server-beans-i18n</module>
 		<module>../legacyworlds-server-beans-mailer</module>
 		<module>../legacyworlds-server-beans-naming</module>
diff --git a/legacyworlds-server-main/pom.xml b/legacyworlds-server-main/pom.xml
index 86b8a84..09fa63d 100644
--- a/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server-main/pom.xml
@@ -26,6 +26,10 @@
 			<artifactId>legacyworlds-server-beans-eventlog</artifactId>
 			<groupId>com.deepclone.lw</groupId>
 		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-beans-events</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-i18n</artifactId>
 			<groupId>com.deepclone.lw</groupId>
diff --git a/legacyworlds-server-tests/pom.xml b/legacyworlds-server-tests/pom.xml
index a77ecca..93d6a74 100644
--- a/legacyworlds-server-tests/pom.xml
+++ b/legacyworlds-server-tests/pom.xml
@@ -26,6 +26,10 @@
 			<artifactId>legacyworlds-server-beans-eventlog</artifactId>
 			<groupId>com.deepclone.lw</groupId>
 		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-beans-events</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-i18n</artifactId>
 			<groupId>com.deepclone.lw</groupId>
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index 606ac67..0cd04f8 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -138,6 +138,11 @@
 				<groupId>com.deepclone.lw</groupId>
 				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
 			</dependency>
+			<dependency>
+				<artifactId>legacyworlds-server-beans-events</artifactId>
+				<groupId>com.deepclone.lw</groupId>
+				<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
+			</dependency>
 			<dependency>
 				<artifactId>legacyworlds-server-beans-i18n</artifactId>
 				<groupId>com.deepclone.lw</groupId>

From d7b14e3de9c4649061de7ec1fe36904a5d21422a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Sun, 1 Jul 2012 15:48:37 +0200
Subject: [PATCH 92/94] Event processing task

Implemented the new ticker task which processes queued events.
---
 .../lw/beans/events/EventProcessorBean.java   | 113 ++++++++++++++++++
 .../lw/beans/events/EventProcessorTask.java   |  86 +++++++++++++
 .../resources/configuration/game/events.xml   |   9 ++
 .../src/main/resources/configuration/game.xml |   1 +
 4 files changed, 209 insertions(+)
 create mode 100644 legacyworlds-server-beans-events/src/main/java/com/deepclone/lw/beans/events/EventProcessorBean.java
 create mode 100644 legacyworlds-server-beans-events/src/main/java/com/deepclone/lw/beans/events/EventProcessorTask.java
 create mode 100644 legacyworlds-server-beans-events/src/main/resources/configuration/game/events.xml

diff --git a/legacyworlds-server-beans-events/src/main/java/com/deepclone/lw/beans/events/EventProcessorBean.java b/legacyworlds-server-beans-events/src/main/java/com/deepclone/lw/beans/events/EventProcessorBean.java
new file mode 100644
index 0000000..54a9154
--- /dev/null
+++ b/legacyworlds-server-beans-events/src/main/java/com/deepclone/lw/beans/events/EventProcessorBean.java
@@ -0,0 +1,113 @@
+package com.deepclone.lw.beans.events;
+
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.interfaces.sys.Ticker;
+import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
+
+
+
+/**
+ * Events processor component
+ * 
+ * <p>
+ * This component acquires the necessary dependencies to create an {@link EventProcessorTask}, does
+ * so, and registers it with the ticker. It clears the reference to the task when destroyed.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+class EventProcessorBean
+		implements InitializingBean , DisposableBean
+{
+
+	/** The ticker */
+	private Ticker ticker;
+
+	/** The Spring transaction template */
+	private TransactionTemplate tTemplate;
+
+	/** The database interface */
+	private DataSource dataSource;
+
+	/** The task */
+	private Runnable task;
+
+
+	/**
+	 * Dependency injector that sets the ticker component
+	 * 
+	 * @param ticker
+	 *            the ticker component
+	 */
+	@Autowired( required = true )
+	public void setTicker( Ticker ticker )
+	{
+		this.ticker = ticker;
+	}
+
+
+	/**
+	 * Dependency injector that sets the transaction manager
+	 * 
+	 * <p>
+	 * Creates the transaction template from the transaction manager
+	 * 
+	 * @param tManager
+	 *            the Spring transaction manager
+	 */
+	@Autowired( required = true )
+	public void setTransactionManager( PlatformTransactionManager tManager )
+	{
+		this.tTemplate = new TransactionTemplate( tManager );
+	}
+
+
+	/**
+	 * Dependency injector that sets the database interface
+	 * 
+	 * @param dataSource
+	 *            the database interface
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.dataSource = dataSource;
+	}
+
+
+	/**
+	 * Initialise and register the event processing task
+	 * 
+	 * <p>
+	 * When the component's dependencies have been injected, this method creates the
+	 * {@link EventProcessorTask} instance and registers it to the ticker.
+	 */
+	@Override
+	public void afterPropertiesSet( )
+	{
+		this.task = new EventProcessorTask( this.tTemplate , this.dataSource );
+		this.ticker.registerTask( Frequency.HIGH , "Events processor" , this.task );
+	}
+
+
+	/**
+	 * Clear the task reference
+	 * 
+	 * <p>
+	 * Remove the reference to the {@link EventProcessorTask} instance, allowing the ticker to clean
+	 * up.
+	 */
+	@Override
+	public void destroy( )
+	{
+		this.task = null;
+	}
+}
diff --git a/legacyworlds-server-beans-events/src/main/java/com/deepclone/lw/beans/events/EventProcessorTask.java b/legacyworlds-server-beans-events/src/main/java/com/deepclone/lw/beans/events/EventProcessorTask.java
new file mode 100644
index 0000000..4818331
--- /dev/null
+++ b/legacyworlds-server-beans-events/src/main/java/com/deepclone/lw/beans/events/EventProcessorTask.java
@@ -0,0 +1,86 @@
+package com.deepclone.lw.beans.events;
+
+
+import javax.sql.DataSource;
+
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Event processing task
+ * 
+ * <p>
+ * This class implements the task responsible for regular database event processing calls. It is
+ * registered as a high-frequency ticker task by {@link EventProcessorBean}.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+class EventProcessorTask
+		implements Runnable
+{
+
+	/** The Spring transaction template */
+	private final TransactionTemplate tTemplate;
+
+	/** The stored procedure that implements event processing */
+	private final StoredProc eqProcess;
+
+
+	/**
+	 * Initialise the transaction template and stored procedure interface
+	 * 
+	 * @param tTemplate
+	 *            the transaction template
+	 * @param dataSource
+	 *            the database interface
+	 */
+	public EventProcessorTask( TransactionTemplate tTemplate , DataSource dataSource )
+	{
+		this.tTemplate = tTemplate;
+		this.eqProcess = new StoredProc( dataSource , "events" , "eq_process" );
+		this.eqProcess.addOutput( "_result" , java.sql.Types.BOOLEAN );
+	}
+
+
+	/**
+	 * When the task executes, it will call <code>events.eq_process()</code> repeatedly until the
+	 * stored procedure returns <code>false</code>. Each call is made in a separate transaction.
+	 */
+	@Override
+	public void run( )
+	{
+		boolean hasMore;
+		do {
+			hasMore = this.processBatch( );
+		} while ( hasMore );
+	}
+
+
+	/**
+	 * Process a batch of events
+	 * 
+	 * <p>
+	 * This method spawns a transaction in which the <code>events.eq_process()</code> stored
+	 * procedure is called.
+	 * 
+	 * @return <code>true</code> if events were processed, <code>false</code> if the queues were
+	 *         empty.
+	 */
+	private boolean processBatch( )
+	{
+		return this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
+
+			@Override
+			public Boolean doInTransaction( TransactionStatus status )
+			{
+				return (Boolean) EventProcessorTask.this.eqProcess.execute( ).get( "_result" );
+			}
+
+		} );
+	}
+}
diff --git a/legacyworlds-server-beans-events/src/main/resources/configuration/game/events.xml b/legacyworlds-server-beans-events/src/main/resources/configuration/game/events.xml
new file mode 100644
index 0000000..bf70490
--- /dev/null
+++ b/legacyworlds-server-beans-events/src/main/resources/configuration/game/events.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+	<bean id="eventProcessor" class="com.deepclone.lw.beans.events.EventProcessorBean" />
+
+</beans>
\ No newline at end of file
diff --git a/legacyworlds-server-main/src/main/resources/configuration/game.xml b/legacyworlds-server-main/src/main/resources/configuration/game.xml
index 8a6c8d6..65d25e6 100644
--- a/legacyworlds-server-main/src/main/resources/configuration/game.xml
+++ b/legacyworlds-server-main/src/main/resources/configuration/game.xml
@@ -7,6 +7,7 @@
 	<!-- ========================================================== -->
 	<!-- Spring configuration loader for all "real" game components -->
 	<!-- ========================================================== -->
+	<import resource="game/events.xml" />
 	<import resource="game/resources.xml" />
 	<import resource="game/technologies.xml" />
 	<import resource="game/updates.xml" />

From d246f221f0d012d933c987362402119adb65d572 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 3 Jul 2012 10:32:15 +0200
Subject: [PATCH 93/94] Event database access

Added tables that store event access interfaces and event states per
interface, as well as functions which allow events to be retrieved:
 * events.interfaces lists access interfaces,
 * events.unprocessed_events lists events which haven't been processed
for each type of interface, with a "pre-processed" flag
 * events.ep_read() is a set of function variants which will read events
 * events.ep_access() is a set of function variants which read events
then update their states.
---
 .../parts/030-data/170-events.sql             |  60 ++
 .../parts/040-functions/173-event-access.sql  | 724 ++++++++++++++++++
 2 files changed, 784 insertions(+)
 create mode 100644 legacyworlds-server-data/db-structure/parts/040-functions/173-event-access.sql

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
index 356d9e2..77fc97c 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql
@@ -402,6 +402,66 @@ ALTER TABLE events.field_values
 GRANT SELECT ON events.field_values TO :dbuser;
 
 
+/*
+ * Event viewing interfaces
+ * ------------------------
+ * 
+ * This table stores the list of interfaces which can be used to access the
+ * events. It allows the system to handle multiple types of event viewing or
+ * processing: for example the web interface and email notifications.
+ */
+CREATE TABLE events.interfaces(
+
+	/* A short string that identifies the interface */
+	evi_id		VARCHAR(8) NOT NULL PRIMARY KEY ,
+
+	/* A description of the interface */
+	evi_descr	TEXT NOT NULL
+
+);
+
+/* Directly insert interface types */
+INSERT INTO events.interfaces( evi_id , evi_descr ) VALUES
+	( 'game' , 'The game''s main interface' ) ,
+	( 'mail' , 'The e-mail notifications system' );
+
+
+/*
+ * Events to process
+ * -----------------
+ * 
+ * This table stores the list of events which need to be processed for each
+ * interface.
+ */
+CREATE TABLE events.unprocessed_events(
+	/* The event's identifier */
+	event_id			BIGINT NOT NULL ,
+
+	/* The interface's identifier */
+	evi_id				VARCHAR(8) NOT NULL ,
+
+	/* Whether the interface has pre-processed the event, but not displayed
+	 * or sent it yet. This is used with e.g. the email notification system,
+	 * which waits for more events once a first event is ready. When the
+	 * server restarts, the field is set back to FALSE.
+	 */
+	upe_preprocessed	BOOLEAN NOT NULL
+							DEFAULT FALSE ,
+	
+	/* Use both the event and interface as the primary key */
+	PRIMARY KEY( event_id , evi_id )
+);
+
+CREATE INDEX idx_unprocessed_interface
+	ON events.unprocessed_events( evi_id );
+
+ALTER TABLE events.unprocessed_events
+	ADD CONSTRAINT fk_unprocessed_event
+		FOREIGN KEY ( event_id ) REFERENCES events.events_v2 ( event_id )
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_unprocessed_interface
+		FOREIGN KEY ( evi_id ) REFERENCES events.interfaces ( evi_id );
+
 
 /*
  * OLD B6M1 CODE BELOW!
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/173-event-access.sql b/legacyworlds-server-data/db-structure/parts/040-functions/173-event-access.sql
new file mode 100644
index 0000000..3d0f6ea
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/173-event-access.sql
@@ -0,0 +1,724 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Functions that read event data and update event
+-- states on behalf of event viewing / processing
+-- interfaces.
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * SOME GENERAL DOCUMENTATION
+ * --------------------------
+ * 
+ * This file provides a few functions the server can use to access the events:
+ * 
+ * The events.ep_read() function variants provide read-only access to the
+ * list. They may be used with or without a target (whether that target is an
+ * user or an empire), and they can be given an offset and a limit if partial
+ * selection is desired. ep_read() accepts the following parameters ("+"
+ * means that the parameter is required).
+ *
+ *		+ access interface identifier
+ *		  target identifier
+ *		  target is empire?
+ *		+ access processed events?
+ *		+ access preprocessed events?
+ *		+ access new events?
+ *		  offset of first event
+ *		  maximal amount of events
+ * 
+ * The events.ep_access() function variants provide the same service, but will
+ * additionally update the events' state after reading them. ep_access()
+ * uses the same parameters as ep_read(), but adds two new mandatory parameters
+ * at the end:
+ * 
+ *		+ mark preprocessed events as processed?
+ *		+ state to set for new events
+ */
+
+
+
+/*
+ * Insert new events in unprocessed list
+ * -------------------------------------
+ * 
+ * This trigger function is used to insert new events into the list of
+ * unprocessed events for all interface types.
+ */
+DROP FUNCTION IF EXISTS events.tgf_insert_unprocessed( ) CASCADE;
+CREATE FUNCTION events.tgf_insert_unprocessed( )
+		RETURNS TRIGGER
+		LANGUAGE PLPGSQL
+		STRICT VOLATILE SECURITY DEFINER
+	AS $tgf_insert_unprocessed$
+BEGIN
+	INSERT INTO events.unprocessed_events( event_id , evi_id )
+		SELECT NEW.event_id , evi_id
+			FROM events.interfaces;
+	RETURN NEW;
+END;
+$tgf_insert_unprocessed$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.tgf_insert_unprocessed( )
+	FROM PUBLIC;
+
+CREATE TRIGGER tg_insert_unprocessed
+	AFTER INSERT ON events.events_v2
+	FOR EACH ROW EXECUTE PROCEDURE events.tgf_insert_unprocessed( );
+
+
+/*
+ * Event processing states
+ * -----------------------
+ * 
+ * This type serves as an indication of event processing state.
+ * 
+ * Note: this type is not used for storage, only for access.
+ */
+DROP TYPE IF EXISTS events.processing_state CASCADE;
+CREATE TYPE events.processing_state AS ENUM(
+		/* The event is new and hasn't been processed yet */
+		'NEW' ,
+		
+		/* The event has been preprocessed */
+		'PRE' ,
+	
+		/* The event has been fully processed */
+		'DONE'
+	);
+
+
+/*
+ * Event list entry
+ * ----------------
+ * 
+ * This type corresponds to entries of the event lists returned by
+ * events.ep_list_events().
+ */
+DROP TYPE IF EXISTS events.ep_list_entry CASCADE;
+CREATE TYPE events.ep_list_entry AS (
+	/* The event's identifier */
+	event_id		BIGINT ,
+	
+	/* The event type */
+	evdef_id		TEXT ,
+
+	/* The user's identifier */
+	empire_id		INT ,
+	
+	/* The event's processing state */
+	event_state		events.processing_state
+);
+
+
+/*
+ * Build the events listing query
+ * ------------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This procedure generates the query string used to extract events for a given
+ * set of parameters.
+ * 
+ * Parameters:
+ *	_check_emp		TRUE if the query ought to check the events' target
+ *						empire, FALSE if all empires should be returned
+ *	_accept_old		Whether processed events should be selected
+ *	_accept_pre		Whether preprocessed events should be selected
+ *	_accept_new		Whether unprocessed events should be selected
+ *
+ * Returns:
+ * 	???				The query string
+ */
+DROP FUNCTION IF EXISTS events.ep_make_list_query(
+		BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN );
+CREATE FUNCTION events.ep_make_list_query(
+			_check_emp	BOOLEAN ,
+			_accept_old	BOOLEAN ,
+			_accept_pre	BOOLEAN ,
+			_accept_new BOOLEAN )
+		RETURNS TEXT
+		LANGUAGE PLPGSQL
+		STRICT IMMUTABLE SECURITY INVOKER
+	AS $ep_make_list_query$
+
+DECLARE
+	_qstr		TEXT;
+	_wstr		TEXT;
+	_need_or	BOOLEAN;
+
+BEGIN
+		IF NOT ( _accept_old OR _accept_pre OR _accept_new )
+	THEN
+		RAISE EXCEPTION 'No events accepted';
+	END IF;
+
+	_qstr := $sel_query$
+		SELECT _evt.event_id ,_evt.evdef_id , _evt.empire_id , ( CASE
+					WHEN _upe.upe_preprocessed IS NULL
+						THEN 'DONE'
+					WHEN _upe.upe_preprocessed
+						THEN 'PRE'
+					ELSE
+						'NEW'
+				END )::events.processing_state
+			FROM events.events_v2 _evt
+				LEFT OUTER JOIN events.unprocessed_events _upe
+					ON _upe.event_id = _evt.event_id AND _upe.evi_id = $1
+	$sel_query$;
+	
+	IF _check_emp THEN
+		_wstr := '_evt.empire_id = $2';
+	ELSE
+		_wstr := '';
+	END IF;
+
+	IF NOT ( _accept_old AND _accept_pre AND _accept_new )
+	THEN
+		IF _wstr <> ''
+		THEN
+			_wstr := _wstr || ' AND ';
+		END IF;
+		_wstr := _wstr || '(';
+
+		_need_or := FALSE;
+		IF _accept_old THEN
+			_wstr := _wstr || '_upe.event_id IS NULL';
+			_need_or := TRUE;
+		END IF;
+		
+		IF _accept_pre THEN
+			IF _need_or THEN
+				_wstr := _wstr || ' OR ';
+			ELSE
+				_need_or := TRUE;
+			END IF;
+			_wstr := _wstr || '_upe.upe_preprocessed';
+		END IF;
+		
+		IF _accept_new THEN
+			IF _need_or THEN
+				_wstr := _wstr || ' OR ';
+			END IF;
+			_wstr := _wstr || 'NOT _upe.upe_preprocessed';
+		END IF;
+		
+		_wstr := _wstr || ')';
+	END IF;
+
+	IF _wstr <> ''
+	THEN
+		_qstr := _qstr || ' WHERE ' || _wstr;
+	END IF;
+	
+	_qstr := _qstr || ' ORDER BY _evt.event_id DESC';
+	RETURN _qstr;
+END;
+$ep_make_list_query$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_make_list_query(
+		BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN )
+	FROM PUBLIC;
+
+
+
+/*
+ * List events
+ * -----------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This procedure generates a list of events for some type of interface.
+ * 
+ * Parameters:
+ *		_evi_id		Identifier of the interface accessing events
+ *		_empire_id	The identifier of the empire to get events for (may be NULL)
+ *		_accept_old	Whether processed events should be selected
+ *		_accept_pre	Whether preprocessed events should be selected
+ *		_accept_new	Whether unprocessed events should be selected
+ *
+ * Returns:
+ * 		the set of events
+ */
+DROP FUNCTION IF EXISTS events.ep_list_events(
+	TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN );
+CREATE FUNCTION events.ep_list_events(
+			_evi_id			TEXT ,
+			_empire_id		INT ,
+			_accept_old		BOOLEAN ,
+			_accept_pre		BOOLEAN ,
+			_accept_new		BOOLEAN )
+		RETURNS SETOF events.ep_list_entry
+		LANGUAGE PLPGSQL
+		CALLED ON NULL INPUT
+		STABLE SECURITY INVOKER
+	AS $ep_list_events$
+
+DECLARE
+	_record	events.ep_list_entry;
+
+BEGIN
+	FOR _record IN EXECUTE events.ep_make_list_query(
+				_empire_id IS NOT NULL ,
+				_accept_old , _accept_pre , _accept_new )
+			USING _evi_id , _empire_id
+	LOOP
+		RETURN NEXT _record;
+	END LOOP;
+END;
+$ep_list_events$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_list_events(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN )
+	FROM PUBLIC;
+
+
+
+DROP TYPE IF EXISTS events.event_list_type CASCADE;
+CREATE TYPE events.event_list_type AS (
+	/* Identifier of the empire to which the event applies */
+	empire_id		INT ,
+	
+	/* Identifier of the user who controls the empire */
+	user_id			INT ,
+
+	/* Selected language for the empire */
+	lang_id			TEXT ,
+
+	/* Numeric identifier of the event */
+	event_id		BIGINT ,
+	
+	/* The event's processing state */
+	event_state		events.processing_state ,
+
+	/* Real time at which the event occurred */
+	event_rtime		TIMESTAMP WITHOUT TIME ZONE ,
+
+	/* Game time (tick identifier) at which the event occurred */
+	event_gtime		BIGINT ,
+
+	/* Event priority from either the definition or the custom priorities */
+	event_priority	INT ,
+	
+	/* Type of the event */
+	evdef_id		TEXT ,
+
+	/* Internationalised string that contains the name of the event type;
+	 * used when displaying priority settings.
+	 */
+	evdef_name		TEXT ,
+	
+	/* Internationalised string that contains the template to use when
+	 * generating the output for a single event.
+	 */
+	evdef_template	TEXT ,
+	
+	/* Field name */
+	efdef_id		TEXT ,
+
+	/* Field value */
+	field_value		TEXT
+);
+
+
+
+/*
+ * Main events listing function
+ * ----------------------------
+ * 
+ * /!\ INTERNAL FUNCTION /!\
+ * 
+ * This function contains the main code for event access. It is capable of
+ * selecting events based on their target and/or state, can limit the amount
+ * of events fetched or fetch them starting from some offset, and is capable
+ * of updating event states for selected events.
+ * 
+ * Parameters:
+ *	_evi_id			The identifier of the interface accessing the events list
+ *	_target_id		An empire or user identifier (may be NULL)
+ *	_target_is_emp	Whether the specified identifier is an empire (TRUE) or an
+ *						user. May be NULL.
+ *	_accept_old		Whether processed events should be selected
+ *	_accept_pre		Whether preprocessed events should be selected
+ *	_accept_new		Whether unprocessed events should be selected
+ *	_offset			Offset of the first event to obtain in the list. May be
+ *						NULL.
+ *	_limit			Maximal amount of events to list. May be NULL.
+ *	_clear_pre		If TRUE, selected events that had been preprocessed will
+ *						be marked as fully processed. May be NULL.
+ *	_change_new		The state to which new events should be set. May be NULL
+ *						or 'NEW' if no changes are to be made.
+ *
+ * Returns:
+ * 	A set of events.event_list_type records
+ */
+DROP FUNCTION IF EXISTS events.ep_get_events_internal(
+	TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT , BOOLEAN ,
+	events.processing_state ) CASCADE;
+CREATE FUNCTION events.ep_get_events_internal(
+			_evi_id			TEXT ,
+			_target_id		INT ,
+			_target_is_emp	BOOLEAN ,
+			_accept_old		BOOLEAN ,
+			_accept_pre		BOOLEAN ,
+			_accept_new		BOOLEAN ,
+			_offset			INT ,
+			_limit			INT ,
+			_clear_pre		BOOLEAN ,
+			_change_new		events.processing_state )
+		RETURNS SETOF events.event_list_type
+		LANGUAGE PLPGSQL
+		CALLED ON NULL INPUT
+		VOLATILE SECURITY INVOKER
+	AS $ep_get_events_internal$
+
+DECLARE
+	_sel_query	TEXT;
+	_empire_id	INT;
+	_record		events.event_list_type;
+
+BEGIN
+	-- Get empire identifier if necessary
+	IF NOT _target_is_emp
+	THEN
+		SELECT INTO _empire_id _emp.name_id
+			FROM emp.empires _emp
+				INNER JOIN naming.empire_names _emp_name
+					ON _emp.name_id = _emp_name.id
+			WHERE _emp_name.owner_id = _target_id;
+		IF NOT FOUND
+		THEN
+			RETURN;
+		END IF;
+	ELSE
+		_empire_id := _target_id;
+	END IF;
+
+	-- Generate event selection query
+	_sel_query := $sel_query$
+		CREATE TEMPORARY TABLE ep_selected
+			ON COMMIT DROP
+			AS SELECT * FROM events.ep_list_events(
+					$1 , $2 , $3 , $4 , $5 )
+	$sel_query$;
+	IF _offset >= 0
+	THEN
+		_sel_query := _sel_query || ' OFFSET ' || _offset;
+	END IF;
+	IF _limit > 0
+	THEN
+		_sel_query := _sel_query || ' LIMIT ' || _limit;
+	END IF;
+
+	-- Select events
+	EXECUTE _sel_query
+		USING _evi_id , _empire_id , _accept_old , _accept_pre , _accept_new;
+
+	-- Update event states if necessary
+	IF _clear_pre THEN
+		DELETE FROM events.unprocessed_events
+			WHERE event_id IN ( SELECT event_id FROM ep_selected )
+				AND upe_preprocessed
+				AND evi_id = _evi_id;
+	END IF;
+	IF _change_new = 'DONE'
+	THEN
+		DELETE FROM events.unprocessed_events
+			WHERE event_id IN ( SELECT event_id FROM ep_selected )
+				AND NOT upe_preprocessed
+				AND evi_id = _evi_id;
+	ELSIF _change_new = 'PRE'
+	THEN
+		UPDATE events.unprocessed_events _upe
+			SET upe_preprocessed = TRUE
+			FROM ep_selected _sel
+			WHERE _sel.event_id = _upe.event_id
+				AND NOT _upe.upe_preprocessed
+				AND _upe.evi_id = _evi_id;
+	END IF;
+
+	-- Select and return event data
+	FOR _record IN
+		SELECT _sel.empire_id , _emp_name.owner_id AS user_id ,
+				_lg.language AS lang_id , _sel.event_id ,
+				_sel.event_state , _ev.event_rtime , _ev.event_gtime ,
+				( CASE
+					WHEN _cp.evcp_priority IS NULL
+						THEN _evdef.evdef_priority
+					ELSE
+						_cp.evcp_priority
+				END ) AS event_priority ,
+				_evdef.evdef_id , _edns.name AS evdef_name ,
+				_edts.name AS evdef_template , _fval.efdef_id ,
+				_fval.efval_litteral AS field_value
+			FROM ep_selected _sel
+				INNER JOIN events.events_v2 _ev
+					USING ( event_id , evdef_id )
+				INNER JOIN events.event_definitions _evdef
+					USING ( evdef_id )
+				INNER JOIN naming.empire_names _emp_name
+					ON _emp_name.id = _sel.empire_id
+				INNER JOIN users.credentials _user
+					ON _user.address_id = _emp_name.owner_id
+				INNER JOIN defs.languages _lg
+					ON _lg.id = _user.language_id
+				INNER JOIN defs.strings _edns
+					ON _edns.id = _evdef.evdef_name_id
+				INNER JOIN defs.strings _edts
+					ON _edts.id = _evdef.evdef_template_id
+				LEFT OUTER JOIN events.field_values _fval
+					USING ( event_id , evdef_id )
+				LEFT OUTER JOIN events.custom_priorities _cp
+					USING ( evdef_id , evdef_adjustable , address_id )
+	LOOP
+		RETURN NEXT _record;
+	END LOOP;
+	
+	DROP TABLE ep_selected;
+END;
+$ep_get_events_internal$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_get_events_internal(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT ,
+		BOOLEAN , events.processing_state )
+	FROM PUBLIC;
+
+
+
+/*
+ * READ-ONLY EVENT ACCESS WRAPPERS 
+ */
+DROP FUNCTION IF EXISTS events.ep_read( TEXT, BOOLEAN , BOOLEAN , BOOLEAN );
+CREATE FUNCTION events.ep_read(
+		_evi_id			TEXT ,
+		_accept_old		BOOLEAN ,
+		_accept_pre		BOOLEAN ,
+		_accept_new		BOOLEAN )
+	RETURNS SETOF events.event_list_type
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $ep_read$
+	SELECT * FROM events.ep_get_events_internal( $1 , NULL , NULL , $2 , $3 ,
+		$4 , NULL , NULL , NULL , NULL );
+$ep_read$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_read( TEXT , BOOLEAN , BOOLEAN , BOOLEAN )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.ep_read( TEXT , BOOLEAN , BOOLEAN , BOOLEAN )
+	TO :dbuser;
+
+DROP FUNCTION IF EXISTS events.ep_read(
+	TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN );
+CREATE FUNCTION events.ep_read(
+		_evi_id			TEXT ,
+		_target_id		INT ,
+		_target_is_emp	BOOLEAN ,
+		_accept_old		BOOLEAN ,
+		_accept_pre		BOOLEAN ,
+		_accept_new		BOOLEAN )
+	RETURNS SETOF events.event_list_type
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $ep_read$
+	SELECT * FROM events.ep_get_events_internal( $1 , $2 , $3 , $4 , $5 , $6 ,
+		NULL , NULL , NULL , NULL );
+$ep_read$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_read(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.ep_read(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN )
+	TO :dbuser;
+
+DROP FUNCTION IF EXISTS events.ep_read(
+	TEXT, BOOLEAN , BOOLEAN , BOOLEAN , INT , INT );
+CREATE FUNCTION events.ep_read(
+		_evi_id			TEXT ,
+		_accept_old		BOOLEAN ,
+		_accept_pre		BOOLEAN ,
+		_accept_new		BOOLEAN ,
+		_offset			INT ,
+		_limit			INT )
+	RETURNS SETOF events.event_list_type
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $ep_read$
+	SELECT * FROM events.ep_get_events_internal( $1 , NULL , NULL , $2 , $3 ,
+		$4 , $5 , $6 , NULL , NULL );
+$ep_read$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_read(
+		TEXT , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.ep_read(
+		TEXT , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT )
+	TO :dbuser;
+
+DROP FUNCTION IF EXISTS events.ep_read(
+	TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT );
+CREATE FUNCTION events.ep_read(
+		_evi_id			TEXT ,
+		_target_id		INT ,
+		_target_is_emp	BOOLEAN ,
+		_accept_old		BOOLEAN ,
+		_accept_pre		BOOLEAN ,
+		_accept_new		BOOLEAN ,
+		_offset			INT ,
+		_limit			INT )
+	RETURNS SETOF events.event_list_type
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $ep_read$
+	SELECT * FROM events.ep_get_events_internal( $1 , $2 , $3 , $4 , $5 , $6 ,
+		$7 , $8 , NULL , NULL );
+$ep_read$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_read(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.ep_read(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT )
+	TO :dbuser;
+
+
+/*
+ * EVENT ACCESS WRAPPERS WITH UPDATE CAPABILITIES 
+ */
+DROP FUNCTION IF EXISTS events.ep_access(
+	TEXT, BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , events.processing_state );
+CREATE FUNCTION events.ep_access(
+		_evi_id			TEXT ,
+		_accept_old		BOOLEAN ,
+		_accept_pre		BOOLEAN ,
+		_accept_new		BOOLEAN ,
+		_clear_pre		BOOLEAN ,
+		_change_new		events.processing_state )
+	RETURNS SETOF events.event_list_type
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $ep_access$
+	SELECT * FROM events.ep_get_events_internal( $1 , NULL , NULL , $2 , $3 ,
+		$4 , NULL , NULL , $5 , $6 );
+$ep_access$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_access(
+		TEXT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN ,
+		events.processing_state )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.ep_access(
+		TEXT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN ,
+		events.processing_state )
+	TO :dbuser;
+
+DROP FUNCTION IF EXISTS events.ep_access(
+	TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN ,
+	events.processing_state );
+CREATE FUNCTION events.ep_access(
+		_evi_id			TEXT ,
+		_target_id		INT ,
+		_target_is_emp	BOOLEAN ,
+		_accept_old		BOOLEAN ,
+		_accept_pre		BOOLEAN ,
+		_accept_new		BOOLEAN ,
+		_clear_pre		BOOLEAN ,
+		_change_new		events.processing_state )
+	RETURNS SETOF events.event_list_type
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $ep_access$
+	SELECT * FROM events.ep_get_events_internal( $1 , $2 , $3 , $4 , $5 , $6 ,
+		NULL , NULL , $7 , $8 );
+$ep_access$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_access(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN ,
+		BOOLEAN , events.processing_state )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.ep_access(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN ,
+		BOOLEAN , events.processing_state )
+	TO :dbuser;
+
+DROP FUNCTION IF EXISTS events.ep_access(
+	TEXT, BOOLEAN , BOOLEAN , BOOLEAN , INT , INT , BOOLEAN ,
+	events.processing_state );
+CREATE FUNCTION events.ep_access(
+		_evi_id			TEXT ,
+		_accept_old		BOOLEAN ,
+		_accept_pre		BOOLEAN ,
+		_accept_new		BOOLEAN ,
+		_offset			INT ,
+		_limit			INT ,
+		_clear_pre		BOOLEAN ,
+		_change_new		events.processing_state )
+	RETURNS SETOF events.event_list_type
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $ep_access$
+	SELECT * FROM events.ep_get_events_internal( $1 , NULL , NULL , $2 , $3 ,
+		$4 , $5 , $6 , $7 , $8 );
+$ep_access$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_access(
+		TEXT , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT , BOOLEAN ,
+		events.processing_state )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.ep_access(
+		TEXT , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT , BOOLEAN ,
+		events.processing_state )
+	TO :dbuser;
+
+DROP FUNCTION IF EXISTS events.ep_access(
+	TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT , BOOLEAN ,
+	events.processing_state );
+CREATE FUNCTION events.ep_access(
+		_evi_id			TEXT ,
+		_target_id		INT ,
+		_target_is_emp	BOOLEAN ,
+		_accept_old		BOOLEAN ,
+		_accept_pre		BOOLEAN ,
+		_accept_new		BOOLEAN ,
+		_offset			INT ,
+		_limit			INT ,
+		_clear_pre		BOOLEAN ,
+		_change_new		events.processing_state )
+	RETURNS SETOF events.event_list_type
+	LANGUAGE SQL
+	STRICT VOLATILE SECURITY DEFINER
+AS $ep_access$
+	SELECT * FROM events.ep_get_events_internal( $1 , $2 , $3 , $4 , $5 , $6 ,
+		$7 , $8 , $9 , $10 );
+$ep_access$;
+
+REVOKE EXECUTE
+	ON FUNCTION events.ep_access(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT,
+		BOOLEAN , events.processing_state )
+	FROM PUBLIC;
+GRANT EXECUTE
+	ON FUNCTION events.ep_access(
+		TEXT , INT , BOOLEAN , BOOLEAN , BOOLEAN , BOOLEAN , INT , INT ,
+		BOOLEAN , events.processing_state )
+	TO :dbuser;

From ff53af6668039b6397c8640e67afaa110dfbf6bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@nocternity.net>
Date: Tue, 23 Oct 2018 09:43:42 +0200
Subject: [PATCH 94/94] Importing SVN archives - Trunk

---
 .../pom.xml                                   |   4 +-
 .../legacyworlds-server-beans-bt/pom.xml      |   4 +-
 .../lw/beans/bt/EmpireSummaryBean.java        |  27 +-
 .../lw/beans/bt/esdata/DebugInformation.java  |  27 +-
 .../beans/bt/esdata/ResearchInformation.java  |  64 ++-
 .../bt/esdata/TechnologyInformation.java      |  48 +++
 .../pom.xml                                   |   4 +-
 .../.classpath                                |  10 +
 .../legacyworlds-server-beans-events/.project |  23 ++
 .../.settings/org.eclipse.jdt.core.prefs      |   6 +
 .../.settings/org.maven.ide.eclipse.prefs     |   9 +
 .../legacyworlds-server-beans-events/pom.xml  |  13 +
 .../legacyworlds-server-beans-i18n/pom.xml    |   4 +-
 .../lw/beans/i18n/TranslatorBean.java         |  79 ++++
 .../legacyworlds-server-beans-mailer/pom.xml  |   4 +-
 .../legacyworlds-server-beans-naming/pom.xml  |   4 +-
 .../legacyworlds-server-beans-simple/pom.xml  |   4 +-
 .../lw/beans/empire/EmpireDAOBean.java        | 105 ++---
 .../lw/beans/empire/EmpireManagementBean.java |  56 ++-
 .../lw/beans/updates/GameUpdateBean.java      | 138 -------
 .../lw/beans/updates/UpdatesDAOBean.java      |  43 --
 .../resources/configuration/simple-beans.xml  |   2 -
 .../legacyworlds-server-beans-system/pom.xml  |   4 +-
 .../deepclone/lw/beans/sys/ConstantsData.java |  20 +-
 .../lw/beans/sys/ConstantsManagerBean.java    |   9 +-
 .../lw/beans/sys/ConstantsRegistrarBean.java  |  43 +-
 .../lw/beans/sys/SystemStatusBean.java        |  84 ++--
 .../deepclone/lw/beans/sys/TickerBean.java    |   7 +-
 .../.classpath                                |  10 +
 .../legacyworlds-server-beans-techs/.project  |  23 ++
 .../.settings/org.eclipse.jdt.core.prefs      |   6 +
 .../.settings/org.maven.ide.eclipse.prefs     |   9 +
 .../legacyworlds-server-beans-techs/pom.xml   |  15 +
 .../beans/techs/EmpireTechnologyDAOBean.java  | 160 ++++++++
 .../techs/EmpireTechnologyManagerBean.java    | 181 +++++++++
 .../lw/beans/techs/EmpireUpdate.java          | 293 ++++++++++++++
 .../beans/techs/ResearchResponseBuilder.java  | 280 +++++++++++++
 .../lw/beans/techs/ResearchUpdateBean.java    | 180 +++++++++
 .../lw/beans/techs/ResearchUpdateDAOBean.java | 138 +++++++
 .../beans/techs/TechnologyGraphDAOBean.java   | 134 +++++++
 .../techs/TechnologyGraphManagerBean.java     |  74 ++++
 .../resources/configuration/techs-beans.xml   |  14 +
 .../techs/empire-technology-dao-bean.xml      |   8 +
 .../techs/empire-technology-manager-bean.xml  |   8 +
 .../techs/research-update-bean.xml            |   8 +
 .../techs/research-update-dao-bean.xml        |   8 +
 .../techs/technology-graph-dao-bean.xml       |   8 +
 .../techs/technology-graph-manager-bean.xml   |   8 +
 .../.classpath                                |  10 +
 .../.project                                  |  23 ++
 .../.settings/org.eclipse.jdt.core.prefs      |   6 +
 .../.settings/org.maven.ide.eclipse.prefs     |   9 +
 .../legacyworlds-server-beans-updates/pom.xml |  13 +
 .../lw/beans/updates/GameUpdateBean.java      | 367 ++++++++++++++++++
 .../beans/updates/ProceduralGameUpdate.java   |  52 +++
 .../lw/beans/updates/UpdatesDAOBean.java      |  85 ++++
 .../resources/configuration/updates-beans.xml |   9 +
 .../updates}/game-update-bean.xml             |   0
 .../updates}/updates-dao-bean.xml             |   0
 .../legacyworlds-server-beans-user/pom.xml    |   4 +-
 .../ListCategoriesCommandDelegateBean.java    |  59 +++
 .../ImplementTechCommandDelegateBean.java     |  17 +-
 ...ResearchPrioritiesCommandDelegateBean.java |  56 +++
 .../ViewResearchCommandDelegateBean.java      |  52 +++
 .../user/admin-session-definer-bean.xml       |   3 +
 .../user/player-session-definer-bean.xml      |   6 +-
 .../legacyworlds-server-beans/pom.xml         |   7 +-
 .../db-structure/parts/020-functions.sql      |   1 +
 .../parts/data/070-constants-data.sql         |   6 +-
 .../parts/data/080-techs-data.sql             |  68 ++--
 .../parts/data/090-buildables-data.sql        |  12 +-
 .../parts/data/100-universe-data.sql          |  12 +-
 .../parts/data/110-empires-data.sql           |  59 ++-
 .../parts/data/120-construction-data.sql      |   8 +-
 .../parts/data/130-fleets-data.sql            |   6 +-
 .../parts/data/170-events-data.sql            | 157 +++++++-
 .../functions/010-constants-functions.sql     |  34 +-
 .../parts/functions/030-tech-functions.sql    | 270 +++++++++----
 .../parts/functions/040-empire-functions.sql  |  95 +----
 .../functions/045-research-functions.sql      | 227 +++++++++++
 .../functions/050-computation-functions.sql   |  92 ++---
 .../functions/060-universe-functions.sql      |  28 +-
 .../parts/functions/140-planets-functions.sql |  67 ++--
 .../parts/functions/150-battle-functions.sql  |   8 +-
 .../parts/functions/160-battle-views.sql      |   4 +-
 .../parts/functions/165-fleets-functions.sql  |  81 ++--
 .../parts/functions/167-planet-list.sql       |   2 +-
 .../parts/functions/170-event-functions.sql   |   2 +-
 .../parts/functions/200-bugs-functions.sql    |  18 +-
 .../parts/updates/000-updates-ctrl.sql        | 104 ++---
 .../parts/updates/010-empire-money.sql        |   4 +-
 .../parts/updates/020-empire-research.sql     | 204 +++++++---
 .../parts/updates/025-empire-debt.sql         |   8 +-
 .../parts/updates/060-planet-battle.sql       |  16 +-
 .../parts/updates/080-planet-construction.sql |  25 +-
 .../parts/updates/090-planet-military.sql     |  10 +-
 .../parts/updates/100-planet-population.sql   |  22 +-
 .../parts/updates/110-planet-money.sql        |   4 +-
 .../legacyworlds-server-data/pom.xml          |   4 +-
 .../lw/sqld/game/EmpireTechLine.java          |  66 ----
 .../lw/sqld/game/EmpireTechnology.java        |  90 -----
 .../lw/sqld/game/GeneralInformation.java      |  24 +-
 .../lw/sqld/game/techs/Category.java          | 169 ++++++++
 .../lw/sqld/game/techs/EmpireTechnology.java  | 175 +++++++++
 .../lw/sqld/game/techs/ResearchStatus.java    |  20 +
 .../sqld/game/techs/ResearchUpdateInput.java  |  90 +++++
 .../sqld/game/techs/ResearchUpdateOutput.java | 134 +++++++
 .../lw/sqld/game/techs/TechGraph.java         | 366 +++++++++++++++++
 .../sqld/game/techs/TechGraphException.java   |  69 ++++
 .../lw/sqld/game/techs/Technology.java        | 285 ++++++++++++++
 .../com/deepclone/lw/sqld/sys/Constant.java   |  14 -
 .../legacyworlds-server-interfaces/pom.xml    |   4 +-
 .../lw/interfaces/game/EmpireDAO.java         |   7 -
 .../lw/interfaces/game/EmpireManagement.java  |  14 +-
 .../lw/interfaces/game/UpdatesDAO.java        |   7 -
 .../game/techs/EmpireTechnologyDAO.java       |  74 ++++
 .../game/techs/EmpireTechnologyManager.java   |  53 +++
 .../game/techs/ResearchUpdateDAO.java         |  56 +++
 .../game/techs/TechnologyGraphDAO.java        |  27 ++
 .../game/techs/TechnologyGraphManager.java    |  36 ++
 .../game/updates/DuplicateUpdateHandler.java  |  30 ++
 .../interfaces/game/updates/GameUpdate.java   |  25 ++
 .../game/updates/GameUpdatePhase.java         |  69 ++++
 .../game/updates/GameUpdatePhaseHandler.java  |  40 ++
 .../game/updates/GameUpdateTarget.java        |  17 +
 .../interfaces/game/updates/UpdatesDAO.java   |  46 +++
 .../interfaces/i18n/LanguageTranslator.java   |  31 ++
 .../lw/interfaces/i18n/Translator.java        |  13 +
 .../lw/interfaces/sys/ConstantsManager.java   |   7 +-
 .../lw/interfaces/sys/SystemStatus.java       |   7 +
 .../data/buildables-test.xml                  |  40 --
 .../data/buildables.xml                       |  12 +-
 .../data/buildables.xsd                       |   7 +-
 .../data/i18n-text.xml                        | 362 ++---------------
 .../data/i18n-text.xsd                        |   1 +
 .../data/techs-test.xml                       |  23 --
 .../legacyworlds-server-main/data/techs.xml   |  38 +-
 .../legacyworlds-server-main/data/techs.xsd   |  18 +-
 .../data/text/buildings.xml                   |  76 ++++
 .../data/text/mail.xml                        |  38 ++
 .../{ => text/mail}/addressChangeMail-en.txt  |   2 +-
 .../{ => text/mail}/addressChangeMail-fr.txt  |   2 +-
 .../data/{ => text/mail}/adminErrorMail.txt   |   2 +-
 .../data/{ => text/mail}/adminRecapMail.txt   |   2 +-
 .../data/{ => text/mail}/banLiftedMail-en.txt |   2 +-
 .../data/{ => text/mail}/banLiftedMail-fr.txt |   2 +-
 .../data/{ => text/mail}/bannedMail-en.txt    |   2 +-
 .../data/{ => text/mail}/bannedMail-fr.txt    |   2 +-
 .../{ => text/mail}/inactivityQuitMail-en.txt |   2 +-
 .../{ => text/mail}/inactivityQuitMail-fr.txt |   2 +-
 .../mail}/inactivityWarningMail-en.txt        |   2 +-
 .../mail}/inactivityWarningMail-fr.txt        |   2 +-
 .../data/{ => text/mail}/messageMail-en.txt   |   2 +-
 .../data/{ => text/mail}/messageMail-fr.txt   |   2 +-
 .../mail}/passwordRecoveryMail-en.txt         |   2 +-
 .../mail}/passwordRecoveryMail-fr.txt         |   2 +-
 .../data/{ => text/mail}/quitMail-en.txt      |   2 +-
 .../data/{ => text/mail}/quitMail-fr.txt      |   2 +-
 .../{ => text/mail}/reactivationMail-en.txt   |   4 +-
 .../{ => text/mail}/reactivationMail-fr.txt   |   4 +-
 .../data/{ => text/mail}/recapMail-en.txt     |   2 +-
 .../data/{ => text/mail}/recapMail-fr.txt     |   2 +-
 .../{ => text/mail}/registrationMail-en.txt   |   4 +-
 .../{ => text/mail}/registrationMail-fr.txt   |   4 +-
 .../data/text/preferences.xml                 | 130 +++++++
 .../data/text/technologies.xml                | 118 ++++++
 .../legacyworlds-server-main/hibernate.xml    |  25 --
 .../legacyworlds-server-main/pom.xml          |  19 +-
 .../deepclone/lw/cli/ImportBuildables.java    |  20 +-
 .../com/deepclone/lw/cli/ImportTechs.java     | 241 ++++++++++--
 .../java/com/deepclone/lw/cli/ImportText.java | 150 +------
 .../com/deepclone/lw/cli/i18n/FileString.java |  54 +++
 .../deepclone/lw/cli/i18n/InlineString.java   |  28 ++
 .../deepclone/lw/cli/i18n/LanguageData.java   |  36 ++
 .../deepclone/lw/cli/i18n/LoadableText.java   |  73 ++++
 .../com/deepclone/lw/cli/i18n/Loader.java     |  99 +++++
 .../com/deepclone/lw/cli/i18n/StringData.java |  25 ++
 .../com/deepclone/lw/cli/i18n/TextData.java   |  34 ++
 .../java/com/deepclone/lw/srv/Server.java     |  23 +-
 .../src/main/resources/lw-server.xml          |   2 +
 .../legacyworlds-server-tests/pom.xml         |   4 +-
 .../legacyworlds-server-utils/pom.xml         |   4 +-
 .../com/deepclone/lw/utils/EmailAddress.java  |   2 +-
 legacyworlds-server/pom.xml                   |   4 +-
 legacyworlds-session/pom.xml                  |   4 +-
 .../lw/cmd/CreateAuthChallengeCommand.java    |   3 +-
 .../lw/cmd/CreateAuthChallengeResponse.java   |   3 +-
 .../deepclone/lw/cmd/MaintenanceResponse.java |   3 +-
 .../lw/cmd/admin/AdminOverviewCommand.java    |   3 +-
 .../lw/cmd/admin/AdminOverviewResponse.java   |   3 +-
 .../deepclone/lw/cmd/admin/AdminResponse.java |   3 +-
 .../lw/cmd/admin/NoOperationCommand.java      |   3 +-
 .../lw/cmd/admin/SetPasswordCommand.java      |   3 +-
 .../lw/cmd/admin/SetPasswordResponse.java     |   3 +-
 .../lw/cmd/admin/adata/AdminOverview.java     |   4 +-
 .../lw/cmd/admin/adata/Administrator.java     |   4 +-
 .../cmd/admin/adata/AdministratorBasics.java  |   4 +-
 .../lw/cmd/admin/adata/Privileges.java        |   1 +
 .../lw/cmd/admin/bans/ArchivedBanRequest.java |   4 +-
 .../lw/cmd/admin/bans/BanRequest.java         |   4 +-
 .../lw/cmd/admin/bans/BansSummaryCommand.java |   3 +-
 .../cmd/admin/bans/BansSummaryResponse.java   |   3 +-
 .../lw/cmd/admin/bans/ConfirmBanCommand.java  |   3 +-
 .../lw/cmd/admin/bans/LiftBanCommand.java     |   3 +-
 .../lw/cmd/admin/bans/ListBansCommand.java    |   3 +-
 .../lw/cmd/admin/bans/ListBansResponse.java   |   3 +-
 .../lw/cmd/admin/bans/RejectBanCommand.java   |   3 +-
 .../lw/cmd/admin/bans/RejectBanResponse.java  |   3 +-
 .../lw/cmd/admin/bans/RequestBanCommand.java  |   3 +-
 .../lw/cmd/admin/bans/RequestBanResponse.java |   3 +-
 .../lw/cmd/admin/bans/SummaryEntry.java       |   4 +-
 .../cmd/admin/bans/ValidatedBanRequest.java   |   4 +-
 .../lw/cmd/admin/bt/BugsSummaryCommand.java   |   3 +-
 .../lw/cmd/admin/bt/BugsSummaryResponse.java  |   3 +-
 .../lw/cmd/admin/bt/GetSnapshotCommand.java   |   3 +-
 .../lw/cmd/admin/bt/GetSnapshotResponse.java  |   3 +-
 .../lw/cmd/admin/bt/ListBugsResponse.java     |   3 +-
 .../lw/cmd/admin/bt/MergeReportsCommand.java  |   3 +-
 .../lw/cmd/admin/bt/MergeReportsResponse.java |   3 +-
 .../cmd/admin/bt/ModerateCommentCommand.java  |   3 +-
 .../lw/cmd/admin/bt/PostCommentResponse.java  |   3 +-
 .../lw/cmd/admin/bt/ReportBugResponse.java    |   3 +-
 .../lw/cmd/admin/bt/ReportStatusCommand.java  |   3 +-
 .../cmd/admin/bt/ReportVisibilityCommand.java |   3 +-
 .../cmd/admin/bt/ValidateReportCommand.java   |   3 +-
 .../lw/cmd/admin/bt/ViewBugResponse.java      |   3 +-
 .../lw/cmd/admin/constants/Category.java      |   4 +-
 .../lw/cmd/admin/constants/Definition.java    |   4 +-
 .../admin/constants/GetConstantsCommand.java  |   3 +-
 .../admin/constants/GetConstantsResponse.java |   3 +-
 .../admin/constants/SetConstantCommand.java   |   3 +-
 .../admin/constants/SetConstantResponse.java  |   3 +-
 .../cmd/admin/i18n/ChangeLanguageCommand.java |   3 +-
 .../admin/i18n/ChangeLanguageResponse.java    |   3 +-
 .../lw/cmd/admin/i18n/GetLanguageCommand.java |   3 +-
 .../cmd/admin/i18n/GetLanguageResponse.java   |   3 +-
 .../lw/cmd/admin/i18n/I18NString.java         |   4 +-
 .../deepclone/lw/cmd/admin/i18n/Language.java |   4 +-
 .../lw/cmd/admin/i18n/SetStringCommand.java   |   3 +-
 .../lw/cmd/admin/i18n/SetStringResponse.java  |   3 +-
 .../cmd/admin/i18n/ViewLanguagesCommand.java  |   3 +-
 .../cmd/admin/i18n/ViewLanguagesResponse.java |   3 +-
 .../lw/cmd/admin/logs/ExceptionEntry.java     |   4 +-
 .../lw/cmd/admin/logs/GetEntryCommand.java    |   3 +-
 .../lw/cmd/admin/logs/GetEntryResponse.java   |   3 +-
 .../deepclone/lw/cmd/admin/logs/LogEntry.java |   4 +-
 .../lw/cmd/admin/logs/TraceEntry.java         |   4 +-
 .../lw/cmd/admin/logs/ViewLogCommand.java     |   3 +-
 .../lw/cmd/admin/logs/ViewLogResponse.java    |   3 +-
 .../admin/mntm/EnableMaintenanceCommand.java  |   3 +-
 .../cmd/admin/mntm/EndMaintenanceCommand.java |   3 +-
 .../admin/mntm/ExtendMaintenanceCommand.java  |   3 +-
 .../admin/mntm/MaintenanceChangeResponse.java |   3 +-
 .../admin/mntm/MaintenanceStatusCommand.java  |   3 +-
 .../admin/mntm/MaintenanceStatusResponse.java |   3 +-
 .../cmd/admin/msg/ComposeMessageCommand.java  |   3 +-
 .../cmd/admin/msg/ComposeMessageResponse.java |   3 +-
 .../lw/cmd/admin/msg/GetMessagesCommand.java  |   3 +-
 .../lw/cmd/admin/msg/GetMessagesResponse.java |   3 +-
 .../lw/cmd/admin/msg/MessageBoxCommand.java   |   3 +-
 .../cmd/admin/msg/PrepareMessageCommand.java  |   3 +-
 .../lw/cmd/admin/msg/ReadMessageCommand.java  |   3 +-
 .../lw/cmd/admin/msg/ReadMessageResponse.java |   3 +-
 .../lw/cmd/admin/msg/SendSpamCommand.java     |   3 +-
 .../lw/cmd/admin/naming/GetNamesCommand.java  |   3 +-
 .../lw/cmd/admin/naming/GetNamesResponse.java |   3 +-
 .../deepclone/lw/cmd/admin/naming/Name.java   |   4 +-
 .../cmd/admin/naming/NamesActionCommand.java  |   3 +-
 .../cmd/admin/naming/NamesSummaryCommand.java |   3 +-
 .../admin/naming/NamesSummaryResponse.java    |   3 +-
 .../admin/prefs/GetPrefDefaultsCommand.java   |   3 +-
 .../cmd/admin/prefs/PrefDefaultsResponse.java |   3 +-
 .../admin/prefs/SetPrefDefaultCommand.java    |   3 +-
 .../cmd/admin/su/AddAdministratorCommand.java |   3 +-
 .../admin/su/AddAdministratorResponse.java    |   3 +-
 .../admin/su/ListAdministratorsCommand.java   |   3 +-
 .../admin/su/ListAdministratorsResponse.java  |   3 +-
 .../admin/su/ResetAdminPasswordCommand.java   |   4 +-
 .../lw/cmd/admin/su/SetPrivilegesCommand.java |   3 +-
 .../admin/su/ViewAdministratorCommand.java    |   3 +-
 .../admin/su/ViewAdministratorResponse.java   |   3 +-
 .../cmd/admin/techs/GetTechnologyCommand.java |  29 ++
 .../admin/techs/GetTechnologyResponse.java    |  62 +++
 .../admin/techs/ListCategoriesCommand.java    |  15 +
 .../admin/techs/ListCategoriesResponse.java   |  40 ++
 .../lw/cmd/admin/techs/TechCategory.java      |  50 +++
 .../lw/cmd/admin/techs/TechEntry.java         |  62 +++
 .../cmd/admin/tick/SetTaskStatusCommand.java  |   3 +-
 .../cmd/admin/tick/TickerStatusCommand.java   |   3 +-
 .../cmd/admin/tick/TickerStatusResponse.java  |   3 +-
 .../lw/cmd/admin/tick/TickerTaskInfo.java     |   4 +-
 .../cmd/admin/tick/ToggleTickerCommand.java   |   3 +-
 .../lw/cmd/admin/users/AccountBanEntry.java   |   4 +-
 .../lw/cmd/admin/users/AccountListEntry.java  |   4 +-
 .../cmd/admin/users/AccountSessionEntry.java  |   4 +-
 .../lw/cmd/admin/users/AccountViewEntry.java  |   4 +-
 .../cmd/admin/users/GiveCreditsCommand.java   |   3 +-
 .../cmd/admin/users/ListAccountsCommand.java  |   3 +-
 .../cmd/admin/users/ListAccountsResponse.java |   3 +-
 .../cmd/admin/users/ListSessionsCommand.java  |   3 +-
 .../cmd/admin/users/ListSessionsResponse.java |   3 +-
 .../lw/cmd/admin/users/UserSession.java       |   4 +-
 .../cmd/admin/users/ViewAccountCommand.java   |   3 +-
 .../cmd/admin/users/ViewAccountResponse.java  |   3 +-
 .../deepclone/lw/cmd/bt/ListBugsCommand.java  |   3 +-
 .../lw/cmd/bt/PostCommentCommand.java         |   3 +-
 .../deepclone/lw/cmd/bt/ReportBugCommand.java |   3 +-
 .../deepclone/lw/cmd/bt/ViewBugCommand.java   |   3 +-
 .../deepclone/lw/cmd/bt/data/BugEvent.java    |   4 +-
 .../deepclone/lw/cmd/bt/data/BugReport.java   |   4 +-
 .../lw/cmd/bt/data/BugSubmitter.java          |   4 +-
 .../ext/ConfirmPasswordRecoveryCommand.java   |   3 +-
 .../ext/ConfirmPasswordRecoveryResponse.java  |   3 +-
 .../lw/cmd/ext/CreateAccountCommand.java      |   3 +-
 .../lw/cmd/ext/CreateAccountResponse.java     |   3 +-
 .../lw/cmd/ext/ListLanguagesCommand.java      |   3 +-
 .../lw/cmd/ext/ListLanguagesResponse.java     |   5 +-
 .../ext/RequestPasswordRecoveryCommand.java   |   3 +-
 .../ext/RequestPasswordRecoveryResponse.java  |   3 +-
 .../com/deepclone/lw/cmd/msgdata/Message.java |   3 +-
 .../lw/cmd/msgdata/MessageListEntry.java      |   4 +-
 .../lw/cmd/player/EmpireResponse.java         |  16 +-
 .../lw/cmd/player/GetNewPlanetCommand.java    |   3 +-
 .../lw/cmd/player/GetNewPlanetResponse.java   |   3 +-
 .../lw/cmd/player/ImplementTechCommand.java   |  28 --
 .../lw/cmd/player/ListPlanetsCommand.java     |   3 +-
 .../lw/cmd/player/ListPlanetsResponse.java    |   3 +-
 .../lw/cmd/player/OverviewCommand.java        |   3 +-
 .../lw/cmd/player/ViewMapCommand.java         |   3 +-
 .../lw/cmd/player/ViewMapResponse.java        |   3 +-
 .../account/AccountReactivationCommand.java   |   3 +-
 .../account/AccountReactivationResponse.java  |   3 +-
 .../account/AccountValidationCommand.java     |   3 +-
 .../account/AccountValidationResponse.java    |   3 +-
 .../cmd/player/account/BanDetailsCommand.java |   3 +-
 .../player/account/BanDetailsResponse.java    |   3 +-
 .../cmd/player/account/CancelQuitCommand.java |   3 +-
 .../cmd/player/account/GetAccountCommand.java |   3 +-
 .../player/account/GetAccountResponse.java    |   3 +-
 .../player/account/GetLanguageCommand.java    |   3 +-
 .../player/account/GetLanguageResponse.java   |   3 +-
 .../cmd/player/account/QuitGameCommand.java   |   3 +-
 .../cmd/player/account/SetAddressCommand.java |   3 +-
 .../player/account/SetAddressResponse.java    |   3 +-
 .../player/account/SetLanguageCommand.java    |   3 +-
 .../player/account/SetPasswordCommand.java    |   3 +-
 .../player/account/SetPasswordResponse.java   |   3 +-
 .../player/account/SetPreferencesCommand.java |   3 +-
 .../player/account/ToggleVacationCommand.java |   3 +-
 .../account/ValidateSetAddressCommand.java    |   3 +-
 .../account/ValidateSetAddressResponse.java   |   3 +-
 .../alliances/AllianceStatusCommand.java      |   3 +-
 .../alliances/AllianceStatusResponse.java     |   3 +-
 .../player/alliances/CancelJoinCommand.java   |   3 +-
 .../alliances/CreateAllianceCommand.java      |   3 +-
 .../alliances/CreateAllianceResponse.java     |   3 +-
 .../player/alliances/JoinAllianceCommand.java |   3 +-
 .../alliances/JoinAllianceResponse.java       |   3 +-
 .../player/alliances/KickMembersCommand.java  |   3 +-
 .../alliances/LeaveAllianceCommand.java       |   3 +-
 .../alliances/ManageRequestsCommand.java      |   3 +-
 .../alliances/TransferLeadershipCommand.java  |   3 +-
 .../player/alliances/ViewAllianceCommand.java |   3 +-
 .../alliances/ViewAllianceResponse.java       |   3 +-
 .../cmd/player/battles/GetBattleCommand.java  |   3 +-
 .../cmd/player/battles/GetBattleResponse.java |   3 +-
 .../player/battles/ListBattlesCommand.java    |   3 +-
 .../player/battles/ListBattlesResponse.java   |   3 +-
 .../lw/cmd/player/bt/ListBugsResponse.java    |   3 +-
 .../lw/cmd/player/bt/PostCommentResponse.java |   3 +-
 .../lw/cmd/player/bt/ReportBugResponse.java   |   3 +-
 .../lw/cmd/player/bt/ViewBugResponse.java     |   3 +-
 .../lw/cmd/player/elist/AddEnemyCommand.java  |   3 +-
 .../lw/cmd/player/elist/AddEnemyResponse.java |   3 +-
 .../lw/cmd/player/elist/EnemyListCommand.java |   3 +-
 .../cmd/player/elist/EnemyListResponse.java   |   3 +-
 .../player/elist/RemoveEnemiesCommand.java    |   3 +-
 .../player/fleets/DisbandFleetsCommand.java   |   4 +-
 .../player/fleets/DisbandFleetsResponse.java  |   3 +-
 .../cmd/player/fleets/MergeFleetsCommand.java |   4 +-
 .../player/fleets/MergeFleetsResponse.java    |   3 +-
 .../cmd/player/fleets/MoveFleetsCommand.java  |   4 +-
 .../cmd/player/fleets/MoveFleetsResponse.java |   3 +-
 .../cmd/player/fleets/MultiFleetsCommand.java |   3 +-
 .../player/fleets/MultiFleetsResponse.java    |   3 +-
 .../player/fleets/RenameFleetsCommand.java    |   4 +-
 .../player/fleets/RenameFleetsResponse.java   |   3 +-
 .../player/fleets/SetFleetsModeCommand.java   |   4 +-
 .../player/fleets/SetFleetsModeResponse.java  |   3 +-
 .../cmd/player/fleets/SplitFleetCommand.java  |   3 +-
 .../cmd/player/fleets/SplitFleetResponse.java |   3 +-
 .../cmd/player/fleets/ViewFleetsCommand.java  |   3 +-
 .../cmd/player/fleets/ViewFleetsResponse.java |   3 +-
 .../lw/cmd/player/gdata/GamePageData.java     |   4 +-
 .../lw/cmd/player/gdata/GameResponseBase.java |   3 +-
 .../lw/cmd/player/gdata/GameTime.java         |   4 +-
 .../lw/cmd/player/gdata/NameIdPair.java       |   4 +-
 .../lw/cmd/player/gdata/PlanetListData.java   |   4 +-
 .../lw/cmd/player/gdata/PlanetReference.java  |   4 +-
 .../lw/cmd/player/gdata/ShortBattleView.java  |   4 +-
 .../lw/cmd/player/gdata/TimeCombo.java        |   4 +-
 .../cmd/player/gdata/account/AccountData.java |   3 +-
 .../player/gdata/account/MailChangeData.java  |   4 +-
 .../player/gdata/account/PrefCategory.java    |   4 +-
 .../cmd/player/gdata/account/PrefChoice.java  |   4 +-
 .../cmd/player/gdata/account/PrefValue.java   |   4 +-
 .../alliance/AllianceCreationStatus.java      |   3 +-
 .../player/gdata/alliance/AllianceData.java   |   4 +-
 .../gdata/alliance/AllianceLeaderData.java    |   3 +-
 .../gdata/alliance/AllianceMemberData.java    |   3 +-
 .../gdata/alliance/AlliancePlanetData.java    |   4 +-
 .../alliance/PublicAllianceInformation.java   |   4 +-
 .../gdata/battles/BattleDescription.java      |   3 +-
 .../player/gdata/battles/BattleDisplay.java   |   3 +-
 .../cmd/player/gdata/battles/BattleEvent.java |   4 +-
 .../gdata/battles/BattleHistoryInterval.java  |   3 +-
 .../gdata/battles/BattleHistoryTick.java      |   3 +-
 .../player/gdata/battles/BattleListEntry.java |   3 +-
 .../gdata/battles/BattlePlanetBuildings.java  |   3 +-
 .../gdata/battles/BattlePlayerShips.java      |   3 +-
 .../player/gdata/battles/BattleShipType.java  |   4 +-
 .../player/gdata/battles/BattleShipsList.java |   4 +-
 .../player/gdata/battles/BattleShipsView.java |   4 +-
 .../player/gdata/battles/BattleSideShips.java |   4 +-
 .../cmd/player/gdata/battles/BattleView.java  |   4 +-
 .../cmd/player/gdata/empire/OverviewData.java |   4 +-
 .../cmd/player/gdata/empire/ResearchData.java | 224 +++++++++++
 .../player/gdata/empire/ResearchLineData.java |  70 ----
 .../gdata/empire/TechnologyCategoryData.java  |  55 +++
 .../player/gdata/empire/TechnologyData.java   |  56 +--
 .../player/gdata/fleets/FleetLocation.java    |   3 +-
 .../cmd/player/gdata/fleets/FleetOwner.java   |   3 +-
 .../cmd/player/gdata/fleets/FleetShips.java   |   4 +-
 .../cmd/player/gdata/fleets/FleetsView.java   |   4 +-
 .../cmd/player/gdata/fleets/MovingFleet.java  |   3 +-
 .../player/gdata/fleets/ShortFleetView.java   |   3 +-
 .../cmd/player/gdata/fleets/SplitShips.java   |   4 +-
 .../cmd/player/gdata/fleets/StaticFleet.java  |   3 +-
 .../cmd/player/gdata/map/MapPlanetData.java   |   3 +-
 .../cmd/player/gdata/map/MapSystemData.java   |   4 +-
 .../gdata/planets/BuildableBuildingData.java  |   4 +-
 .../gdata/planets/BuildableItemData.java      |   3 +-
 .../gdata/planets/BuildableShipData.java      |   3 +-
 .../player/gdata/planets/BuildingData.java    |   4 +-
 .../gdata/planets/OwnPlanetStatusData.java    |   3 +-
 .../player/gdata/planets/PlanetBasicView.java |   4 +-
 .../gdata/planets/PlanetOrbitalView.java      |   4 +-
 .../player/gdata/planets/PlanetOwnView.java   |   4 +-
 .../cmd/player/gdata/planets/QueueData.java   |   4 +-
 .../player/gdata/planets/QueueItemData.java   |   3 +-
 .../player/msgs/ComposeMessageCommand.java    |   3 +-
 .../player/msgs/ComposeMessageResponse.java   |   3 +-
 .../cmd/player/msgs/GetMessagesCommand.java   |   3 +-
 .../cmd/player/msgs/GetMessagesResponse.java  |   3 +-
 .../cmd/player/msgs/ListTargetsCommand.java   |   3 +-
 .../cmd/player/msgs/ListTargetsResponse.java  |   3 +-
 .../lw/cmd/player/msgs/MessageBoxCommand.java |   3 +-
 .../player/msgs/PrepareMessageCommand.java    |   3 +-
 .../cmd/player/msgs/ReadMessageCommand.java   |   3 +-
 .../cmd/player/msgs/ReadMessageResponse.java  |   3 +-
 .../player/planets/AbandonPlanetCommand.java  |   4 +-
 .../cmd/player/planets/BuildShipsCommand.java |   4 +-
 .../player/planets/BuildingActionCommand.java |   4 +-
 .../planets/BuildingActionResponse.java       |   3 +-
 .../cmd/player/planets/FlushQueueCommand.java |   4 +-
 .../player/planets/RenamePlanetCommand.java   |   4 +-
 .../player/planets/RenamePlanetResponse.java  |   3 +-
 .../cmd/player/planets/ViewPlanetCommand.java |   3 +-
 .../player/planets/ViewPlanetResponse.java    |   3 +-
 .../player/research/ImplementTechCommand.java |  29 ++
 .../research/ResearchOperationResponse.java   |  87 +++++
 .../SetResearchPrioritiesCommand.java         |  32 ++
 .../player/research/ViewResearchCommand.java  |  15 +
 .../player/research/ViewResearchResponse.java |  45 +++
 .../java/com/deepclone/lw/session/API.java    |  15 +
 .../com/deepclone/lw/session/Command.java     |   4 +-
 .../deepclone/lw/session/CommandResponse.java |   2 +-
 .../deepclone/lw/session/NullResponse.java    |   2 +-
 .../lw/session/SessionCommandException.java   |   2 +-
 .../lw/session/SessionException.java          |   2 +-
 .../session/SessionIdentifierException.java   |   2 +-
 .../lw/session/SessionInternalException.java  |   2 +-
 .../lw/session/SessionReference.java          |   2 +-
 .../deepclone/lw/session/SessionResponse.java |   2 +-
 .../lw/session/SessionStateException.java     |   2 +-
 legacyworlds-utils/pom.xml                    |   4 +-
 .../WEB-INF/fm/types/technologies.ftl         |  22 ++
 .../WebContent/WEB-INF/fm/version.ftl         |   4 +-
 .../legacyworlds-web-admin/pom.xml            |   4 +-
 .../lw/web/admin/TechnologyPages.java         |  37 ++
 .../legacyworlds-web-beans/pom.xml            |   4 +-
 .../deepclone/lw/web/csess/AdminSession.java  |  11 +
 .../deepclone/lw/web/csess/PlayerSession.java |  27 +-
 .../WebContent/WEB-INF/fm/en/static/home.ftl  |   2 +-
 .../WEB-INF/fm/en/types/overview.ftl          |  51 ---
 .../WEB-INF/fm/en/types/technologies.ftl      | 171 ++++++++
 .../WEB-INF/fm/fr/types/overview.ftl          |  51 ---
 .../WEB-INF/fm/fr/types/technologies.ftl      | 171 ++++++++
 .../WebContent/WEB-INF/fm/version.ftl         |   4 +-
 .../WebContent/css/main.css                   |  57 +++
 .../legacyworlds-web-main/pom.xml             |   4 +-
 .../lw/web/main/game/OverviewPage.java        |  19 -
 .../lw/web/main/game/TechnologyPages.java     | 143 +++++++
 legacyworlds-web/pom.xml                      |   4 +-
 pom.xml                                       |  10 +-
 runsrv.sh                                     |   2 +-
 runtool.sh                                    |   2 +-
 507 files changed, 8866 insertions(+), 2450 deletions(-)
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/TechnologyInformation.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.classpath
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.project
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.settings/org.eclipse.jdt.core.prefs
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.settings/org.maven.ide.eclipse.prefs
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/pom.xml
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
 delete mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.classpath
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.project
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.settings/org.eclipse.jdt.core.prefs
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.settings/org.maven.ide.eclipse.prefs
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/pom.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireTechnologyDAOBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireTechnologyManagerBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireUpdate.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchResponseBuilder.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchUpdateBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchUpdateDAOBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/TechnologyGraphDAOBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/TechnologyGraphManagerBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs-beans.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/empire-technology-dao-bean.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/empire-technology-manager-bean.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/research-update-bean.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/research-update-dao-bean.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/technology-graph-dao-bean.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/technology-graph-manager-bean.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.classpath
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.project
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.settings/org.eclipse.jdt.core.prefs
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.settings/org.maven.ide.eclipse.prefs
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/pom.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ProceduralGameUpdate.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/resources/configuration/updates-beans.xml
 rename legacyworlds-server/legacyworlds-server-beans/{legacyworlds-server-beans-simple/src/main/resources/configuration/simple => legacyworlds-server-beans-updates/src/main/resources/configuration/updates}/game-update-bean.xml (100%)
 rename legacyworlds-server/legacyworlds-server-beans/{legacyworlds-server-beans-simple/src/main/resources/configuration/simple => legacyworlds-server-beans-updates/src/main/resources/configuration/updates}/updates-dao-bean.xml (100%)
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/techs/ListCategoriesCommandDelegateBean.java
 rename legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/{ => techs}/ImplementTechCommandDelegateBean.java (65%)
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/SetResearchPrioritiesCommandDelegateBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/ViewResearchCommandDelegateBean.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/045-research-functions.sql
 delete mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
 delete mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/Category.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/EmpireTechnology.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchStatus.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchUpdateInput.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchUpdateOutput.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/TechGraph.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/TechGraphException.java
 create mode 100644 legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/Technology.java
 delete mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/EmpireTechnologyDAO.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/EmpireTechnologyManager.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/ResearchUpdateDAO.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/TechnologyGraphDAO.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/TechnologyGraphManager.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/DuplicateUpdateHandler.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdate.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdatePhase.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdatePhaseHandler.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateTarget.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java
 create mode 100644 legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/LanguageTranslator.java
 delete mode 100644 legacyworlds-server/legacyworlds-server-main/data/buildables-test.xml
 delete mode 100644 legacyworlds-server/legacyworlds-server-main/data/techs-test.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-main/data/text/buildings.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-main/data/text/mail.xml
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/addressChangeMail-en.txt (82%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/addressChangeMail-fr.txt (83%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/adminErrorMail.txt (76%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/adminRecapMail.txt (73%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/banLiftedMail-en.txt (79%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/banLiftedMail-fr.txt (77%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/bannedMail-en.txt (88%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/bannedMail-fr.txt (90%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/inactivityQuitMail-en.txt (88%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/inactivityQuitMail-fr.txt (88%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/inactivityWarningMail-en.txt (89%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/inactivityWarningMail-fr.txt (91%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/messageMail-en.txt (82%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/messageMail-fr.txt (77%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/passwordRecoveryMail-en.txt (92%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/passwordRecoveryMail-fr.txt (91%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/quitMail-en.txt (91%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/quitMail-fr.txt (85%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/reactivationMail-en.txt (85%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/reactivationMail-fr.txt (87%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/recapMail-en.txt (77%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/recapMail-fr.txt (72%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/registrationMail-en.txt (86%)
 rename legacyworlds-server/legacyworlds-server-main/data/{ => text/mail}/registrationMail-fr.txt (88%)
 create mode 100644 legacyworlds-server/legacyworlds-server-main/data/text/preferences.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-main/data/text/technologies.xml
 delete mode 100644 legacyworlds-server/legacyworlds-server-main/hibernate.xml
 create mode 100644 legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/FileString.java
 create mode 100644 legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/InlineString.java
 create mode 100644 legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/LanguageData.java
 create mode 100644 legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/LoadableText.java
 create mode 100644 legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/Loader.java
 create mode 100644 legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/StringData.java
 create mode 100644 legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/TextData.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/GetTechnologyCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/GetTechnologyResponse.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/ListCategoriesCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/ListCategoriesResponse.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/TechCategory.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/TechEntry.java
 delete mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ImplementTechCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
 delete mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchLineData.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyCategoryData.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ImplementTechCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ResearchOperationResponse.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/SetResearchPrioritiesCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ViewResearchCommand.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ViewResearchResponse.java
 create mode 100644 legacyworlds-session/src/main/java/com/deepclone/lw/session/API.java
 create mode 100644 legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/technologies.ftl
 create mode 100644 legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/TechnologyPages.java
 create mode 100644 legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/technologies.ftl
 create mode 100644 legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/technologies.ftl
 create mode 100644 legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/TechnologyPages.java

diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/pom.xml
index 093cc46..67b65e7 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-accounts/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-accounts</artifactId>
 	<name>Legacy Worlds account management</name>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<description>This package contains the beans responsible for managing accounts, including registration, inactivity checks, bans and authentication.</description>
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/pom.xml
index 35ab5e4..80a4b6e 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<dependencies>
@@ -20,6 +20,6 @@
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-bt</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds bug tracking system</name>
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
index 07c8949..a6823f7 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/EmpireSummaryBean.java
@@ -28,6 +28,7 @@ public class EmpireSummaryBean
 
 	private final RowMapper< DebugInformation > mMainInfo;
 	private final RowMapper< ResearchInformation > mResearch;
+	private final RowMapper< TechnologyInformation > mTechnology;
 	private final RowMapper< PlanetInformation > mPlanet;
 	private final RowMapper< QueueItemInformation > mQueueItem;
 	private final RowMapper< BuildingsInformation > mBuildings;
@@ -42,7 +43,8 @@ public class EmpireSummaryBean
 				AccountInformation.class , AllianceInformation.class , BuildingsInformation.class ,
 				DebugInformation.class , EmpireInformation.class , FleetInformation.class , MovementInformation.class ,
 				PlanetInformation.class , QueueInformation.class , QueueItemInformation.class ,
-				ResearchInformation.class , ShipsInformation.class , SystemInformation.class
+				ResearchInformation.class , TechnologyInformation.class , ShipsInformation.class ,
+				SystemInformation.class
 		} );
 
 		this.mMainInfo = new RowMapper< DebugInformation >( ) {
@@ -83,13 +85,23 @@ public class EmpireSummaryBean
 					throws SQLException
 			{
 				ResearchInformation ri = new ResearchInformation( );
-				ri.setId( rs.getInt( "line_id" ) );
-				ri.setCurrentLevel( rs.getInt( "level" ) );
-				ri.setLevelName( rs.getString( "name" ) );
+				ri.setName( rs.getString( "name" ) );
 				ri.setAccumulated( rs.getDouble( "accumulated" ) );
+				ri.setPriority( rs.getInt( "priority" ) );
 				return ri;
 			}
 		};
+		this.mTechnology = new RowMapper< TechnologyInformation >( ) {
+			@Override
+			public TechnologyInformation mapRow( ResultSet rs , int rowNum )
+					throws SQLException
+			{
+				TechnologyInformation ti = new TechnologyInformation( );
+				ti.setName( rs.getString( "name" ) );
+				ti.setImplemented( rs.getBoolean( "implemented" ) );
+				return ti;
+			}
+		};
 		this.mPlanet = new RowMapper< PlanetInformation >( ) {
 			@Override
 			public PlanetInformation mapRow( ResultSet rs , int rowNum )
@@ -199,9 +211,10 @@ public class EmpireSummaryBean
 		DebugInformation di = this.dTemplate.queryForObject( sql , this.mMainInfo , empireId );
 
 		sql = "SELECT * FROM bugs.dump_research_view WHERE empire_id = ?";
-		for ( ResearchInformation ri : this.dTemplate.query( sql , this.mResearch , empireId ) ) {
-			di.getResearch( ).add( ri );
-		}
+		di.getResearch( ).addAll( this.dTemplate.query( sql , this.mResearch , empireId ) );
+
+		sql = "SELECT * FROM bugs.dump_technologies_view WHERE empire_id = ?";
+		di.getTechnologies( ).addAll( this.dTemplate.query( sql , this.mTechnology , empireId ) );
 
 		sql = "SELECT * FROM bugs.dump_planets_view WHERE empire_id = ?";
 		Map< Integer , PlanetInformation > planets = new HashMap< Integer , PlanetInformation >( );
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java
index 847fcb1..2a676e0 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/DebugInformation.java
@@ -15,26 +15,29 @@ public class DebugInformation
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = 2L;
 
 	@XStreamAsAttribute
 	@XStreamAlias( "dump-version" )
-	private int version = 1;
+	private final int version = 2;
 
-	private SystemInformation system = new SystemInformation( );
+	private final SystemInformation system = new SystemInformation( );
 
-	private AccountInformation account = new AccountInformation( );
+	private final AccountInformation account = new AccountInformation( );
 
-	private EmpireInformation empire = new EmpireInformation( );
+	private final EmpireInformation empire = new EmpireInformation( );
 
 	@XStreamAlias( "research" )
-	private List< ResearchInformation > research = new LinkedList< ResearchInformation >( );
+	private final List< ResearchInformation > research = new LinkedList< ResearchInformation >( );
+
+	@XStreamAlias( "technologies" )
+	private final List< TechnologyInformation > technologies = new LinkedList< TechnologyInformation >( );
 
 	@XStreamAlias( "planets" )
-	private List< PlanetInformation > planets = new LinkedList< PlanetInformation >( );
+	private final List< PlanetInformation > planets = new LinkedList< PlanetInformation >( );
 
 	@XStreamAlias( "fleets" )
-	private List< FleetInformation > fleets = new LinkedList< FleetInformation >( );
+	private final List< FleetInformation > fleets = new LinkedList< FleetInformation >( );
 
 
 	public int getVersion( )
@@ -67,6 +70,12 @@ public class DebugInformation
 	}
 
 
+	public List< TechnologyInformation > getTechnologies( )
+	{
+		return technologies;
+	}
+
+
 	public List< PlanetInformation > getPlanets( )
 	{
 		return planets;
@@ -78,4 +87,4 @@ public class DebugInformation
 		return fleets;
 	}
 
-}
+}
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java
index 69d9f63..ec90c4a 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/ResearchInformation.java
@@ -8,64 +8,34 @@ import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
 
 
 
-@XStreamAlias( "research-line" )
+@XStreamAlias( "research-topic" )
 public class ResearchInformation
 		implements Serializable
 
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = 2L;
 
 	@XStreamAsAttribute
-	@XStreamAlias( "line")
-	private int id;
-	
-	@XStreamAsAttribute
-	@XStreamAlias( "level")
-	private int currentLevel;
+	private String name;
 
 	@XStreamAsAttribute
-	@XStreamAlias( "name")
-	private String levelName;
-
-	@XStreamAsAttribute
-	@XStreamAlias( "accumulated-points")
+	@XStreamAlias( "accumulated-points" )
 	private double accumulated;
 
+	@XStreamAsAttribute
+	private int priority;
 
-	public int getId( )
+
+	public String getName( )
 	{
-		return id;
+		return name;
 	}
 
 
-	public void setId( int id )
+	public void setName( String name )
 	{
-		this.id = id;
-	}
-
-
-	public int getCurrentLevel( )
-	{
-		return currentLevel;
-	}
-
-
-	public void setCurrentLevel( int currentLevel )
-	{
-		this.currentLevel = currentLevel;
-	}
-
-
-	public String getLevelName( )
-	{
-		return levelName;
-	}
-
-
-	public void setLevelName( String levelName )
-	{
-		this.levelName = levelName;
+		this.name = name;
 	}
 
 
@@ -80,4 +50,16 @@ public class ResearchInformation
 		this.accumulated = accumulated;
 	}
 
+
+	public int getPriority( )
+	{
+		return priority;
+	}
+
+
+	public void setPriority( int priority )
+	{
+		this.priority = priority;
+	}
+
 }
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/TechnologyInformation.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/TechnologyInformation.java
new file mode 100644
index 0000000..0839378
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-bt/src/main/java/com/deepclone/lw/beans/bt/esdata/TechnologyInformation.java
@@ -0,0 +1,48 @@
+package com.deepclone.lw.beans.bt.esdata;
+
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+@XStreamAlias( "technology" )
+public class TechnologyInformation
+		implements Serializable
+{
+
+	private static final long serialVersionUID = 1L;
+
+	@XStreamAsAttribute
+	private String name;
+
+	@XStreamAsAttribute
+	private boolean implemented;
+
+
+	public String getName( )
+	{
+		return name;
+	}
+
+
+	public void setName( String name )
+	{
+		this.name = name;
+	}
+
+
+	public boolean isImplemented( )
+	{
+		return implemented;
+	}
+
+
+	public void setImplemented( boolean implemented )
+	{
+		this.implemented = implemented;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/pom.xml
index 0305043..939d46e 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-eventlog/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-eventlog</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds event log</name>
 	<description>This package is responsible for all logging in Legacy Worlds through three different beans (system event logger, admin event logger and user event logger).</description>
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.classpath
new file mode 100644
index 0000000..7c3d14f
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.project
new file mode 100644
index 0000000..99714d6
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>legacyworlds-server-beans-events</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..6839360
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,6 @@
+#Sun Apr 03 09:36:59 CEST 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.settings/org.maven.ide.eclipse.prefs
new file mode 100644
index 0000000..abbef3f
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/.settings/org.maven.ide.eclipse.prefs
@@ -0,0 +1,9 @@
+#Sun Apr 03 09:36:59 CEST 2011
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/pom.xml
new file mode 100644
index 0000000..143c198
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-events/pom.xml
@@ -0,0 +1,13 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>legacyworlds-server-beans</artifactId>
+    <groupId>com.deepclone.lw</groupId>
+    <version>5.99.2</version>
+  </parent>
+  <groupId>com.deepclone.lw</groupId>
+  <artifactId>legacyworlds-server-beans-events</artifactId>
+  <version>5.99.2</version>
+  <name>Game events</name>
+  <description>This module contains components which manage game events.</description>
+</project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/pom.xml
index 26ad335..a3f0ce7 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/pom.xml
@@ -4,13 +4,13 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-i18n</artifactId>
 	<name>Legacy Worlds internationalisation</name>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<description>This package defines the two beans which control server-side internationalised text management.</description>
 
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
index 7ce8046..7ef4255 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-i18n/src/main/java/com/deepclone/lw/beans/i18n/TranslatorBean.java
@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
+import com.deepclone.lw.interfaces.i18n.LanguageTranslator;
 import com.deepclone.lw.interfaces.i18n.Translator;
 import com.deepclone.lw.interfaces.i18n.UnknownLanguageException;
 import com.deepclone.lw.interfaces.i18n.UnknownStringException;
@@ -21,6 +22,67 @@ import com.deepclone.lw.interfaces.i18n.UnknownStringException;
 public class TranslatorBean
 		implements Translator
 {
+
+	/**
+	 * Implementation of a language-specific translator.
+	 */
+	private static class LanguageTranslatorImpl
+			implements LanguageTranslator
+	{
+		/** Language used by the translator */
+		private final String language;
+
+		/** Translations store */
+		private final I18NData data;
+
+
+		LanguageTranslatorImpl( String language , TranslatorBean translator )
+		{
+			this.language = language;
+			this.data = translator.data;
+		}
+
+
+		/* Documentation in LanguageTranslator interface */
+		@Override
+		public String getLanguage( )
+		{
+			return this.language;
+		}
+
+
+		/* Documentation in LanguageTranslator interface */
+		@Override
+		public String getLanguageName( )
+		{
+			this.data.readLock( ).lock( );
+			try {
+				return this.data.getLanguageName( language );
+			} finally {
+				this.data.readLock( ).unlock( );
+			}
+		}
+
+
+		/* Documentation in LanguageTranslator interface */
+		@Override
+		public String translate( String string )
+				throws UnknownStringException
+		{
+			this.data.readLock( ).lock( );
+			try {
+				if ( !this.data.hasString( string ) ) {
+					throw new UnknownStringException( string );
+				}
+				return this.data.getTranslation( language , string );
+			} finally {
+				this.data.readLock( ).unlock( );
+			}
+		}
+
+	}
+
+	/** Translations store */
 	private I18NData data;
 
 
@@ -119,4 +181,21 @@ public class TranslatorBean
 		}
 	}
 
+
+	/* Documentation in Translator interface */
+	@Override
+	public LanguageTranslator getLanguageTranslator( String language )
+			throws UnknownLanguageException
+	{
+		this.data.readLock( ).lock( );
+		try {
+			if ( !this.data.isLanguageComplete( language ) ) {
+				throw new UnknownLanguageException( language );
+			}
+			return new LanguageTranslatorImpl( language , this );
+		} finally {
+			this.data.readLock( ).unlock( );
+		}
+	}
+
 }
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/pom.xml
index 9b2eba1..4bdff1a 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-mailer/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-mailer</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds mailer</name>
 	<description>
 		This package contains the mailer component, which uses LW's i18n system and Spring's mail sending interfaces.
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/pom.xml
index 00964d7..59a113f 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-naming/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-naming</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds object naming system</name>
 	<description>This module contains the beans responsible for managing the names of the various objects (players and planets).</description>
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/pom.xml
index e1929cf..ea2e695 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/pom.xml
@@ -3,11 +3,11 @@
   <parent>
     <artifactId>legacyworlds-server-beans</artifactId>
     <groupId>com.deepclone.lw</groupId>
-    <version>5.99.1</version>
+    <version>5.99.2</version>
   </parent>
   <groupId>com.deepclone.lw</groupId>
   <artifactId>legacyworlds-server-beans-simple</artifactId>
-  <version>5.99.1</version>
+  <version>5.99.2</version>
   <name>Legacy Worlds simple game</name>
   <description>This module contains code that corresponds to a simple "placeholder" game. This code should become obsolete over time, as it is being replaced with actual LWB6 code, until the module can finally be removed.</description>
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
index daaa57b..5e30025 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
@@ -4,9 +4,7 @@ package com.deepclone.lw.beans.empire;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import javax.sql.DataSource;
 
@@ -20,8 +18,6 @@ import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.PlanetListData;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
 import com.deepclone.lw.interfaces.game.EmpireDAO;
-import com.deepclone.lw.sqld.game.EmpireTechLine;
-import com.deepclone.lw.sqld.game.EmpireTechnology;
 import com.deepclone.lw.sqld.game.GeneralInformation;
 import com.deepclone.lw.utils.StoredProc;
 
@@ -31,7 +27,6 @@ public class EmpireDAOBean
 		implements EmpireDAO
 {
 	private SimpleJdbcTemplate dTemplate;
-	private StoredProc fImplementTech;
 	private StoredProc fAddEmpEnemy;
 	private StoredProc fAddAllEnemy;
 	private StoredProc fRemoveEmpEnemy;
@@ -40,16 +35,36 @@ public class EmpireDAOBean
 
 	private final PlanetListMapper mPlanetList = new PlanetListMapper( );
 
+	private final RowMapper< GeneralInformation > mEmpireInfo;
+
+
+	public EmpireDAOBean( )
+	{
+		this.mEmpireInfo = new RowMapper< GeneralInformation >( ) {
+			@Override
+			public GeneralInformation mapRow( ResultSet rs , int rowNum )
+					throws SQLException
+			{
+				String st = rs.getString( "status" );
+				Character status = ( st == null ) ? null : st.charAt( 0 );
+				String name = rs.getString( "name" );
+				String tag = rs.getString( "alliance" );
+				String language = rs.getString( "language" );
+				long cash = rs.getLong( "cash" );
+				long nextTick = rs.getLong( "game_time" );
+				int accountId = rs.getInt( "account_id" );
+				return new GeneralInformation( status , name , tag , language , cash , nextTick , accountId );
+			}
+		};
+
+	}
+
 
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
 	{
 		this.dTemplate = new SimpleJdbcTemplate( dataSource );
 
-		this.fImplementTech = new StoredProc( dataSource , "emp" , "implement_tech" );
-		this.fImplementTech.addParameter( "empire_id" , Types.INTEGER );
-		this.fImplementTech.addParameter( "line_id" , Types.INTEGER );
-
 		this.fAddEmpEnemy = new StoredProc( dataSource , "emp" , "add_enemy_empire" );
 		this.fAddEmpEnemy.addParameter( "empire_id" , Types.INTEGER );
 		this.fAddEmpEnemy.addParameter( "enemy_name" , Types.VARCHAR );
@@ -79,19 +94,8 @@ public class EmpireDAOBean
 	public GeneralInformation getInformation( int empireId )
 	{
 		String sql = "SELECT * FROM emp.general_information WHERE id = ?";
-		RowMapper< GeneralInformation > mapper = new RowMapper< GeneralInformation >( ) {
-			@Override
-			public GeneralInformation mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				String st = rs.getString( "status" );
-				Character status = ( st == null ) ? null : st.charAt( 0 );
-				return new GeneralInformation( status , rs.getString( "name" ) , rs.getString( "alliance" ) , rs
-						.getLong( "cash" ) , rs.getLong( "game_time" ) , rs.getInt( "account_id" ) );
-			}
-		};
 		try {
-			return this.dTemplate.queryForObject( sql , mapper , empireId );
+			return this.dTemplate.queryForObject( sql , this.mEmpireInfo , empireId );
 		} catch ( EmptyResultDataAccessException e ) {
 			return null;
 		}
@@ -156,65 +160,6 @@ public class EmpireDAOBean
 	}
 
 
-	@Override
-	public List< EmpireTechLine > getTechnology( int empireId )
-	{
-		String sql = "SELECT * FROM emp.tech_lines_view WHERE empire = ?";
-		RowMapper< EmpireTechLine > lineMapper = new RowMapper< EmpireTechLine >( ) {
-			@Override
-			public EmpireTechLine mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				EmpireTechLine etl = new EmpireTechLine( );
-				etl.setId( rs.getInt( "tech_line" ) );
-				etl.setName( rs.getString( "name" ) );
-				etl.setDescription( rs.getString( "description" ) );
-				return etl;
-			}
-		};
-
-		List< EmpireTechLine > lines = this.dTemplate.query( sql , lineMapper , empireId );
-		if ( lines.isEmpty( ) ) {
-			return lines;
-		}
-
-		Map< Integer , EmpireTechLine > linesById = new HashMap< Integer , EmpireTechLine >( );
-		for ( EmpireTechLine etl : lines ) {
-			linesById.put( etl.getId( ) , etl );
-		}
-
-		sql = "SELECT * FROM emp.technologies_view WHERE empire = ?";
-		RowMapper< EmpireTechnology > techMapper = new RowMapper< EmpireTechnology >( ) {
-			@Override
-			public EmpireTechnology mapRow( ResultSet rs , int rowNum )
-					throws SQLException
-			{
-				EmpireTechnology et = new EmpireTechnology( );
-				et.setLine( rs.getInt( "tech_line" ) );
-				et.setName( rs.getString( "name" ) );
-				et.setDescription( rs.getString( "description" ) );
-				et.setImplemented( rs.getBoolean( "implemented" ) );
-				et.setProgress( (int) rs.getDouble( "progress" ) );
-				et.setCost( rs.getInt( "cost" ) );
-				return et;
-			}
-		};
-
-		for ( EmpireTechnology et : this.dTemplate.query( sql , techMapper , empireId ) ) {
-			linesById.get( et.getLine( ) ).addTechnology( et );
-		}
-
-		return lines;
-	}
-
-
-	@Override
-	public void implementTechnology( int empireId , int lineId )
-	{
-		this.fImplementTech.execute( empireId , lineId );
-	}
-
-
 	@Override
 	public List< PlanetListData > getPlanetList( int empireId )
 	{
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
index 18a9571..0fc3d79 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
@@ -18,19 +18,18 @@ import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.PlanetListData;
 import com.deepclone.lw.cmd.player.gdata.battles.BattleListEntry;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
-import com.deepclone.lw.cmd.player.gdata.empire.ResearchLineData;
-import com.deepclone.lw.cmd.player.gdata.empire.TechnologyData;
 import com.deepclone.lw.interfaces.acm.UsersDAO;
 import com.deepclone.lw.interfaces.game.BattlesCache;
 import com.deepclone.lw.interfaces.game.BattlesDAO;
 import com.deepclone.lw.interfaces.game.EmpireDAO;
 import com.deepclone.lw.interfaces.game.EmpireManagement;
+import com.deepclone.lw.interfaces.i18n.LanguageTranslator;
+import com.deepclone.lw.interfaces.i18n.Translator;
+import com.deepclone.lw.interfaces.i18n.UnknownLanguageException;
 import com.deepclone.lw.interfaces.naming.NamingDAO;
 import com.deepclone.lw.interfaces.prefs.AccountPreferences;
 import com.deepclone.lw.interfaces.prefs.PreferencesDAO;
 import com.deepclone.lw.sqld.accounts.Account;
-import com.deepclone.lw.sqld.game.EmpireTechLine;
-import com.deepclone.lw.sqld.game.EmpireTechnology;
 import com.deepclone.lw.sqld.game.GeneralInformation;
 import com.deepclone.lw.sqld.game.battle.BattleListRecord;
 import com.deepclone.lw.utils.EmailAddress;
@@ -46,6 +45,7 @@ public class EmpireManagementBean
 	private EmpireDAO empireDao;
 	private PreferencesDAO prefsDao;
 	private BattlesDAO battlesDao;
+	private Translator translator;
 
 
 	@Autowired( required = true )
@@ -83,6 +83,13 @@ public class EmpireManagementBean
 	}
 
 
+	@Autowired( required = true )
+	public void setI18NManager( Translator translator )
+	{
+		this.translator = translator;
+	}
+
+
 	@Override
 	public Integer getEmpireId( EmailAddress address )
 	{
@@ -108,29 +115,22 @@ public class EmpireManagementBean
 	}
 
 
+	@Override
+	public LanguageTranslator getTranslator( int empireId )
+	{
+		GeneralInformation generalInformation = this.empireDao.getInformation( empireId );
+		try {
+			return this.translator.getLanguageTranslator( generalInformation.getLanguage( ) );
+		} catch ( UnknownLanguageException e ) {
+			throw new RuntimeException( "account for empire " + empireId + " is using an unsupported language" , e );
+		}
+	}
+
+
 	@Override
 	public EmpireResponse getOverview( int empireId )
 	{
 		OverviewData overview = this.empireDao.getOverview( empireId );
-		List< ResearchLineData > research = new LinkedList< ResearchLineData >( );
-
-		for ( EmpireTechLine etl : this.empireDao.getTechnology( empireId ) ) {
-			List< TechnologyData > implemented = new LinkedList< TechnologyData >( );
-			TechnologyData current = null;
-
-			for ( EmpireTechnology et : etl.getTechnologies( ) ) {
-				if ( et.isImplemented( ) ) {
-					implemented.add( new TechnologyData( et.getName( ) , et.getDescription( ) ) );
-				} else if ( et.getProgress( ) == 100 ) {
-					current = new TechnologyData( et.getName( ) , et.getDescription( ) , 100 , et.getCost( ) );
-				} else {
-					current = new TechnologyData( et.getName( ) , et.getDescription( ) , et.getProgress( ) );
-				}
-			}
-
-			research.add( new ResearchLineData( etl.getId( ) , etl.getName( ) , etl.getDescription( ) , implemented ,
-					current ) );
-		}
 
 		List< BattleListEntry > battles = new LinkedList< BattleListEntry >( );
 		for ( BattleListRecord record : this.battlesDao.getBattles( empireId ) ) {
@@ -150,15 +150,7 @@ public class EmpireManagementBean
 			battles.add( entry );
 		}
 
-		return new EmpireResponse( this.getGeneralInformation( empireId ) , overview , research , battles );
-	}
-
-
-	@Override
-	public EmpireResponse implementTechnology( int empireId , int techId )
-	{
-		this.empireDao.implementTechnology( empireId , techId );
-		return this.getOverview( empireId );
+		return new EmpireResponse( this.getGeneralInformation( empireId ) , overview , battles );
 	}
 
 
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
deleted file mode 100644
index 3973457..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.deepclone.lw.beans.updates;
-
-
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.TransactionCallback;
-import org.springframework.transaction.support.TransactionTemplate;
-
-import com.deepclone.lw.cmd.admin.logs.LogLevel;
-import com.deepclone.lw.interfaces.eventlog.Logger;
-import com.deepclone.lw.interfaces.eventlog.SystemLogger;
-import com.deepclone.lw.interfaces.game.UpdatesDAO;
-import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
-import com.deepclone.lw.interfaces.sys.SystemStatus;
-import com.deepclone.lw.interfaces.sys.TickStatusException;
-import com.deepclone.lw.interfaces.sys.Ticker;
-import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
-
-
-
-public class GameUpdateBean
-		implements InitializingBean , Runnable
-{
-	private Ticker ticker;
-	private SystemStatus systemStatus;
-	private SystemLogger logger;
-
-	private TransactionTemplate tTemplate;
-	private UpdatesDAO updatesDao;
-
-
-	@Autowired( required = true )
-	public void setTicker( Ticker ticker )
-	{
-		this.ticker = ticker;
-	}
-
-
-	@Autowired( required = true )
-	public void setSystemStatus( SystemStatus systemStatus )
-	{
-		this.systemStatus = systemStatus;
-	}
-
-
-	@Autowired( required = true )
-	public void setLogger( Logger logger )
-	{
-		this.logger = logger.getSystemLogger( "GameUpdate" );
-	}
-
-
-	@Autowired( required = true )
-	public void setTransactionManager( PlatformTransactionManager transactionManager )
-	{
-		this.tTemplate = new TransactionTemplate( transactionManager );
-	}
-
-
-	@Autowired( required = true )
-	public void setUpdatesDAO( UpdatesDAO updatesDao )
-	{
-		this.updatesDao = updatesDao;
-	}
-
-
-	@Override
-	public void afterPropertiesSet( )
-	{
-		try {
-			this.endPreviousTick( );
-		} catch ( MaintenanceStatusException e ) {
-			// Do nothing
-		}
-		this.ticker.registerTask( Frequency.MINUTE , "Game update" , this );
-	}
-
-
-	@Override
-	public void run( )
-	{
-		// Attempt to end the previous tick, if e.g. maintenance mode was initiated while it was
-		// being processed
-		try {
-			this.endPreviousTick( );
-		} catch ( MaintenanceStatusException e1 ) {
-			return;
-		}
-
-		// Initiate next tick
-		long tickId;
-		try {
-			tickId = this.systemStatus.startTick( );
-		} catch ( TickStatusException e ) {
-			throw new RuntimeException( "tick initiated while previous tick still being processed" , e );
-		} catch ( MaintenanceStatusException e ) {
-			return;
-		}
-
-		// Execute tick
-		this.logger.log( LogLevel.DEBUG , "Tick " + tickId + " started" ).flush( );
-		this.executeTick( tickId );
-	}
-
-
-	private void endPreviousTick( )
-			throws MaintenanceStatusException
-	{
-		Long currentTick = this.systemStatus.checkStuckTick( );
-		if ( currentTick == null ) {
-			return;
-		}
-
-		this.logger.log( LogLevel.WARNING , "Tick " + currentTick + " restarted" ).flush( );
-		this.executeTick( currentTick.longValue( ) );
-	}
-
-
-	private void executeTick( final long tickId )
-	{
-		boolean hasMore;
-		do {
-			hasMore = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
-
-				@Override
-				public Boolean doInTransaction( TransactionStatus status )
-				{
-					return updatesDao.processUpdates( tickId );
-				}
-
-			} );
-		} while ( hasMore );
-		this.logger.log( LogLevel.TRACE , "Tick " + tickId + " completed" ).flush( );
-	}
-
-}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
deleted file mode 100644
index 77a1bbd..0000000
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.deepclone.lw.beans.updates;
-
-
-import java.sql.Types;
-import java.util.Map;
-
-import javax.sql.DataSource;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.SqlOutParameter;
-import org.springframework.jdbc.core.SqlParameter;
-import org.springframework.jdbc.core.simple.SimpleJdbcCall;
-
-import com.deepclone.lw.interfaces.game.UpdatesDAO;
-
-
-
-public class UpdatesDAOBean
-		implements UpdatesDAO
-{
-
-	private SimpleJdbcCall process;
-
-
-	@Autowired( required = true )
-	public void setDataSource( DataSource dataSource )
-	{
-		this.process = new SimpleJdbcCall( dataSource );
-		this.process.withCatalogName( "sys" ).withFunctionName( "process_updates" );
-		this.process.withoutProcedureColumnMetaDataAccess( );
-		this.process.addDeclaredParameter( new SqlParameter( "tick_id" , Types.BIGINT ) );
-		this.process.addDeclaredParameter( new SqlOutParameter( "has_more" , Types.BOOLEAN ) );
-	}
-
-
-	@Override
-	public boolean processUpdates( long tickId )
-	{
-		Map< String , Object > m = this.process.execute( tickId );
-		return (Boolean) m.get( "has_more" );
-	}
-
-}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
index ffd1068..a6219d9 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
@@ -10,13 +10,11 @@
 	<import resource="simple/empire-management-bean.xml" />
 	<import resource="simple/fleet-management-bean.xml" />
 	<import resource="simple/fleets-dao-bean.xml" />
-	<import resource="simple/game-update-bean.xml" />
 	<import resource="simple/map-viewer-bean.xml" />
 	<import resource="simple/message-beans.xml" />
 	<import resource="simple/planet-dao-bean.xml" />
 	<import resource="simple/planets-management-bean.xml" />
 	<import resource="simple/universe-dao-bean.xml" />
 	<import resource="simple/universe-generator-bean.xml" />
-	<import resource="simple/updates-dao-bean.xml" />
 
 </beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/pom.xml
index 47dfd4f..593f8fb 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-system</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds system management</name>
 	<description>This module regroups system management beans such as the constants manager.</description>
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
index 4096acd..2712a22 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsData.java
@@ -93,7 +93,7 @@ class ConstantsData
 		this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "cname" , Types.VARCHAR ) );
 		this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "cdesc" , Types.VARCHAR ) );
 		this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "catname" , Types.VARCHAR ) );
-		this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "value" , Types.REAL ) );
+		this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "value" , Types.DOUBLE ) );
 
 		this.uocConstantSingleBound = new SimpleJdbcCall( dataSource );
 		this.uocConstantSingleBound.withCatalogName( "sys" ).withFunctionName( "uoc_constant" );
@@ -101,8 +101,8 @@ class ConstantsData
 		this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "cname" , Types.VARCHAR ) );
 		this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "cdesc" , Types.VARCHAR ) );
 		this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "catname" , Types.VARCHAR ) );
-		this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "value" , Types.REAL ) );
-		this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "boundary" , Types.REAL ) );
+		this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "value" , Types.DOUBLE ) );
+		this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "boundary" , Types.DOUBLE ) );
 		this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "is_min" , Types.BOOLEAN ) );
 
 		this.uocConstantTwoBounds = new SimpleJdbcCall( dataSource );
@@ -111,13 +111,13 @@ class ConstantsData
 		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "cname" , Types.VARCHAR ) );
 		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "cdesc" , Types.VARCHAR ) );
 		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "catname" , Types.VARCHAR ) );
-		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "value" , Types.REAL ) );
-		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "min" , Types.REAL ) );
-		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "max" , Types.REAL ) );
+		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "value" , Types.DOUBLE ) );
+		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "min" , Types.DOUBLE ) );
+		this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "max" , Types.DOUBLE ) );
 
 		this.fSetConstant = new StoredProc( dataSource , "sys" , "set_constant" );
 		this.fSetConstant.addParameter( "cname" , Types.VARCHAR );
-		this.fSetConstant.addParameter( "value" , Types.REAL );
+		this.fSetConstant.addParameter( "value" , Types.DOUBLE );
 		this.fSetConstant.addParameter( "admin" , Types.INTEGER );
 
 		this.tTemplate = tTemplate;
@@ -149,8 +149,8 @@ class ConstantsData
 				c.setName( rs.getString( "name" ) );
 				c.setDescription( rs.getString( "description" ) );
 				c.setValue( rs.getDouble( "value" ) );
-				c.setMinValue( (Float) rs.getObject( "min" ) );
-				c.setMaxValue( (Float) rs.getObject( "max" ) );
+				c.setMinValue( (Double) rs.getObject( "min" ) );
+				c.setMaxValue( (Double) rs.getObject( "max" ) );
 				return c;
 			}
 
@@ -609,7 +609,7 @@ class ConstantsData
 				@Override
 				protected void doInTransactionWithoutResult( TransactionStatus status )
 				{
-					fSetConstant.execute( name , value.floatValue( ) , admin );
+					fSetConstant.execute( name , value , admin );
 				}
 
 			} );
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java
index 04064d3..6a73a8c 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsManagerBean.java
@@ -1,8 +1,9 @@
 package com.deepclone.lw.beans.sys;
 
 
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.Set;
+import java.util.HashSet;
 
 import javax.sql.DataSource;
 
@@ -92,9 +93,10 @@ public class ConstantsManagerBean
 
 	/* Documented in ConstantsManager interface */
 	@Override
-	public void registerUser( ConstantsUser user , Set< String > constants )
+	public void registerUser( ConstantsUser user , String... constants )
 	{
-		this.data.registerUser( user , constants );
+		HashSet< String > cSet = new HashSet< String >( Arrays.asList( constants ) );
+		this.data.registerUser( user , cSet );
 	}
 
 
@@ -112,4 +114,5 @@ public class ConstantsManagerBean
 	{
 		return new ConstantsAdministrationImpl( this.data , admin );
 	}
+
 }
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
index 59743d9..cd8379e 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java
@@ -89,7 +89,7 @@ public class ConstantsRegistrarBean
 		// Work and income
 		String[] wcNames = {
 				"population" , "factory" , "strikeEffect" , "wuPerPopUnit" , "destructionRecovery" , "destructionWork" ,
-				"rpPerPopUnit" , "cancelRecovery"
+				"cancelRecovery"
 		};
 		for ( int i = 0 ; i < wcNames.length ; i++ ) {
 			wcNames[ i ] = "game.work." + wcNames[ i ];
@@ -107,10 +107,8 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( wcNames[ 4 ] , cat , cDesc , 0.1 , 0.01 , 0.99 ) );
 		cDesc = "Proportion of a building's construction work units required to destroy it";
 		defs.add( new ConstantDefinition( wcNames[ 5 ] , cat , cDesc , 0.25 , 0.01 , 1.0 ) );
-		cDesc = "Research points per population unit.";
-		defs.add( new ConstantDefinition( wcNames[ 6 ] , cat , cDesc , 0.50 , 0.01 , true ) );
 		cDesc = "Proportion of queue investments that is recovered when flushing the queue.";
-		defs.add( new ConstantDefinition( wcNames[ 7 ] , cat , cDesc , 0.1 , 0.01 , 1.0 ) );
+		defs.add( new ConstantDefinition( wcNames[ 6 ] , cat , cDesc , 0.1 , 0.01 , 1.0 ) );
 
 		// Vacation mode
 		cDesc = "Initial vacation credits.";
@@ -155,7 +153,7 @@ public class ConstantsRegistrarBean
 		// Ticker
 		cDesc = "Interval between ticks with the highest frequency, in milliseconds.";
 		defs.add( new ConstantDefinition( "ticker.interval" , "Ticker" , cDesc , 5000.0 , 1000.0 , true ) );
-		
+
 		// Accounts
 		cDesc = "Minimal interval between address change requests (seconds)";
 		defs.add( new ConstantDefinition( "accounts.acrDelay" , "Accounts" , cDesc , 14400.0 , 1.0 , true ) );
@@ -169,32 +167,43 @@ public class ConstantsRegistrarBean
 		defs.add( new ConstantDefinition( "accounts.banDelay" , "Accounts" , cDesc , 178000.0 , 3600.0 , true ) );
 		cDesc = "Delay before a ban request expires (seconds)";
 		defs.add( new ConstantDefinition( "accounts.banExpiration" , "Accounts" , cDesc , oneWeek , 3600.0 , true ) );
-		
+
 		// Accounts - warnings
 		cDesc = "Amount of warnings that triggers an automatic ban request.";
-		defs.add( new ConstantDefinition( "accounts.warnings.autoBan" , "Accounts - Warnings" , cDesc , 3.0 , 1.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.warnings.autoBan" , "Accounts - Warnings" , cDesc , 3.0 , 1.0 ,
+				true ) );
 		cDesc = "Period after a warning is received during which additional warnings will be ignored (seconds).";
-		defs.add( new ConstantDefinition( "accounts.warnings.grace" , "Accounts - Warnings" , cDesc , 7200.0 , 60.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.warnings.grace" , "Accounts - Warnings" , cDesc , 7200.0 , 60.0 ,
+				true ) );
 		cDesc = "Time after which warnings are decreased (expressed in units as defined by a.w.expiration.units).";
-		defs.add( new ConstantDefinition( "accounts.warnings.expiration" , "Accounts - Warnings" , cDesc , 60.0 , 1.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.warnings.expiration" , "Accounts - Warnings" , cDesc , 60.0 , 1.0 ,
+				true ) );
 		cDesc = "Units used to express warning expiration time (seconds).";
-		defs.add( new ConstantDefinition( "accounts.warnings.expiration.units" , "Accounts - Warnings" , cDesc , 86400.0 , 1.0 , true ) );
-		
+		defs.add( new ConstantDefinition( "accounts.warnings.expiration.units" , "Accounts - Warnings" , cDesc ,
+				86400.0 , 1.0 , true ) );
+
 		// Account inactivity
 		cDesc = "Time units (seconds)";
-		defs.add( new ConstantDefinition( "accounts.inactivity.units" , "Accounts - Inactivity" , cDesc , oneWeek , 3600.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.inactivity.units" , "Accounts - Inactivity" , cDesc , oneWeek ,
+				3600.0 , true ) );
 		cDesc = "Time after which the inactivity warning e-mail is to be sent, expressed using units defined by a.i.units.";
-		defs.add( new ConstantDefinition( "accounts.inactivity.warningMail" , "Accounts - Inactivity" , cDesc , 3.0 , 1.0 , true ) );
+		defs.add( new ConstantDefinition( "accounts.inactivity.warningMail" , "Accounts - Inactivity" , cDesc , 3.0 ,
+				1.0 , true ) );
 		cDesc = "Time between the inactivity warning e-mail and actual account deletion, expressed using units defined by a.i.units.";
-		defs.add( new ConstantDefinition( "accounts.inactivity.deletion" , "Accounts - Inactivity" , cDesc , 1.0 , 1.0 , true ) );
-		
+		defs.add( new ConstantDefinition( "accounts.inactivity.deletion" , "Accounts - Inactivity" , cDesc , 1.0 , 1.0 ,
+				true ) );
+
 		// Bug reports
 		cDesc = "Amount of credits granted for low priority bug reports.";
 		defs.add( new ConstantDefinition( "bugtracker.lowCredits" , "Bug tracking system" , cDesc , 1.0 , 1.0 , true ) );
 		cDesc = "Amount of credits granted for normal bug reports.";
-		defs.add( new ConstantDefinition( "bugtracker.mediumCredits" , "Bug tracking system" , cDesc , 2.0 , 1.0 , true ) );
+		defs
+				.add( new ConstantDefinition( "bugtracker.mediumCredits" , "Bug tracking system" , cDesc , 2.0 , 1.0 ,
+						true ) );
 		cDesc = "Amount of credits granted for critical bug reports.";
-		defs.add( new ConstantDefinition( "bugtracker.highCredits" , "Bug tracking system" , cDesc , 3.0 , 1.0 , true ) );
+		defs
+				.add( new ConstantDefinition( "bugtracker.highCredits" , "Bug tracking system" , cDesc , 3.0 , 1.0 ,
+						true ) );
 
 		cm.registerConstants( defs );
 	}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
index 18db00f..3d0631d 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/SystemStatusBean.java
@@ -10,8 +10,6 @@ import javax.sql.DataSource;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.core.SqlOutParameter;
-import org.springframework.jdbc.core.simple.SimpleJdbcCall;
 import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionStatus;
@@ -44,13 +42,14 @@ public class SystemStatusBean
 	/** Current maintenance mode record */
 	private MaintenanceData maintenance = null;
 
-	private SimpleJdbcCall doStartTick;
-	private SimpleJdbcCall doCheckTick;
-
 	private StoredProc fEnterMaintenanceMode;
 	private StoredProc fExtendMaintenanceMode;
 	private StoredProc fExitMaintenanceMode;
 
+	private StoredProc fCheckTick;
+	private StoredProc fStartTick;
+	private StoredProc fEndTick;
+
 
 	@Autowired( required = true )
 	public void setDataSource( DataSource dataSource )
@@ -72,16 +71,14 @@ public class SystemStatusBean
 		this.fExitMaintenanceMode.addParameter( "admin_id" , Types.INTEGER );
 		this.fExitMaintenanceMode.addOutput( "success" , Types.BOOLEAN );
 
-		this.doStartTick = new SimpleJdbcCall( dataSource );
-		this.doStartTick.withCatalogName( "sys" ).withFunctionName( "start_tick" );
-		this.doStartTick.withoutProcedureColumnMetaDataAccess( );
-		this.doStartTick.addDeclaredParameter( new SqlOutParameter( "tick_id" , Types.BIGINT ) );
+		this.fCheckTick = new StoredProc( dataSource , "sys" , "check_stuck_tick" );
+		this.fCheckTick.addOutput( "tick_id" , Types.BIGINT );
 
-		this.doCheckTick = new SimpleJdbcCall( dataSource );
-		this.doCheckTick.withCatalogName( "sys" ).withFunctionName( "check_stuck_tick" );
-		this.doCheckTick.withoutProcedureColumnMetaDataAccess( );
-		this.doCheckTick.addDeclaredParameter( new SqlOutParameter( "tick_id" , Types.BIGINT ) );
+		this.fStartTick = new StoredProc( dataSource , "sys" , "start_tick" );
+		this.fStartTick.addOutput( "tick_id" , Types.BIGINT );
 
+		this.fEndTick = new StoredProc( dataSource , "sys" , "end_tick" );
+		this.fEndTick.addParameter( "tick_id" , Types.BIGINT );
 	}
 
 
@@ -246,6 +243,31 @@ public class SystemStatusBean
 	}
 
 
+	/* Documented in interface */
+	@Override
+	public Long checkStuckTick( )
+			throws MaintenanceStatusException
+	{
+		Long tid = this.tTemplate.execute( new TransactionCallback< Long >( ) {
+
+			@Override
+			public Long doInTransaction( TransactionStatus status )
+			{
+				Map< String , Object > m = fCheckTick.execute( );
+				loadStatus( );
+				return (Long) m.get( "tick_id" );
+			}
+
+		} );
+
+		if ( tid == null && this.maintenance != null ) {
+			throw new MaintenanceStatusException( this.maintenance );
+		}
+
+		return tid;
+	}
+
+
 	/* Documented in interface */
 	@Override
 	synchronized public long startTick( )
@@ -256,7 +278,7 @@ public class SystemStatusBean
 			@Override
 			public Long doInTransaction( TransactionStatus status )
 			{
-				Map< String , Object > m = doStartTick.execute( );
+				Map< String , Object > m = fStartTick.execute( );
 				loadStatus( );
 				return (Long) m.get( "tick_id" );
 			}
@@ -277,26 +299,28 @@ public class SystemStatusBean
 
 	/* Documented in interface */
 	@Override
-	public Long checkStuckTick( )
-			throws MaintenanceStatusException
+	public void endTick( )
+			throws TickStatusException , MaintenanceStatusException
 	{
-		Long tid = this.tTemplate.execute( new TransactionCallback< Long >( ) {
-
-			@Override
-			public Long doInTransaction( TransactionStatus status )
-			{
-				Map< String , Object > m = doCheckTick.execute( );
-				loadStatus( );
-				return (Long) m.get( "tick_id" );
-			}
-
-		} );
-
-		if ( tid == null && this.maintenance != null ) {
+		if ( this.maintenance != null ) {
 			throw new MaintenanceStatusException( this.maintenance );
 		}
 
-		return tid;
+		final Long tid = this.status.getCurrentTick( );
+		if ( tid == null ) {
+			throw new TickStatusException( );
+		}
+
+		this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
+
+			@Override
+			protected void doInTransactionWithoutResult( TransactionStatus status )
+			{
+				fEndTick.execute( tid );
+				loadStatus( );
+			}
+
+		} );
 	}
 
 }
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java
index b39d795..57d810d 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/TickerBean.java
@@ -1,9 +1,6 @@
 package com.deepclone.lw.beans.sys;
 
 
-import java.util.HashSet;
-import java.util.Set;
-
 import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -101,9 +98,7 @@ public class TickerBean
 		this.mainThread = new TickerThread( this.logger , this.tickerManager );
 
 		// Register thread as a constants user
-		Set< String > use = new HashSet< String >( );
-		use.add( "ticker.interval" );
-		this.constantsManager.registerUser( this.mainThread , use );
+		this.constantsManager.registerUser( this.mainThread , "ticker.interval" );
 	}
 
 
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.classpath
new file mode 100644
index 0000000..7c3d14f
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.project
new file mode 100644
index 0000000..cc4857f
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>legacyworlds-server-beans-techs</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..c2127c6
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,6 @@
+#Mon Mar 28 09:14:01 CEST 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.settings/org.maven.ide.eclipse.prefs
new file mode 100644
index 0000000..cdf9a81
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/.settings/org.maven.ide.eclipse.prefs
@@ -0,0 +1,9 @@
+#Mon Mar 28 09:13:56 CEST 2011
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/pom.xml
new file mode 100644
index 0000000..12861da
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/pom.xml
@@ -0,0 +1,15 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>legacyworlds-server-beans</artifactId>
+		<groupId>com.deepclone.lw</groupId>
+		<version>5.99.2</version>
+	</parent>
+
+	<groupId>com.deepclone.lw</groupId>
+	<artifactId>legacyworlds-server-beans-techs</artifactId>
+	<version>5.99.2</version>
+	<name>Legacy Worlds technology management</name>
+	<description>This package contains components which are used to manage technologies in LW games.</description>
+</project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireTechnologyDAOBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireTechnologyDAOBean.java
new file mode 100644
index 0000000..924142c
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireTechnologyDAOBean.java
@@ -0,0 +1,160 @@
+package com.deepclone.lw.beans.techs;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+
+import com.deepclone.lw.interfaces.game.techs.EmpireTechnologyDAO;
+import com.deepclone.lw.sqld.game.techs.EmpireTechnology;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Implementation the empire technology and research data access component.
+ * 
+ * @author tseeker
+ */
+public class EmpireTechnologyDAOBean
+		implements EmpireTechnologyDAO
+{
+	/** SQL query that reads the list of an empire's technologies */
+	private static final String qGetTechnologies = "SELECT * FROM emp.technologies_view WHERE empire = ?";
+
+	/** SQL query that executes the priorities update preparation function */
+	private static final String qStartPrioritiesUpdate = "SELECT emp.prepare_research_priorities_update( )";
+
+	/** SQL query that inserts research priority changes into the temporary table */
+	private static final String qUploadPriorities = "INSERT INTO research_priorities_updates ( technology , priority ) "
+			+ "VALUES ( ? , ? )";
+
+	/** Data source access component */
+	private SimpleJdbcTemplate dTemplate;
+
+	/** Row mapper for empire technologies */
+	private RowMapper< EmpireTechnology > mEmpireTechnology;
+
+	/** Wrapper for the stored procedure that implements technologies */
+	private StoredProc fImplementTech;
+
+	/** Wrapper for the stored procedure to apply research priority updates */
+	private StoredProc fApplyResearchPriorities;
+
+
+	/** Initialise the row mapper */
+	public EmpireTechnologyDAOBean( )
+	{
+		this.mEmpireTechnology = new RowMapper< EmpireTechnology >( ) {
+
+			@Override
+			public EmpireTechnology mapRow( ResultSet rs , int rowNum )
+					throws SQLException
+			{
+				int techId = rs.getInt( "technology_id" );
+				String techName = rs.getString( "technology" );
+				boolean detailed = rs.getBoolean( "detailed" );
+				Integer completion = (Integer) rs.getObject( "completion" );
+				Integer cost = (Integer) rs.getObject( "cost" );
+				Integer priority = (Integer) rs.getObject( "priority" );
+
+				EmpireTechnology result;
+				if ( !detailed ) {
+					result = new EmpireTechnology( techId * rs.getInt( "empire" ) , techName , completion , priority );
+				} else if ( completion != null ) {
+					result = new EmpireTechnology( techName , completion , priority );
+				} else if ( cost != null ) {
+					result = new EmpireTechnology( techName , cost );
+				} else {
+					result = new EmpireTechnology( techName );
+				}
+				return result;
+			}
+
+		};
+	}
+
+
+	/**
+	 * Dependency injector that initialises the database access component and stored procedure
+	 * wrappers.
+	 * 
+	 * @param dataSource
+	 *            the data source
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+
+		// Stored procedure for technology implementation
+		this.fImplementTech = new StoredProc( dataSource , "emp" , "implement_tech" );
+		this.fImplementTech.addParameter( "empire_id" , Types.INTEGER );
+		this.fImplementTech.addParameter( "technology" , Types.VARCHAR );
+		this.fImplementTech.addOutput( "error_code" , Types.INTEGER );
+
+		// Stored procedure to apply research priority updates
+		this.fApplyResearchPriorities = new StoredProc( dataSource , "emp" , "apply_research_priorities" );
+		this.fApplyResearchPriorities.addParameter( "empire_id" , Types.INTEGER );
+		this.fApplyResearchPriorities.addOutput( "error_code" , Types.INTEGER );
+	}
+
+
+	/* Documentation in EmpireTechnologyDAO interface */
+	@Override
+	public List< EmpireTechnology > getTechnologies( int empireId )
+	{
+		return this.dTemplate.query( qGetTechnologies , this.mEmpireTechnology , empireId );
+	}
+
+
+	/* Documentation in EmpireTechnologyDAO interface */
+	@Override
+	public int implementTechnology( int empireId , String technology )
+	{
+		return (Integer) this.fImplementTech.execute( empireId , technology ).get( "error_code" );
+	}
+
+
+	/* Documentation in EmpireTechnologyDAO interface */
+	@Override
+	public void startPrioritiesUpdate( )
+	{
+		this.dTemplate.queryForList( qStartPrioritiesUpdate );
+	}
+
+
+	/* Documentation in EmpireTechnologyDAO interface */
+	@Override
+	public void uploadPriorities( Map< String , Integer > priorities )
+	{
+		List< Object[] > batch = new ArrayList< Object[] >( priorities.size( ) );
+		int counter = 0;
+		for ( Entry< String , Integer > entry : priorities.entrySet( ) ) {
+			Object[] values = new Object[] {
+					entry.getKey( ) , entry.getValue( )
+			};
+			batch.add( counter++ , values );
+		}
+		this.dTemplate.batchUpdate( qUploadPriorities , batch );
+	}
+
+
+	/* Documentation in EmpireTechnologyDAO interface */
+	@Override
+	public int finishPrioritiesUpdate( int empireId )
+	{
+		return (Integer) this.fApplyResearchPriorities.execute( empireId ).get( "error_code" );
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireTechnologyManagerBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireTechnologyManagerBean.java
new file mode 100644
index 0000000..ab85d96
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireTechnologyManagerBean.java
@@ -0,0 +1,181 @@
+package com.deepclone.lw.beans.techs;
+
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse;
+import com.deepclone.lw.cmd.player.research.ViewResearchResponse;
+import com.deepclone.lw.interfaces.game.EmpireManagement;
+import com.deepclone.lw.interfaces.game.techs.EmpireTechnologyDAO;
+import com.deepclone.lw.interfaces.game.techs.EmpireTechnologyManager;
+import com.deepclone.lw.interfaces.game.techs.TechnologyGraphManager;
+import com.deepclone.lw.interfaces.sys.ConstantDefinition;
+import com.deepclone.lw.interfaces.sys.ConstantsManager;
+
+
+
+/**
+ * Implementation of the empire technology and research management component.
+ * 
+ * @author tseeker
+ */
+@Transactional
+public class EmpireTechnologyManagerBean
+		implements EmpireTechnologyManager
+{
+	/** Empire management component */
+	private EmpireManagement empireManager;
+
+	/** Data access component for empire technologies and research */
+	private EmpireTechnologyDAO empireTechnologyDAO;
+
+	/** Technology graph management component */
+	private TechnologyGraphManager techGraphManager;
+
+
+	/**
+	 * Dependency injector for the empire management component.
+	 * 
+	 * @param empireManager
+	 *            the empire management component
+	 */
+	@Autowired( required = true )
+	public void setEmpireManager( EmpireManagement empireManager )
+	{
+		this.empireManager = empireManager;
+	}
+
+
+	/**
+	 * Dependency injector for the data access component for empire technologies and research
+	 * 
+	 * @param empireTechnologyDAO
+	 *            data access component for empire technologies and research
+	 */
+	@Autowired( required = true )
+	public void setEmpireTechnologyDAO( EmpireTechnologyDAO empireTechnologyDAO )
+	{
+		this.empireTechnologyDAO = empireTechnologyDAO;
+	}
+
+
+	/**
+	 * Dependency injector for the technology graph management component.
+	 * 
+	 * @param techGraphManager
+	 *            the technology graph management component
+	 */
+	@Autowired( required = true )
+	public void setTechnologyGraphManager( TechnologyGraphManager techGraphManager )
+	{
+		this.techGraphManager = techGraphManager;
+	}
+
+
+	/**
+	 * Dependency injector which registers research-related constants into the constants manager.
+	 * 
+	 * @param constantsManager
+	 *            the constants manager component
+	 */
+	@Autowired( required = true )
+	public void setConstantsManager( ConstantsManager constantsManager )
+	{
+		ConstantDefinition[] definitions = new ConstantDefinition[] {
+				new ConstantDefinition( "game.research.minPoints" , "Research" ,
+						"Minimal research points before a technology being researched is identified" , 50000.0 , 1.0 ,
+						true ) ,
+				new ConstantDefinition( "game.research.minRatio" , "Research" ,
+						"Minimal ratio before a technology being researched is identified" , 0.75 , 0.01 , 0.99 ) ,
+				new ConstantDefinition( "game.research.perPopUnit" , "Research" ,
+						"Research points per population unit." , 0.50 , 0.01 , true )
+		};
+		constantsManager.registerConstants( Arrays.asList( definitions ) );
+	}
+
+
+	/* Documentation in EmpireTechnologyManager interface */
+	@Override
+	public ViewResearchResponse getResearchData( int empireId )
+	{
+		return new ResearchResponseBuilder( empireId , this.empireManager , this.empireTechnologyDAO ,
+				this.techGraphManager ).getResponse( );
+	}
+
+
+	/* Documentation in EmpireTechnologyManager interface */
+	@Override
+	public ResearchOperationResponse implementTechnology( int empireId , String technology )
+	{
+		int result = this.empireTechnologyDAO.implementTechnology( empireId , technology );
+		ResearchOperationResponse response;
+
+		if ( result == 0 ) {
+			response = new ResearchOperationResponse( );
+		} else {
+			ResearchResponseBuilder builder = new ResearchResponseBuilder( empireId , this.empireManager ,
+					this.empireTechnologyDAO , this.techGraphManager );
+			switch ( result ) {
+
+				case 1:
+					response = builder.getResponse( ResearchOperationResponse.Result.ERR_RESOURCES );
+					break;
+
+				case 2:
+					response = builder.getResponse( ResearchOperationResponse.Result.ERR_STATE_CHANGED );
+					break;
+
+				default:
+					throw new RuntimeException( "unsupported return value " + result );
+
+			}
+		}
+
+		return response;
+	}
+
+
+	/* Documentation in EmpireTechnologyManager interface */
+	@Override
+	public ResearchOperationResponse setResearchPriorities( int empireId , Map< String , Integer > priorities )
+	{
+		int result;
+		if ( priorities.size( ) < 2 ) {
+			result = 2;
+		} else {
+			this.empireTechnologyDAO.startPrioritiesUpdate( );
+			this.empireTechnologyDAO.uploadPriorities( priorities );
+			result = this.empireTechnologyDAO.finishPrioritiesUpdate( empireId );
+		}
+
+		ResearchOperationResponse response;
+		if ( result == 0 ) {
+			response = new ResearchOperationResponse( );
+		} else {
+			ResearchResponseBuilder builder = new ResearchResponseBuilder( empireId , this.empireManager ,
+					this.empireTechnologyDAO , this.techGraphManager );
+
+			switch ( result ) {
+
+				case 1:
+					response = builder.getResponse( ResearchOperationResponse.Result.ERR_STATE_CHANGED );
+					break;
+
+				case 2:
+					response = builder.getResponse( ResearchOperationResponse.Result.ERR_INVALID );
+					break;
+
+				default:
+					throw new RuntimeException( "unsupported return value " + result );
+
+			}
+		}
+
+		return response;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireUpdate.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireUpdate.java
new file mode 100644
index 0000000..c32ebba
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/EmpireUpdate.java
@@ -0,0 +1,293 @@
+package com.deepclone.lw.beans.techs;
+
+
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.deepclone.lw.cmd.admin.logs.LogLevel;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+import com.deepclone.lw.sqld.game.techs.ResearchStatus;
+import com.deepclone.lw.sqld.game.techs.ResearchUpdateInput;
+import com.deepclone.lw.sqld.game.techs.ResearchUpdateOutput;
+import com.deepclone.lw.sqld.game.techs.TechGraph;
+import com.deepclone.lw.sqld.game.techs.TechGraphException;
+import com.deepclone.lw.sqld.game.techs.Technology;
+
+
+
+/**
+ * Research update implementation for a single empire.
+ * 
+ * @author tseeker
+ */
+class EmpireUpdate
+{
+	/** Empire identifier */
+	private final int id;
+
+	/** Research points produced */
+	private double production;
+
+	/** All technologies (known or being researched) */
+	private final Map< String , ResearchUpdateInput > byName;
+
+	/** Technology names by status */
+	private final Map< ResearchStatus , Set< String >> byStatus;
+
+	/** Output records by name */
+	private final Map< String , ResearchUpdateOutput > output;
+
+	/** Sum of the current research priorities */
+	private double totalPriority;
+
+	/** Amount of active research topics */
+	private int activeTopics;
+
+	/** Parent component's logging interface */
+	private SystemLogger logger;
+
+
+	/**
+	 * Initialise the empire updater using the global lists of production points and research
+	 * statuses.
+	 * 
+	 * @param logger
+	 *            the component's logging system
+	 * @param empireId
+	 *            the empire for whom the research update is to be computed
+	 * @param production
+	 *            the map of production points for all empires in the batch
+	 * @param status
+	 *            the list of statuses for all empires in the batch
+	 */
+	EmpireUpdate( SystemLogger logger , int empireId , Map< Integer , Double > production ,
+			HashMap< Integer , List< ResearchUpdateInput >> status )
+	{
+		this.id = empireId;
+		this.production = production.get( this.id );
+		this.logger = logger;
+
+		this.byName = new HashMap< String , ResearchUpdateInput >( );
+		this.output = new HashMap< String , ResearchUpdateOutput >( );
+
+		this.byStatus = new EnumMap< ResearchStatus , Set< String > >( ResearchStatus.class );
+		this.byStatus.put( ResearchStatus.IN_PROGRESS , new HashSet< String >( ) );
+		this.byStatus.put( ResearchStatus.RESEARCHED , new HashSet< String >( ) );
+		this.byStatus.put( ResearchStatus.IMPLEMENTED , new HashSet< String >( ) );
+
+		// Initialise sets and maps
+		List< ResearchUpdateInput > empStatus = status.get( this.id );
+		if ( empStatus != null ) {
+			for ( ResearchUpdateInput entry : status.get( this.id ) ) {
+				this.byName.put( entry.getTechnology( ) , entry );
+				this.byStatus.get( entry.getStatus( ) ).add( entry.getTechnology( ) );
+			}
+			this.logger.flush( );
+		}
+	}
+
+
+	/**
+	 * Perform the research update, filling the {@link #output} map.
+	 * 
+	 * @param techGraph
+	 *            the technology graph
+	 * @throws TechGraphException
+	 *             indicates an internal error
+	 */
+	void compute( TechGraph techGraph )
+			throws TechGraphException
+	{
+		if ( this.production == 0 ) {
+			return;
+		}
+		this.prepareOutput( );
+
+		List< Technology > newResearch = this.findNewTechnologies( techGraph );
+		if ( !newResearch.isEmpty( ) ) {
+			this.addResearch( newResearch );
+		}
+		if ( this.output.isEmpty( ) ) {
+			return;
+		}
+
+		List< String > inProgress = new LinkedList< String >( this.output.keySet( ) );
+		for ( String resName : inProgress ) {
+			this.researchProgress( techGraph.getTechnology( resName ) , resName );
+		}
+
+		this.updatePriorities( );
+	}
+
+
+	/**
+	 * Copy information from the list of research to the output map.
+	 */
+	private void prepareOutput( )
+	{
+		this.totalPriority = 0;
+		for ( ResearchUpdateInput entry : this.byName.values( ) ) {
+			if ( entry.getStatus( ) != ResearchStatus.IN_PROGRESS ) {
+				continue;
+			}
+			this.output.put( entry.getTechnology( ) , new ResearchUpdateOutput( this.id , entry.getTechnology( ) ,
+					false , entry.getPoints( ) , entry.getPriority( ) ) );
+			this.totalPriority += entry.getPriority( );
+		}
+		this.activeTopics = this.byStatus.get( ResearchStatus.IN_PROGRESS ).size( );
+	}
+
+
+	/**
+	 * Find new technologies to research
+	 * 
+	 * XXX: we shouldn't be doing that at every update; it's only required if the techs were added
+	 * or deleted, if dependencies were changed or if the empire implemented a technology.
+	 * 
+	 * @param techGraph
+	 *            the technology graph
+	 * @return the list of technologies on which research can start
+	 * @throws TechGraphException
+	 *             indicates an internal error
+	 */
+	private List< Technology > findNewTechnologies( TechGraph techGraph )
+			throws TechGraphException
+	{
+		LinkedList< Technology > newResearch = new LinkedList< Technology >( );
+		Set< String > implemented = this.byStatus.get( ResearchStatus.IMPLEMENTED );
+		if ( implemented == null ) {
+			throw new RuntimeException( "wtf?!" );
+		}
+		for ( String catName : techGraph.getCategories( ) ) {
+			for ( String techName : techGraph.getCategory( catName ).getTechnologies( ) ) {
+				// Check if the empire "knows" of the tech
+				if ( this.byName.containsKey( techName ) ) {
+					continue;
+				}
+
+				// Check dependencies
+				Technology tech = techGraph.getTechnology( techName );
+				boolean mayStart = true;
+				for ( String depName : tech.getDependencies( ) ) {
+					if ( !implemented.contains( depName ) ) {
+						mayStart = false;
+						break;
+					}
+				}
+
+				if ( mayStart ) {
+					newResearch.add( tech );
+				}
+			}
+		}
+		return newResearch;
+	}
+
+
+	/**
+	 * Add new entries to the list of current research topics and updates the total priority
+	 * accordingly.
+	 * 
+	 * @param newResearch
+	 *            the list of research topics to add
+	 */
+	private void addResearch( List< Technology > newResearch )
+	{
+		int increment;
+		if ( this.totalPriority == 0 ) {
+			increment = 1;
+		} else {
+			increment = (int) Math.ceil( this.totalPriority / this.activeTopics );
+		}
+
+		for ( Technology tech : newResearch ) {
+			this.output.put( tech.getName( ) , new ResearchUpdateOutput( this.id , tech.getName( ) , true , 0 ,
+					increment ) );
+			this.totalPriority += increment;
+		}
+		this.activeTopics += newResearch.size( );
+	}
+
+
+	/**
+	 * Update the progress on a research topic.
+	 * 
+	 * @param technology
+	 *            the technology's definition
+	 * @param name
+	 *            the name of the research topic
+	 */
+	private void researchProgress( Technology technology , String name )
+	{
+		ResearchUpdateOutput entry = this.output.get( name );
+		if ( entry.getPriority( ) == 0 ) {
+			return;
+		}
+
+		double points = this.production * entry.getPriority( ) / this.totalPriority;
+		double total = points + entry.getPoints( );
+
+		if ( total >= technology.getPoints( ) ) {
+			this.totalPriority -= entry.getPriority( );
+			this.production = this.production + total - entry.getPoints( );
+			this.output.put( name , new ResearchUpdateOutput( this.id , name ) );
+			this.activeTopics--;
+		} else {
+			entry.addPoints( points );
+		}
+	}
+
+
+	/**
+	 * Update priorities of active research topics in the output map.
+	 */
+	private void updatePriorities( )
+	{
+		if ( this.activeTopics == 0 || this.totalPriority == 100 ) {
+			return;
+		}
+
+		List< ResearchUpdateOutput > progress = new LinkedList< ResearchUpdateOutput >( );
+		int newTotal = 0;
+		for ( ResearchUpdateOutput update : this.output.values( ) ) {
+			Integer prio = update.getPriority( );
+			if ( prio == null ) {
+				continue;
+			}
+
+			int newValue = (int) Math.floor( 100.0 * prio / this.totalPriority );
+			progress.add( update );
+			update.setPriority( newValue );
+			newTotal += newValue;
+		}
+
+		// Distribute the rest of the points
+		while ( newTotal < 100 ) {
+			for ( ResearchUpdateOutput update : progress ) {
+				update.setPriority( update.getPriority( ) + 1 );
+				newTotal++;
+				if ( newTotal == 100 ) {
+					break;
+				}
+			}
+		}
+		this.logger.flush( );
+	}
+
+
+	/** @return the update output records */
+	Collection< ResearchUpdateOutput > getOutput( )
+	{
+		if ( !this.output.isEmpty( ) ) {
+			this.logger.log( LogLevel.TRACE , "Empire " + this.id + " - " + this.output.size( ) + " update(s)" )
+					.flush( );
+		}
+		return this.output.values( );
+	}
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchResponseBuilder.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchResponseBuilder.java
new file mode 100644
index 0000000..1c24a1f
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchResponseBuilder.java
@@ -0,0 +1,280 @@
+package com.deepclone.lw.beans.techs;
+
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+import com.deepclone.lw.cmd.player.gdata.empire.TechnologyCategoryData;
+import com.deepclone.lw.cmd.player.gdata.empire.TechnologyData;
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse;
+import com.deepclone.lw.cmd.player.research.ViewResearchResponse;
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse.Result;
+import com.deepclone.lw.interfaces.game.EmpireManagement;
+import com.deepclone.lw.interfaces.game.techs.EmpireTechnologyDAO;
+import com.deepclone.lw.interfaces.game.techs.TechnologyGraphManager;
+import com.deepclone.lw.interfaces.i18n.LanguageTranslator;
+import com.deepclone.lw.interfaces.i18n.UnknownStringException;
+import com.deepclone.lw.sqld.game.techs.Category;
+import com.deepclone.lw.sqld.game.techs.EmpireTechnology;
+import com.deepclone.lw.sqld.game.techs.ResearchStatus;
+import com.deepclone.lw.sqld.game.techs.TechGraph;
+import com.deepclone.lw.sqld.game.techs.TechGraphException;
+import com.deepclone.lw.sqld.game.techs.Technology;
+
+
+
+/**
+ * Class that builds {@link ViewResearchResponse} or {@link ResearchOperationResponse} instances.
+ * 
+ * @author tseeker
+ */
+class ResearchResponseBuilder
+{
+
+	/** Empire identifier */
+	private final int empireId;
+
+	/** Empire management component */
+	private final EmpireManagement empireManager;
+
+	/** Technology and research data access component */
+	private final EmpireTechnologyDAO empireTechnologyDAO;
+
+	/** Technology graph management component */
+	private final TechnologyGraphManager techGraphManager;
+
+	/** Game page data */
+	private GamePageData pageData;
+
+	/** List of current research topics */
+	private List< ResearchData > research;
+
+	/** Lists of implemented technologies by category */
+	private Map< String , List< TechnologyData > > implementedMap;
+
+	/** Final list of implemented technologies */
+	private List< TechnologyCategoryData > implementedLists;
+
+	/** All implemented technologies by identifier */
+	private Map< String , TechnologyData > implementedById;
+
+
+	/**
+	 * Initialise the response builder by setting the empire identifier and the various components
+	 * that may be required to generate the response.
+	 * 
+	 * @param empireId
+	 *            the empire identifier
+	 * @param empireManager
+	 *            the empire management component
+	 * @param empireTechnologyDAO
+	 *            the technology and research data access component
+	 * @param techGraphManager
+	 *            the technology graph management component
+	 */
+	ResearchResponseBuilder( int empireId , EmpireManagement empireManager , EmpireTechnologyDAO empireTechnologyDAO ,
+			TechnologyGraphManager techGraphManager )
+	{
+		this.empireId = empireId;
+		this.empireManager = empireManager;
+		this.empireTechnologyDAO = empireTechnologyDAO;
+		this.techGraphManager = techGraphManager;
+	}
+
+
+	/**
+	 * Build a basic research view response.
+	 * 
+	 * @return the research view response.
+	 */
+	ViewResearchResponse getResponse( )
+	{
+		try {
+			this.buildResponseContents( );
+		} catch ( Exception e ) {
+			throw new RuntimeException( "internal error while processing research data" , e );
+		}
+		return new ViewResearchResponse( this.pageData , this.research , this.implementedLists );
+	}
+
+
+	/**
+	 * Build a research operation response.
+	 * 
+	 * @param errorCode
+	 *            the operation's error code (should not be
+	 *            {@link ResearchOperationResponse.Result#OK}).
+	 * @return the research operation response.
+	 */
+	ResearchOperationResponse getResponse( Result errorCode )
+	{
+		try {
+			this.buildResponseContents( );
+		} catch ( Exception e ) {
+			throw new RuntimeException( "internal error while processing research data" , e );
+		}
+		return new ResearchOperationResponse( this.pageData , this.research , this.implementedLists , errorCode );
+	}
+
+
+	/**
+	 * Build the lists and records required by research view or operation responses. This includes
+	 * the list of current research topics as well as the list of implemented technologies.
+	 * 
+	 * @throws UnknownStringException
+	 *             if there is an incoherence between the tech graph and the translations database.
+	 * @throws TechGraphException
+	 *             if there is an incoherence between the empire research records and the tech
+	 *             graph.
+	 */
+	private void buildResponseContents( )
+			throws UnknownStringException , TechGraphException
+	{
+		LanguageTranslator translator = this.empireManager.getTranslator( this.empireId );
+		TechGraph techGraph = this.techGraphManager.getGraph( );
+
+		this.research = new LinkedList< ResearchData >( );
+		this.implementedMap = new HashMap< String , List< TechnologyData > >( );
+		this.implementedById = new HashMap< String , TechnologyData >( );
+
+		for ( EmpireTechnology record : this.empireTechnologyDAO.getTechnologies( this.empireId ) ) {
+			if ( record.getStatus( ) == ResearchStatus.IMPLEMENTED ) {
+				this.addImplemented( record , translator , techGraph );
+			} else {
+				this.addResearchData( record , translator , techGraph );
+			}
+		}
+
+		this.pageData = this.empireManager.getGeneralInformation( this.empireId );
+		Collections.sort( this.research );
+		this.postProcessImplemented( translator , techGraph );
+	}
+
+
+	/**
+	 * Add an implemented technology record.
+	 * 
+	 * @param record
+	 *            the original record as returned by the {@link EmpireTechnologyDAO}.
+	 * @param translator
+	 *            the translator for the empire's language
+	 * @param techGraph
+	 *            the technology graph
+	 * 
+	 * @throws TechGraphException
+	 *             if there is an incoherence between the empire research records and the tech
+	 *             graph.
+	 * @throws UnknownStringException
+	 *             if there is an incoherence between the tech graph and the translations database.
+	 */
+	private void addImplemented( EmpireTechnology record , LanguageTranslator translator , TechGraph techGraph )
+			throws TechGraphException , UnknownStringException
+	{
+		String techId = record.getIdentifier( );
+		Technology tech = techGraph.getTechnology( techId );
+		Category category = tech.getCategory( );
+
+		List< TechnologyData > catTechs = this.implementedMap.get( category.getName( ) );
+		if ( catTechs == null ) {
+			catTechs = new LinkedList< TechnologyData >( );
+			this.implementedMap.put( category.getName( ) , catTechs );
+		}
+
+		TechnologyData tData;
+		String name = translator.translate( techId );
+		String description = translator.translate( tech.getDescription( ) );
+		tData = new TechnologyData( techId , name , description , tech.getDependencies( ) , new LinkedList< String >( ) );
+
+		catTechs.add( tData );
+		this.implementedById.put( techId , tData );
+	}
+
+
+	/**
+	 * Add a research topic record.
+	 * 
+	 * @param record
+	 *            the original record as returned by the {@link EmpireTechnologyDAO}.
+	 * @param translator
+	 *            the translator for the empire's language
+	 * @param techGraph
+	 *            the technology graph
+	 * 
+	 * @throws TechGraphException
+	 *             if there is an incoherence between the empire research records and the tech
+	 *             graph.
+	 * @throws UnknownStringException
+	 *             if there is an incoherence between the tech graph and the translations database.
+	 */
+	private void addResearchData( EmpireTechnology record , LanguageTranslator translator , TechGraph techGraph )
+			throws TechGraphException , UnknownStringException
+	{
+		String techId = record.getIdentifier( );
+		Technology tech = techGraph.getTechnology( techId );
+		String category = translator.translate( tech.getCategory( ).getName( ) );
+
+		ResearchData rData;
+
+		if ( !record.isDetailed( ) ) {
+			rData = new ResearchData( record.getNumericId( ) , category , record.getPercentage( ) , record
+					.getPriority( ) );
+		} else {
+			String name = translator.translate( techId );
+			String description = translator.translate( tech.getDescription( ) );
+			String[] dependencies = tech.getDependencies( ).toArray( new String[] { } );
+			if ( record.getStatus( ) == ResearchStatus.RESEARCHED ) {
+				rData = new ResearchData( techId , category , name , description , dependencies , (int) tech.getCost( ) );
+			} else {
+				rData = new ResearchData( techId , category , name , description , dependencies , record
+						.getPercentage( ) , record.getPriority( ) );
+			}
+		}
+
+		this.research.add( rData );
+	}
+
+
+	/**
+	 * Post-process implemented technologies by adding reverse dependencies, sorting each list, and
+	 * generating the sorted list of technology categories.
+	 * 
+	 * @param translator
+	 *            the translator for the empire's language
+	 * @param techGraph
+	 *            the technology graph
+	 * 
+	 * @throws TechGraphException
+	 *             if there is an incoherence between the empire research records and the tech
+	 *             graph.
+	 * @throws UnknownStringException
+	 *             if there is an incoherence between the tech graph and the translations database.
+	 */
+	private void postProcessImplemented( LanguageTranslator translator , TechGraph techGraph )
+			throws UnknownStringException , TechGraphException
+	{
+		this.implementedLists = new LinkedList< TechnologyCategoryData >( );
+		for ( String catId : this.implementedMap.keySet( ) ) {
+
+			List< TechnologyData > techs = this.implementedMap.get( catId );
+			for ( TechnologyData tData : techs ) {
+				for ( String dependency : tData.getDependsOn( ) ) {
+					TechnologyData depData = this.implementedById.get( dependency );
+					if ( depData != null ) {
+						depData.getDependencyOf( ).add( tData.getIdentifier( ) );
+					}
+				}
+			}
+			Collections.sort( techs );
+
+			String catName = translator.translate( catId );
+			String catDesc = translator.translate( techGraph.getCategory( catId ).getDescription( ) );
+			this.implementedLists.add( new TechnologyCategoryData( catName , catDesc , techs ) );
+		}
+		Collections.sort( this.implementedLists );
+	}
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchUpdateBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchUpdateBean.java
new file mode 100644
index 0000000..f499a3a
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchUpdateBean.java
@@ -0,0 +1,180 @@
+package com.deepclone.lw.beans.techs;
+
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.interfaces.eventlog.Logger;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+import com.deepclone.lw.interfaces.game.techs.ResearchUpdateDAO;
+import com.deepclone.lw.interfaces.game.techs.TechnologyGraphManager;
+import com.deepclone.lw.interfaces.game.updates.GameUpdate;
+import com.deepclone.lw.interfaces.game.updates.GameUpdatePhase;
+import com.deepclone.lw.interfaces.game.updates.GameUpdatePhaseHandler;
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.sqld.game.techs.ResearchUpdateInput;
+import com.deepclone.lw.sqld.game.techs.ResearchUpdateOutput;
+import com.deepclone.lw.sqld.game.techs.TechGraph;
+import com.deepclone.lw.sqld.game.techs.TechGraphException;
+
+
+
+/**
+ * Component responsible for updating empires' research status.
+ * 
+ * @author tseeker
+ */
+public class ResearchUpdateBean
+		implements GameUpdatePhaseHandler
+{
+	/** The component's logging interface */
+	private SystemLogger logger;
+
+	/** The technology graph management component */
+	private TechnologyGraphManager techGraphManager;
+
+	/** The current technology graph */
+	private TechGraph techGraph;
+
+	/** The main game update data access component */
+	private UpdatesDAO updatesDAO;
+
+	/** The research updates data access component */
+	private ResearchUpdateDAO researchUpdateDAO;
+
+
+	/**
+	 * Dependency injector that initialises the component's logging interface.
+	 * 
+	 * @param logger
+	 *            the system logger
+	 */
+	@Autowired( required = true )
+	public void setLogger( Logger logger )
+	{
+		this.logger = logger.getSystemLogger( "ResearchUpdate" );
+	}
+
+
+	/**
+	 * Dependency injector for the technology graph management component.
+	 * 
+	 * @param techGraphManager
+	 *            the technology graph management component
+	 */
+	@Autowired( required = true )
+	public void setTechGraphManager( TechnologyGraphManager techGraphManager )
+	{
+		this.techGraphManager = techGraphManager;
+	}
+
+
+	/**
+	 * Dependency injector that sets the main game update data access component.
+	 * 
+	 * @param updatesDAO
+	 *            the main game update data access component
+	 */
+	@Autowired( required = true )
+	public void setUpdatesDAO( UpdatesDAO updatesDAO )
+	{
+		this.updatesDAO = updatesDAO;
+	}
+
+
+	/**
+	 * Dependency injector that sets the research updates data access component
+	 * 
+	 * @param researchUpdateDAO
+	 *            the research updates data access component
+	 */
+	@Autowired( required = true )
+	public void setResearchUpdateDAO( ResearchUpdateDAO researchUpdateDAO )
+	{
+		this.researchUpdateDAO = researchUpdateDAO;
+	}
+
+
+	/**
+	 * Dependency injector that registers the handler with the main game update manager
+	 * 
+	 * @param updateManager
+	 *            the game update manager
+	 */
+	@Autowired( required = true )
+	public void setUpdateManager( GameUpdate updateManager )
+	{
+		updateManager.registerHandler( this );
+	}
+
+
+	/* Documentation in GameUpdatePhaseHandler interface */
+	@Override
+	public GameUpdatePhase getPhase( )
+	{
+		return GameUpdatePhase.EMPIRE_RESEARCH;
+	}
+
+
+	/**
+	 * Get a copy of the technology graph when the update phase starts.
+	 */
+	@Override
+	public void onPhaseStart( long updateId )
+	{
+		this.techGraph = this.techGraphManager.getGraph( );
+	}
+
+
+	/**
+	 * If there are research updates left to process, lock the records, then obtain all relevant
+	 * data, process each empire using {@link EmpireUpdate}, and send the updates to the database.
+	 */
+	@Override
+	public boolean updateGame( long updateId )
+	{
+		// Look for records to update
+		if ( !this.updatesDAO.prepareUpdates( updateId , this.getPhase( ) ) ) {
+			return false;
+		}
+
+		Map< Integer , Double > production;
+		HashMap< Integer , List< ResearchUpdateInput >> status;
+		status = new HashMap< Integer , List< ResearchUpdateInput > >( );
+
+		// Prepare update and load input
+		this.researchUpdateDAO.prepareUpdate( updateId );
+		production = this.researchUpdateDAO.getResearchPoints( updateId );
+		for ( ResearchUpdateInput input : this.researchUpdateDAO.getUpdateData( updateId ) ) {
+			List< ResearchUpdateInput > eStatus = status.get( input.getEmpireId( ) );
+			if ( eStatus == null ) {
+				eStatus = new LinkedList< ResearchUpdateInput >( );
+				status.put( input.getEmpireId( ) , eStatus );
+			}
+			eStatus.add( input );
+		}
+
+		// Handle the update for each empire
+		List< ResearchUpdateOutput > output = new LinkedList< ResearchUpdateOutput >( );
+		for ( int empireId : production.keySet( ) ) {
+			EmpireUpdate update = new EmpireUpdate( this.logger , empireId , production , status );
+			try {
+				update.compute( this.techGraph );
+			} catch ( TechGraphException e ) {
+				throw new RuntimeException( "incoherent technology graph" , e );
+			}
+			output.addAll( update.getOutput( ) );
+		}
+
+		// Send and validate updates
+		this.researchUpdateDAO.submitUpdateData( output );
+		this.updatesDAO.validateUpdatedRecords( updateId , GameUpdatePhase.EMPIRE_RESEARCH );
+
+		return true;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchUpdateDAOBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchUpdateDAOBean.java
new file mode 100644
index 0000000..b50d807
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/ResearchUpdateDAOBean.java
@@ -0,0 +1,138 @@
+package com.deepclone.lw.beans.techs;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.SqlParameterSource;
+import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+
+import com.deepclone.lw.interfaces.game.techs.ResearchUpdateDAO;
+import com.deepclone.lw.sqld.game.techs.ResearchUpdateInput;
+import com.deepclone.lw.sqld.game.techs.ResearchUpdateOutput;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+public class ResearchUpdateDAOBean
+		implements ResearchUpdateDAO
+{
+	/** Query that reads research update input */
+	private static final String qGetInput = "SELECT * FROM emp.research_update_input_view WHERE update_id = ?";
+
+	/** Query that reads empire research points production */
+	private static final String qGetPoints = "SELECT * FROM emp.research_points_production WHERE update_id = ?";
+
+	/** Query that adds a record to the output */
+	private static final String qAddOutput = "INSERT INTO research_update_output "
+			+ "(empire_id , technology , creation , points , priority) "
+			+ "VALUES ( :empireId , :technology , :creation , :points , :priority )";
+
+	/** Query that submits the update */
+	private static final String qSubmitUpdate = "SELECT emp.submit_research_update( )";
+
+	/** Data source access */
+	private SimpleJdbcTemplate dTemplate;
+
+	/** Wrapper for the stored procedure that prepares the database for update */
+	private StoredProc fPrepareUpdate;
+
+	/** Row mapper for research update input */
+	RowMapper< ResearchUpdateInput > ruiMapper;
+
+
+	/**
+	 * Initialise the component's row mapper.
+	 */
+	public ResearchUpdateDAOBean( )
+	{
+		this.ruiMapper = new RowMapper< ResearchUpdateInput >( ) {
+			@Override
+			public ResearchUpdateInput mapRow( ResultSet rs , int rowNum )
+					throws SQLException
+			{
+				int empireId = rs.getInt( "empire_id" );
+				String technology = rs.getString( "technology" );
+
+				Boolean implemented = (Boolean) rs.getObject( "implemented" );
+				if ( implemented == null ) {
+					double points = rs.getDouble( "points" );
+					int priority = rs.getInt( "priority" );
+					return new ResearchUpdateInput( empireId , technology , points , priority );
+				}
+
+				return new ResearchUpdateInput( empireId , technology , implemented );
+			}
+		};
+	}
+
+
+	/**
+	 * Dependency injector that initialises the component's database access and stored procedure
+	 * wrappers.
+	 * 
+	 * @param dataSource
+	 *            the data source
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.dTemplate = new SimpleJdbcTemplate( dataSource );
+
+		// Stored procedure that prepares the database for update
+		this.fPrepareUpdate = new StoredProc( dataSource , "emp" , "prepare_research_update" );
+		this.fPrepareUpdate.addParameter( "update_id" , Types.BIGINT );
+	}
+
+
+	/* Documented in ResearchUpdateDAO interface */
+	@Override
+	public void prepareUpdate( long updateId )
+	{
+		this.fPrepareUpdate.execute( updateId );
+	}
+
+
+	/* Documented in ResearchUpdateDAO interface */
+	@Override
+	public List< ResearchUpdateInput > getUpdateData( long updateId )
+	{
+		return this.dTemplate.query( qGetInput , this.ruiMapper , updateId );
+	}
+
+
+	/* Documented in ResearchUpdateDAO interface */
+	@Override
+	public Map< Integer , Double > getResearchPoints( long updateId )
+	{
+		Map< Integer , Double > result = new HashMap< Integer , Double >( );
+
+		for ( Map< String , Object > row : this.dTemplate.queryForList( qGetPoints , updateId ) ) {
+			result.put( (Integer) row.get( "empire_id" ) , (Double) row.get( "points" ) );
+		}
+
+		return result;
+	}
+
+
+	/* Documented in ResearchUpdateDAO interface */
+	@Override
+	public void submitUpdateData( List< ResearchUpdateOutput > output )
+	{
+		if ( !output.isEmpty( ) ) {
+			SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch( output.toArray( ) );
+			this.dTemplate.batchUpdate( qAddOutput , batch );
+		}
+		this.dTemplate.queryForList( qSubmitUpdate );
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/TechnologyGraphDAOBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/TechnologyGraphDAOBean.java
new file mode 100644
index 0000000..f716b5a
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/TechnologyGraphDAOBean.java
@@ -0,0 +1,134 @@
+package com.deepclone.lw.beans.techs;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowCallbackHandler;
+
+import com.deepclone.lw.interfaces.game.techs.TechnologyGraphDAO;
+import com.deepclone.lw.sqld.game.techs.TechGraph;
+import com.deepclone.lw.sqld.game.techs.TechGraphException;
+
+
+
+public class TechnologyGraphDAOBean
+		implements TechnologyGraphDAO
+{
+	private static final String qGetCategories = "SELECT name , description FROM tech.categories_view";
+	private static final String qGetTechnologies = "SELECT category , name , description , points , cost FROM tech.technologies_view";
+	private static final String qGetDependencies = "SELECT technology , dependency FROM tech.dependencies_view";
+
+	private static abstract class TechGraphDBHandler
+			implements RowCallbackHandler
+	{
+		TechGraph graph;
+		TechGraphException error;
+
+
+		public TechGraphDBHandler( )
+		{
+			// EMPTY
+		}
+
+
+		public final void setGraph( TechGraph graph )
+		{
+			this.graph = graph;
+		}
+
+
+		@Override
+		public final void processRow( ResultSet rs )
+				throws SQLException
+		{
+			if ( this.error != null ) {
+				return;
+			}
+
+			try {
+				this.extractData( rs );
+			} catch ( TechGraphException e ) {
+				this.error = e;
+			}
+		}
+
+
+		protected abstract void extractData( ResultSet rs )
+				throws TechGraphException , SQLException;
+
+	}
+
+	private static class CategoryExtractor
+			extends TechGraphDBHandler
+	{
+		@Override
+		protected void extractData( ResultSet rs )
+				throws TechGraphException , SQLException
+		{
+			this.graph.addCategory( rs.getString( "name" ) , rs.getString( "description" ) );
+		}
+
+	}
+
+	private static class TechnologyExtractor
+			extends TechGraphDBHandler
+	{
+		@Override
+		protected void extractData( ResultSet rs )
+				throws TechGraphException , SQLException
+		{
+			this.graph.addTechnology( rs.getString( "category" ) , rs.getString( "name" ) , rs
+					.getString( "description" ) , rs.getInt( "points" ) , rs.getInt( "cost" ) );
+		}
+	}
+
+	private static class DependencyExtractor
+			extends TechGraphDBHandler
+	{
+		@Override
+		protected void extractData( ResultSet rs )
+				throws TechGraphException , SQLException
+		{
+			this.graph.addDependency( rs.getString( "technology" ) , rs.getString( "dependency" ) );
+		}
+
+	}
+
+	private JdbcTemplate dTemplate;
+
+
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		this.dTemplate = new JdbcTemplate( dataSource );
+	}
+
+
+	@Override
+	public TechGraph loadGraph( )
+			throws TechGraphException
+	{
+		TechGraph result = new TechGraph( );
+		this.extractGraphData( result , new CategoryExtractor( ) , qGetCategories );
+		this.extractGraphData( result , new TechnologyExtractor( ) , qGetTechnologies );
+		this.extractGraphData( result , new DependencyExtractor( ) , qGetDependencies );
+		return result;
+	}
+
+
+	private void extractGraphData( TechGraph graph , TechGraphDBHandler extractor , String query )
+			throws TechGraphException
+	{
+		extractor.setGraph( graph );
+		this.dTemplate.query( query , extractor );
+		if ( extractor.error != null ) {
+			throw extractor.error;
+		}
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/TechnologyGraphManagerBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/TechnologyGraphManagerBean.java
new file mode 100644
index 0000000..2699fdb
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/java/com/deepclone/lw/beans/techs/TechnologyGraphManagerBean.java
@@ -0,0 +1,74 @@
+package com.deepclone.lw.beans.techs;
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.deepclone.lw.cmd.admin.techs.TechCategory;
+import com.deepclone.lw.interfaces.game.techs.TechnologyGraphDAO;
+import com.deepclone.lw.interfaces.game.techs.TechnologyGraphManager;
+import com.deepclone.lw.sqld.game.techs.Category;
+import com.deepclone.lw.sqld.game.techs.TechGraph;
+import com.deepclone.lw.sqld.game.techs.TechGraphException;
+
+
+
+@Transactional
+public class TechnologyGraphManagerBean
+		implements TechnologyGraphManager
+{
+	private TechGraph graph;
+	private TechnologyGraphDAO technologyGraphDAO;
+
+
+	@Autowired( required = true )
+	public void setTechGraphDAO( TechnologyGraphDAO technologyGraphDAO )
+	{
+		this.technologyGraphDAO = technologyGraphDAO;
+	}
+
+
+	private void loadGraph( )
+			throws TechGraphException
+	{
+		if ( this.graph == null ) {
+			this.graph = this.technologyGraphDAO.loadGraph( );
+		}
+	}
+
+
+	/* Documented in TechnologyGraphManager interface */
+	@Override
+	public TechGraph getGraph( )
+	{
+		try {
+			this.loadGraph( );
+		} catch ( TechGraphException e ) {
+			throw new RuntimeException( e );
+		}
+		return new TechGraph( this.graph );
+	}
+
+
+	/* Documented in TechnologyGraphManager interface */
+	@Override
+	public List< TechCategory > listCategories( )
+	{
+		try {
+			this.loadGraph( );
+
+			List< TechCategory > result = new LinkedList< TechCategory >( );
+			for ( String catName : this.graph.getCategories( ) ) {
+				Category cat = this.graph.getCategory( catName );
+				result.add( new TechCategory( catName , cat.getDescription( ) , cat.getTechnologies( ) ) );
+			}
+
+			return result;
+		} catch ( TechGraphException e ) {
+			throw new RuntimeException( e );
+		}
+	}
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs-beans.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs-beans.xml
new file mode 100644
index 0000000..df082ba
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs-beans.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<import resource="techs/empire-technology-dao-bean.xml" />
+	<import resource="techs/empire-technology-manager-bean.xml" />
+	<import resource="techs/research-update-bean.xml" />
+	<import resource="techs/research-update-dao-bean.xml" />
+	<import resource="techs/technology-graph-dao-bean.xml" />
+	<import resource="techs/technology-graph-manager-bean.xml" />
+
+</beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/empire-technology-dao-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/empire-technology-dao-bean.xml
new file mode 100644
index 0000000..2edfe89
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/empire-technology-dao-bean.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<bean id="empireTechnologyDAO" class="com.deepclone.lw.beans.techs.EmpireTechnologyDAOBean" />
+
+</beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/empire-technology-manager-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/empire-technology-manager-bean.xml
new file mode 100644
index 0000000..20b8065
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/empire-technology-manager-bean.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<bean id="empireTechnologyManager" class="com.deepclone.lw.beans.techs.EmpireTechnologyManagerBean" />
+
+</beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/research-update-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/research-update-bean.xml
new file mode 100644
index 0000000..2bf00a9
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/research-update-bean.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<bean id="researchUpdate" class="com.deepclone.lw.beans.techs.ResearchUpdateBean" />
+
+</beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/research-update-dao-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/research-update-dao-bean.xml
new file mode 100644
index 0000000..1e29667
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/research-update-dao-bean.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<bean id="researchUpdateDAO" class="com.deepclone.lw.beans.techs.ResearchUpdateDAOBean" />
+
+</beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/technology-graph-dao-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/technology-graph-dao-bean.xml
new file mode 100644
index 0000000..5a77f1c
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/technology-graph-dao-bean.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<bean id="technologyGraphDAO" class="com.deepclone.lw.beans.techs.TechnologyGraphDAOBean" />
+
+</beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/technology-graph-manager-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/technology-graph-manager-bean.xml
new file mode 100644
index 0000000..511a02b
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-techs/src/main/resources/configuration/techs/technology-graph-manager-bean.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<bean id="technologyGraphManager" class="com.deepclone.lw.beans.techs.TechnologyGraphManagerBean" />
+
+</beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.classpath b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.classpath
new file mode 100644
index 0000000..7c3d14f
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.project b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.project
new file mode 100644
index 0000000..b085973
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>legacyworlds-server-beans-updates</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.settings/org.eclipse.jdt.core.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e13ce5e
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,6 @@
+#Tue Mar 29 13:49:47 CEST 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.settings/org.maven.ide.eclipse.prefs b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.settings/org.maven.ide.eclipse.prefs
new file mode 100644
index 0000000..052be69
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/.settings/org.maven.ide.eclipse.prefs
@@ -0,0 +1,9 @@
+#Tue Mar 29 13:49:47 CEST 2011
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/pom.xml
new file mode 100644
index 0000000..bc4f4e5
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/pom.xml
@@ -0,0 +1,13 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>legacyworlds-server-beans</artifactId>
+    <groupId>com.deepclone.lw</groupId>
+    <version>5.99.2</version>
+  </parent>
+  <groupId>com.deepclone.lw</groupId>
+  <artifactId>legacyworlds-server-beans-updates</artifactId>
+  <version>5.99.2</version>
+  <name>Legacy Worlds updates and pre-computation management</name>
+  <description>This Maven module contains the components which implement both the game's "standard", per-minute update system as well as the pre-computation manager.</description>
+</project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
new file mode 100644
index 0000000..2a1b0ae
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
@@ -0,0 +1,367 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.cmd.admin.logs.LogLevel;
+import com.deepclone.lw.interfaces.eventlog.Logger;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+import com.deepclone.lw.interfaces.game.updates.DuplicateUpdateHandler;
+import com.deepclone.lw.interfaces.game.updates.GameUpdate;
+import com.deepclone.lw.interfaces.game.updates.GameUpdatePhase;
+import com.deepclone.lw.interfaces.game.updates.GameUpdatePhaseHandler;
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.interfaces.sys.ConstantDefinition;
+import com.deepclone.lw.interfaces.sys.ConstantsManager;
+import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
+import com.deepclone.lw.interfaces.sys.SystemStatus;
+import com.deepclone.lw.interfaces.sys.TickStatusException;
+import com.deepclone.lw.interfaces.sys.Ticker;
+import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
+
+
+
+/**
+ * Implementation of the game update management component.
+ * 
+ * @author tseeker
+ */
+public class GameUpdateBean
+		implements GameUpdate , InitializingBean , Runnable , ApplicationContextAware
+{
+	/** The event scheduling component */
+	private Ticker ticker;
+
+	/** The system status access and update component */
+	private SystemStatus systemStatus;
+
+	/** The game update component's logger */
+	private SystemLogger logger;
+
+	/** Transaction template */
+	private TransactionTemplate tTemplate;
+
+	/** Game updates data access component */
+	private UpdatesDAO updatesDao;
+
+	/** Amount of registered, non-default handlers */
+	private int nRegisteredHandlers = 0;
+
+	/** Amount of handler components */
+	private int nHandlerComponents = -1;
+
+	/** Registered game update phase handlers */
+	private final EnumMap< GameUpdatePhase , GameUpdatePhaseHandler > handlers;
+
+
+	public GameUpdateBean( )
+	{
+		this.handlers = new EnumMap< GameUpdatePhase , GameUpdatePhaseHandler >( GameUpdatePhase.class );
+	}
+
+
+	/**
+	 * Dependency injector for the event scheduling component
+	 * 
+	 * @param ticker
+	 *            the event scheduling component
+	 */
+	@Autowired( required = true )
+	public void setTicker( Ticker ticker )
+	{
+		this.ticker = ticker;
+	}
+
+
+	/**
+	 * Dependency injector for the system status access component
+	 * 
+	 * @param systemStatus
+	 *            the system status access component
+	 */
+	@Autowired( required = true )
+	public void setSystemStatus( SystemStatus systemStatus )
+	{
+		this.systemStatus = systemStatus;
+	}
+
+
+	/**
+	 * Dependency injector that initialises the game update component's logger.
+	 * 
+	 * @param logger
+	 *            the system logging component
+	 */
+	@Autowired( required = true )
+	public void setLogger( Logger logger )
+	{
+		this.logger = logger.getSystemLogger( "GameUpdate" );
+	}
+
+
+	/**
+	 * Dependency injector that initialises the transaction template.
+	 * 
+	 * @param transactionManager
+	 *            the application's transaction manager
+	 */
+	@Autowired( required = true )
+	public void setTransactionManager( PlatformTransactionManager transactionManager )
+	{
+		this.tTemplate = new TransactionTemplate( transactionManager );
+	}
+
+
+	/**
+	 * Dependency injector for the game updates data access component
+	 * 
+	 * @param updatesDao
+	 *            the game updates data access component
+	 */
+	@Autowired( required = true )
+	public void setUpdatesDAO( UpdatesDAO updatesDao )
+	{
+		this.updatesDao = updatesDao;
+	}
+
+
+	/**
+	 * Dependency injector which registers the game.updatesPerDay constant.
+	 * 
+	 * @param constantsManager
+	 *            the constants manager
+	 */
+	@Autowired( required = true )
+	public void setConstantsManager( ConstantsManager constantsManager )
+	{
+		List< ConstantDefinition > definitions;
+		ConstantDefinition constant;
+		definitions = new ArrayList< ConstantDefinition >( 1 );
+		constant = new ConstantDefinition(
+				"game.updatesPerDay" ,
+				"Game (misc)" ,
+				"Game updates per day from the computations's point of view. "
+						+ "This does not affect the actual amount of updates, but changes the computations. "
+						+ "Can be used to speed things up or slow them down without actually changing ticker.interval." ,
+				1440.0 , 0.05 , 5760.0 );
+		definitions.add( 0 , constant );
+		constantsManager.registerConstants( definitions );
+	}
+
+
+	/**
+	 * Read the amount of update phase handlers from the application context.
+	 * 
+	 * @param context
+	 *            the application context
+	 */
+	@Override
+	public void setApplicationContext( ApplicationContext context )
+			throws BeansException
+	{
+		this.nHandlerComponents = context.getBeansOfType( GameUpdatePhaseHandler.class ).size( );
+		if ( this.nRegisteredHandlers == this.nHandlerComponents ) {
+			this.initialise( );
+		}
+	}
+
+
+	/* Documentation in GameUpdate interface */
+	@Override
+	public void registerHandler( GameUpdatePhaseHandler handler )
+			throws DuplicateUpdateHandler
+	{
+		GameUpdatePhase phase = handler.getPhase( );
+		synchronized ( this.handlers ) {
+			if ( this.handlers.containsKey( phase ) ) {
+				throw new DuplicateUpdateHandler( phase );
+			}
+			this.handlers.put( phase , handler );
+		}
+		this.logger.log( LogLevel.DEBUG , "Registered game update handler for phase " + phase.toString( ) ).flush( );
+
+		this.nRegisteredHandlers++;
+		if ( this.nRegisteredHandlers == this.nHandlerComponents ) {
+			this.initialise( );
+		}
+	}
+
+
+	/**
+	 * Initialise the component if it is ready.
+	 */
+	@Override
+	public void afterPropertiesSet( )
+	{
+		if ( this.nRegisteredHandlers == this.nHandlerComponents ) {
+			this.initialise( );
+		}
+	}
+
+
+	/**
+	 * Finish any pending computations (unless maintenance mode is active), then register the game
+	 * update task into the {@link #ticker}.
+	 */
+	private void initialise( )
+	{
+		// Finish previous tick if possible
+		try {
+			this.endPreviousTick( );
+		} catch ( MaintenanceStatusException e ) {
+			// EMPTY
+		}
+
+		// Register ticker task
+		this.ticker.registerTask( Frequency.MINUTE , "Game update" , this );
+
+		// Make sure initialisation only occurs once
+		this.nHandlerComponents = -1;
+	}
+
+
+	/**
+	 * When the game update event is triggered by the system's scheduling component, attempt to run
+	 * a game update.
+	 * 
+	 * First, check if there is already a game update in progress, and attempt to finish running it
+	 * if necessary. Otherwise start a new update though the system status manager, and execute it.
+	 */
+	@Override
+	public void run( )
+	{
+		// Attempt to end the previous tick, if e.g. maintenance mode was initiated while it was
+		// being processed
+		try {
+			if ( this.endPreviousTick( ) ) {
+				return;
+			}
+		} catch ( MaintenanceStatusException e ) {
+			return;
+		}
+
+		// Initiate next tick
+		long tickId;
+		try {
+			tickId = this.systemStatus.startTick( );
+		} catch ( TickStatusException e ) {
+			throw new RuntimeException( "tick initiated while previous tick still being processed" , e );
+		} catch ( MaintenanceStatusException e ) {
+			return;
+		}
+
+		// Execute tick
+		this.logger.log( LogLevel.DEBUG , "Tick " + tickId + " started" ).flush( );
+		this.executeUpdate( tickId );
+	}
+
+
+	/**
+	 * Check if a game update was in progress and finish running it if necessary.
+	 * 
+	 * @return <code>true</code> if a game update was already being executed, <code>false</code>
+	 *         otherwise.
+	 * 
+	 * @throws MaintenanceStatusException
+	 *             if the game is under maintenance.
+	 */
+	private boolean endPreviousTick( )
+			throws MaintenanceStatusException
+	{
+		Long currentTick = this.systemStatus.checkStuckTick( );
+		if ( currentTick == null ) {
+			return false;
+		}
+
+		this.logger.log( LogLevel.WARNING , "Tick " + currentTick + " restarted" ).flush( );
+		this.executeUpdate( currentTick.longValue( ) );
+
+		return true;
+	}
+
+
+	/**
+	 * Execute all phase handlers for a game update, then mark the update as completed.
+	 * 
+	 * @param updateId
+	 *            the identifier of the current update
+	 */
+	private void executeUpdate( long updateId )
+	{
+		for ( GameUpdatePhase phase : GameUpdatePhase.values( ) ) {
+			this.executeUpdatePhase( updateId , phase );
+		}
+
+		try {
+			this.systemStatus.endTick( );
+		} catch ( TickStatusException e ) {
+			throw new RuntimeException( "Game update completed but status error reported" , e );
+		} catch ( MaintenanceStatusException e ) {
+			return;
+		}
+
+		this.logger.log( LogLevel.TRACE , "Tick " + updateId + " completed" ).flush( );
+	}
+
+
+	/**
+	 * Execute a phase of the game update.
+	 * 
+	 * @param updateId
+	 *            the identifier of the current update
+	 * @param phase
+	 *            the phase of the update to execute
+	 */
+	private void executeUpdatePhase( final long updateId , GameUpdatePhase phase )
+	{
+		final GameUpdatePhaseHandler handler = this.getHandlerForPhase( phase );
+		handler.onPhaseStart( updateId );
+
+		boolean hasMore;
+		do {
+			hasMore = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
+				@Override
+				public Boolean doInTransaction( TransactionStatus status )
+				{
+					return handler.updateGame( updateId );
+				}
+			} );
+		} while ( hasMore );
+	}
+
+
+	/**
+	 * Access the handler for an update phase. If no handler has been registered, create a default
+	 * {@link ProceduralGameUpdate} handler.
+	 * 
+	 * @param phase
+	 *            the update phase whose handler is to be retrieved
+	 * @return the game update handler
+	 */
+	private GameUpdatePhaseHandler getHandlerForPhase( GameUpdatePhase phase )
+	{
+		GameUpdatePhaseHandler handler;
+		synchronized ( this.handlers ) {
+			handler = this.handlers.get( phase );
+			if ( handler == null ) {
+				this.logger.log( LogLevel.DEBUG , "Creating default handler for phase " + phase.toString( ) ).flush( );
+				handler = new ProceduralGameUpdate( this.updatesDao , phase );
+				this.handlers.put( phase , handler );
+			}
+		}
+		return handler;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ProceduralGameUpdate.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ProceduralGameUpdate.java
new file mode 100644
index 0000000..73480ed
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ProceduralGameUpdate.java
@@ -0,0 +1,52 @@
+package com.deepclone.lw.beans.updates;
+
+
+import com.deepclone.lw.interfaces.game.updates.GameUpdatePhase;
+import com.deepclone.lw.interfaces.game.updates.GameUpdatePhaseHandler;
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+
+
+
+class ProceduralGameUpdate
+		implements GameUpdatePhaseHandler
+{
+
+	private final GameUpdatePhase phase;
+	private final UpdatesDAO updatesDao;
+
+
+	ProceduralGameUpdate( UpdatesDAO updatesDao , GameUpdatePhase phase )
+	{
+		this.updatesDao = updatesDao;
+		this.phase = phase;
+	}
+
+
+	@Override
+	public GameUpdatePhase getPhase( )
+	{
+		return this.phase;
+	}
+
+
+	@Override
+	public boolean updateGame( long updateId )
+	{
+		if ( !this.updatesDao.prepareUpdates( updateId , this.phase ) ) {
+			return false;
+		}
+
+		this.updatesDao.executeProceduralUpdate( updateId , this.phase );
+		this.updatesDao.validateUpdatedRecords( updateId , this.phase );
+
+		return true;
+	}
+
+
+	@Override
+	public void onPhaseStart( long updateId )
+	{
+		// EMPTY
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
new file mode 100644
index 0000000..69a3af3
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
@@ -0,0 +1,85 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.sql.Types;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.interfaces.game.updates.GameUpdatePhase;
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Implementation of the game update data access component.
+ * 
+ * @author tseeker
+ */
+public class UpdatesDAOBean
+		implements UpdatesDAO
+{
+
+	/** Wrapper for the stored procedure that prepares updates */
+	private StoredProc fPrepareUpdates;
+
+	/** Wrapper for the stored procedure that executes a procedural game update */
+	private StoredProc fExecuteProcedural;
+
+	/** Wrapper for the stored procedure that marks update records as processed */
+	private StoredProc fUpdatesProcessed;
+
+
+	/**
+	 * Dependency injector that initialises stored procedure call handlers.
+	 * 
+	 * @param dataSource
+	 *            the data source
+	 */
+	@Autowired( required = true )
+	public void setDataSource( DataSource dataSource )
+	{
+		// Stored procedure that prepares updates
+		this.fPrepareUpdates = new StoredProc( dataSource , "sys" , "prepare_updates" );
+		this.fPrepareUpdates.addParameter( "u_id" , Types.BIGINT );
+		this.fPrepareUpdates.addParameter( "u_type" , "update_type" );
+		this.fPrepareUpdates.addOutput( "has_more" , Types.BOOLEAN );
+
+		// Stored procedure that executes a procedural game update
+		this.fExecuteProcedural = new StoredProc( dataSource , "sys" , "exec_update_proc" );
+		this.fExecuteProcedural.addParameter( "u_id" , Types.BIGINT );
+		this.fExecuteProcedural.addParameter( "u_type" , "update_type" );
+
+		// Stored procedure that marks update records as processed
+		this.fUpdatesProcessed = new StoredProc( dataSource , "sys" , "updates_processed" );
+		this.fUpdatesProcessed.addParameter( "u_id" , Types.BIGINT );
+		this.fUpdatesProcessed.addParameter( "u_type" , "update_type" );
+	}
+
+
+	/* Documentation in UpdatesDAO interface */
+	@Override
+	public boolean prepareUpdates( long updateId , GameUpdatePhase phase )
+	{
+		return (Boolean) this.fPrepareUpdates.execute( updateId , phase.toString( ) ).get( "has_more" );
+	}
+
+
+	/* Documentation in UpdatesDAO interface */
+	@Override
+	public void executeProceduralUpdate( long updateId , GameUpdatePhase phase )
+	{
+		this.fExecuteProcedural.execute( updateId , phase.toString( ) );
+	}
+
+
+	/* Documentation in UpdatesDAO interface */
+	@Override
+	public void validateUpdatedRecords( long updateId , GameUpdatePhase phase )
+	{
+		this.fUpdatesProcessed.execute( updateId , phase );
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/resources/configuration/updates-beans.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/resources/configuration/updates-beans.xml
new file mode 100644
index 0000000..7387fce
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/resources/configuration/updates-beans.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+	<import resource="updates/game-update-bean.xml" />
+	<import resource="updates/updates-dao-bean.xml" />
+
+</beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/resources/configuration/updates/game-update-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
rename to legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/resources/configuration/updates/game-update-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/resources/configuration/updates/updates-dao-bean.xml
similarity index 100%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml
rename to legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-updates/src/main/resources/configuration/updates/updates-dao-bean.xml
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/pom.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/pom.xml
index 0de863a..f1969df 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server-beans</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans-user</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds server - user actions</name>
 	<description>This module defines beans and classes that handle user actions.</description>
 </project>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/techs/ListCategoriesCommandDelegateBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/techs/ListCategoriesCommandDelegateBean.java
new file mode 100644
index 0000000..be5fc43
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/admin/main/techs/ListCategoriesCommandDelegateBean.java
@@ -0,0 +1,59 @@
+package com.deepclone.lw.beans.user.admin.main.techs;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.admin.common.AdminOperation;
+import com.deepclone.lw.beans.user.admin.main.AdminCommandsBean;
+import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.cmd.admin.adata.Privileges;
+import com.deepclone.lw.cmd.admin.techs.ListCategoriesCommand;
+import com.deepclone.lw.cmd.admin.techs.ListCategoriesResponse;
+import com.deepclone.lw.interfaces.game.techs.TechnologyGraphManager;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+
+
+
+public class ListCategoriesCommandDelegateBean
+		extends AdminOperation
+		implements AutowiredCommandDelegate
+{
+	private TechnologyGraphManager manager;
+
+
+	@Autowired( required = true )
+	public void setManager( TechnologyGraphManager manager )
+	{
+		this.manager = manager;
+	}
+
+
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return AdminCommandsBean.class;
+	}
+
+
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return ListCategoriesCommand.class;
+	}
+
+
+	@Override
+	public CommandResponse execute( ServerSession session , Command command )
+	{
+		Administrator admin = this.getAdministrator( session );
+		if ( !admin.hasPrivilege( Privileges.GDAT ) ) {
+			return new ListCategoriesResponse( admin );
+		}
+		return new ListCategoriesResponse( admin , this.manager.listCategories( ) );
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/ImplementTechCommandDelegateBean.java
similarity index 65%
rename from legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java
rename to legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/ImplementTechCommandDelegateBean.java
index d6d7165..a0bc22a 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/ImplementTechCommandDelegateBean.java
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/ImplementTechCommandDelegateBean.java
@@ -1,4 +1,4 @@
-package com.deepclone.lw.beans.user.player.game;
+package com.deepclone.lw.beans.user.player.game.techs;
 
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -6,8 +6,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
 import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
 import com.deepclone.lw.beans.user.player.GameSubTypeBean;
-import com.deepclone.lw.cmd.player.ImplementTechCommand;
-import com.deepclone.lw.interfaces.game.EmpireManagement;
+import com.deepclone.lw.cmd.player.research.ImplementTechCommand;
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse;
+import com.deepclone.lw.interfaces.game.techs.EmpireTechnologyManager;
 import com.deepclone.lw.interfaces.session.ServerSession;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.CommandResponse;
@@ -19,13 +20,13 @@ public class ImplementTechCommandDelegateBean
 
 {
 
-	private EmpireManagement empireManagement;
+	private EmpireTechnologyManager techManagement;
 
 
 	@Autowired( required = true )
-	public void setEmpireManager( EmpireManagement manager )
+	public void setEmpireManager( EmpireTechnologyManager manager )
 	{
-		this.empireManagement = manager;
+		this.techManagement = manager;
 	}
 
 
@@ -49,8 +50,8 @@ public class ImplementTechCommandDelegateBean
 		ImplementTechCommand command = (ImplementTechCommand) cParam;
 		int empireId = session.get( "empireId" , Integer.class );
 		if ( session.get( "vacation" , Boolean.class ) ) {
-			return this.empireManagement.getOverview( empireId );
+			return new ResearchOperationResponse( );
 		}
-		return this.empireManagement.implementTechnology( empireId , command.getTech( ) );
+		return this.techManagement.implementTechnology( empireId , command.getTech( ) );
 	}
 }
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/SetResearchPrioritiesCommandDelegateBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/SetResearchPrioritiesCommandDelegateBean.java
new file mode 100644
index 0000000..58923ad
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/SetResearchPrioritiesCommandDelegateBean.java
@@ -0,0 +1,56 @@
+package com.deepclone.lw.beans.user.player.game.techs;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.player.GameSubTypeBean;
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse;
+import com.deepclone.lw.cmd.player.research.SetResearchPrioritiesCommand;
+import com.deepclone.lw.interfaces.game.techs.EmpireTechnologyManager;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+
+
+
+public class SetResearchPrioritiesCommandDelegateBean
+		implements AutowiredCommandDelegate
+{
+
+	private EmpireTechnologyManager techManagement;
+
+
+	@Autowired( required = true )
+	public void setEmpireManager( EmpireTechnologyManager manager )
+	{
+		this.techManagement = manager;
+	}
+
+
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return SetResearchPrioritiesCommand.class;
+	}
+
+
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return GameSubTypeBean.class;
+	}
+
+
+	@Override
+	public CommandResponse execute( ServerSession session , Command cParam )
+	{
+		SetResearchPrioritiesCommand command = (SetResearchPrioritiesCommand) cParam;
+		int empireId = session.get( "empireId" , Integer.class );
+		if ( session.get( "vacation" , Boolean.class ) ) {
+			return new ResearchOperationResponse( );
+		}
+		return this.techManagement.setResearchPriorities( empireId , command.getPriorities( ) );
+	}
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/ViewResearchCommandDelegateBean.java b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/ViewResearchCommandDelegateBean.java
new file mode 100644
index 0000000..d54061b
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/techs/ViewResearchCommandDelegateBean.java
@@ -0,0 +1,52 @@
+package com.deepclone.lw.beans.user.player.game.techs;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
+import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
+import com.deepclone.lw.beans.user.player.GameSubTypeBean;
+import com.deepclone.lw.cmd.player.research.ViewResearchCommand;
+import com.deepclone.lw.interfaces.game.techs.EmpireTechnologyManager;
+import com.deepclone.lw.interfaces.session.ServerSession;
+import com.deepclone.lw.session.Command;
+import com.deepclone.lw.session.CommandResponse;
+
+
+
+public class ViewResearchCommandDelegateBean
+		implements AutowiredCommandDelegate
+{
+
+	private EmpireTechnologyManager techManagement;
+
+
+	@Autowired( required = true )
+	public void setEmpireManager( EmpireTechnologyManager manager )
+	{
+		this.techManagement = manager;
+	}
+
+
+	@Override
+	public Class< ? extends Command > getType( )
+	{
+		return ViewResearchCommand.class;
+	}
+
+
+	@Override
+	public Class< ? extends SessionCommandHandler > getCommandHandler( )
+	{
+		return GameSubTypeBean.class;
+	}
+
+
+	@Override
+	public CommandResponse execute( ServerSession session , Command cParam )
+	{
+		int empireId = session.get( "empireId" , Integer.class );
+		return this.techManagement.getResearchData( empireId );
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml
index deab9c8..9ee2df0 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/admin-session-definer-bean.xml
@@ -93,5 +93,8 @@
 	<bean class="com.deepclone.lw.beans.user.admin.main.mntm.EnableMaintenanceCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.admin.main.mntm.EndMaintenanceCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.admin.main.mntm.ExtendMaintenanceCommandDelegateBean" />
+	
+	<!-- Technology graph -->
+	<bean class="com.deepclone.lw.beans.user.admin.main.techs.ListCategoriesCommandDelegateBean" />
 
 </beans>
diff --git a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml
index 37a91ea..16a23e9 100644
--- a/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/legacyworlds-server-beans-user/src/main/resources/configuration/user/player-session-definer-bean.xml
@@ -44,8 +44,12 @@
 
 	<!-- Game: empire -->
 	<bean class="com.deepclone.lw.beans.user.player.game.OverviewCommandDelegateBean" />
-	<bean class="com.deepclone.lw.beans.user.player.game.ImplementTechCommandDelegateBean" />
 	<bean class="com.deepclone.lw.beans.user.player.game.GetNewPlanetCommandDelegateBean" />
+	
+	<!-- Game: technologies -->
+	<bean class="com.deepclone.lw.beans.user.player.game.techs.ViewResearchCommandDelegateBean" />
+	<bean class="com.deepclone.lw.beans.user.player.game.techs.ImplementTechCommandDelegateBean" />
+	<bean class="com.deepclone.lw.beans.user.player.game.techs.SetResearchPrioritiesCommandDelegateBean" />
 
 	<!-- Game: planet list -->
 	<bean class="com.deepclone.lw.beans.user.player.game.ListPlanetsCommandDelegateBean" />
diff --git a/legacyworlds-server/legacyworlds-server-beans/pom.xml b/legacyworlds-server/legacyworlds-server-beans/pom.xml
index 9d74460..b93a25f 100644
--- a/legacyworlds-server/legacyworlds-server-beans/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-beans/pom.xml
@@ -4,13 +4,13 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-beans</artifactId>
 	<name>Legacy Worlds server beans</name>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<packaging>pom</packaging>
 	<description>This metapackage regroups all packages which define beans for the Legacy Worlds server.</description>
 
@@ -32,5 +32,8 @@
 		<module>legacyworlds-server-beans-bt</module>
 		<module>legacyworlds-server-beans-user</module>
 		<module>legacyworlds-server-beans-simple</module>
+		<module>legacyworlds-server-beans-techs</module>
+		<module>legacyworlds-server-beans-updates</module>
+		<module>legacyworlds-server-beans-events</module>
 	</modules>
 </project>
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/020-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/020-functions.sql
index 5a55389..3ef2d79 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/020-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/020-functions.sql
@@ -14,6 +14,7 @@
 \i parts/functions/030-tech-functions.sql
 \i parts/functions/035-users-view.sql
 \i parts/functions/040-empire-functions.sql
+\i parts/functions/045-research-functions.sql
 \i parts/functions/050-computation-functions.sql
 \i parts/functions/060-universe-functions.sql
 \i parts/functions/070-users-functions.sql
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql
index 82bca01..6441785 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/070-constants-data.sql
@@ -25,9 +25,9 @@ CREATE TABLE sys.constant_definitions(
 	name			VARCHAR(64) NOT NULL PRIMARY KEY,
 	category_id		INT NOT NULL ,
 	description		TEXT NOT NULL ,
-	min_value		REAL ,
-	max_value		REAL ,
-	c_value			REAL NOT NULL ,
+	min_value		DOUBLE PRECISION ,
+	max_value		DOUBLE PRECISION ,
+	c_value			DOUBLE PRECISION NOT NULL ,
 	CHECK(
 		( min_value IS NULL OR (
 				min_value IS NOT NULL AND c_value >= min_value ) )
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql
index c539ac2..b46a85a 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/080-techs-data.sql
@@ -8,49 +8,65 @@
 
 
 --
--- Technology lines
+-- Categories
 --
-
-CREATE TABLE tech.lines(
+CREATE TABLE tech.categories(
 	name_id			INT NOT NULL PRIMARY KEY ,
 	description_id	INT NOT NULL
 );
 
-CREATE INDEX idx_lines_description
-	ON tech.lines (description_id);
+CREATE INDEX idx_categories_description
+	ON tech.categories (description_id);
 
-ALTER TABLE tech.lines
-	ADD CONSTRAINT fk_lines_name
+ALTER TABLE tech.categories
+	ADD CONSTRAINT fk_categories_name
 		FOREIGN KEY (name_id) REFERENCES defs.strings ,
-	ADD CONSTRAINT fk_lines_description
+	ADD CONSTRAINT fk_categories_description
 		FOREIGN KEY (description_id) REFERENCES defs.strings;
-		
+
 
 --
--- Technology levels
+-- Technologies
 --
 
-CREATE TABLE tech.levels(
-	id				SERIAL NOT NULL PRIMARY KEY ,
-	line_id			INT NOT NULL ,
-	level			INT NOT NULL CHECK( level > 0 ) ,
-	name_id			INT NOT NULL ,
+CREATE TABLE tech.technologies(
+	name_id			INT NOT NULL PRIMARY KEY ,
 	description_id	INT NOT NULL ,
+	category_id		INT NOT NULL ,
 	points			INT NOT NULL CHECK( points > 0 ) ,
 	cost			INT NOT NULL CHECK( cost > 0 )
 );
 
-CREATE UNIQUE INDEX idx_levels_linelevel
-	ON tech.levels (line_id, level);
-CREATE INDEX idx_levels_name
-	ON tech.levels (name_id);
-CREATE INDEX idx_levels_description
-	ON tech.levels (description_id);
+CREATE INDEX idx_technologies_description
+	ON tech.technologies (description_id);
+CREATE INDEX idx_technologies_category
+	ON tech.technologies (category_id);
 
-ALTER TABLE tech.levels
-	ADD CONSTRAINT fk_levels_line
-		FOREIGN KEY (line_id) REFERENCES tech.lines ,
-	ADD CONSTRAINT fk_levels_name
+ALTER TABLE tech.technologies
+	ADD CONSTRAINT fk_technologies_category
+		FOREIGN KEY (category_id) REFERENCES tech.categories ,
+	ADD CONSTRAINT fk_technologies_name
 		FOREIGN KEY (name_id) REFERENCES defs.strings ,
-	ADD CONSTRAINT fk_levels_description
+	ADD CONSTRAINT fk_technologies_description
 		FOREIGN KEY (description_id) REFERENCES defs.strings;
+
+
+
+--
+-- Dependencies
+--
+
+CREATE TABLE tech.dependencies(
+	technology_id	INT NOT NULL ,
+	depends_on		INT NOT NULL ,
+	PRIMARY KEY( technology_id , depends_on )
+);
+
+CREATE INDEX idx_dependencies_dependson
+	ON tech.dependencies (depends_on);
+
+ALTER TABLE tech.dependencies
+	ADD CONSTRAINT fk_dependencies_technology
+		FOREIGN KEY (technology_id) REFERENCES tech.technologies ,
+	ADD CONSTRAINT fk_dependencies_dependson
+		FOREIGN KEY (depends_on) REFERENCES tech.technologies;
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql
index 3a80b4d..5ab70ee 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/090-buildables-data.sql
@@ -32,18 +32,18 @@ ALTER TABLE tech.buildables
 --
 CREATE TABLE tech.buildable_requirements(
 	buildable_id	INT NOT NULL ,
-	level_id		INT NOT NULL ,
-	PRIMARY KEY( buildable_id , level_id )
+	technology_id	INT NOT NULL ,
+	PRIMARY KEY( buildable_id , technology_id )
 );
 
-CREATE INDEX idx_buildablereqs_level
-	ON tech.buildable_requirements( level_id );
+CREATE INDEX idx_buildablereqs_technology
+	ON tech.buildable_requirements( technology_id );
 
 ALTER TABLE tech.buildable_requirements
 	ADD CONSTRAINT fk_buildablereqs_buildable
 		FOREIGN KEY (buildable_id) REFERENCES tech.buildables ,
-	ADD CONSTRAINT fk_buildablereqs_level
-		FOREIGN KEY (level_id) REFERENCES tech.levels;
+	ADD CONSTRAINT fk_buildablereqs_technology
+		FOREIGN KEY (technology_id) REFERENCES tech.technologies;
 
 
 --
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
index fb8f379..a784220 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/100-universe-data.sql
@@ -28,7 +28,7 @@ CREATE TABLE verse.planets(
 	orbit		INT NOT NULL
 					CHECK( orbit BETWEEN 1 AND 5 ) ,
 	picture		INT NOT NULL ,
-	population	REAL NOT NULL
+	population	DOUBLE PRECISION NOT NULL
 					CHECK( population >= 0 )
 );
 
@@ -47,9 +47,9 @@ ALTER TABLE verse.planets
 --
 CREATE TABLE verse.planet_happiness(
 	planet_id	INT NOT NULL PRIMARY KEY ,
-	target		REAL NOT NULL
+	target		DOUBLE PRECISION NOT NULL
 					CHECK( target BETWEEN 0.0 AND 1.0 ) ,
-	current		REAL NOT NULL
+	current		DOUBLE PRECISION NOT NULL
 					CHECK( current > 0 )
 );
 
@@ -63,9 +63,9 @@ ALTER TABLE verse.planet_happiness
 --
 CREATE TABLE verse.planet_money(
 	planet_id	INT NOT NULL PRIMARY KEY ,
-	income		REAL NOT NULL
+	income		DOUBLE PRECISION NOT NULL
 					CHECK( income >= 0 ) ,
-	upkeep		REAL NOT NULL
+	upkeep		DOUBLE PRECISION NOT NULL
 					CHECK( upkeep >= 0 )
 );
 
@@ -81,7 +81,7 @@ CREATE TABLE verse.planet_buildings(
 	planet_id		INT NOT NULL ,
 	building_id		INT NOT NULL ,
 	amount			INT NOT NULL CHECK( amount >= 0 ) ,
-	damage			REAL NOT NULL CHECK( damage >= 0 ) ,
+	damage			DOUBLE PRECISION NOT NULL CHECK( damage >= 0 ) ,
 	PRIMARY KEY( planet_id , building_id )
 );
 
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
index 5a3d00a..0cf46be 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/110-empires-data.sql
@@ -13,9 +13,9 @@
 
 CREATE TABLE emp.empires(
 	name_id		INT NOT NULL PRIMARY KEY ,
-	cash		REAL NOT NULL
+	cash		DOUBLE PRECISION NOT NULL
 					CHECK( cash >= 0 ),
-	debt		REAL NOT NULL DEFAULT 0
+	debt		DOUBLE PRECISION NOT NULL DEFAULT 0
 					CHECK( debt >= 0)
 );
 
@@ -25,28 +25,55 @@ ALTER TABLE emp.empires
 
 
 --
--- Empire technologies
+-- Research in progress
 --
 
-CREATE TABLE emp.technologies(
+CREATE TABLE emp.research(
 	empire_id		INT NOT NULL ,
-	line_id			INT NOT NULL ,
-	level			INT NOT NULL DEFAULT 1
-						CHECK( level > 0 ) ,
-	accumulated		REAL NOT NULL DEFAULT 0
-						CHECK( accumulated >= 0 ),
-	PRIMARY KEY( empire_id , line_id )
+	technology_id	INT	NOT NULL ,
+	accumulated		DOUBLE PRECISION NOT NULL DEFAULT 0
+						CHECK( accumulated >= 0 ) ,
+	priority		INT NOT NULL
+						CHECK( priority BETWEEN 0 AND 100 ) ,
+	PRIMARY KEY( empire_id , technology_id )
 );
 
-CREATE INDEX idx_technologies_line
-	ON emp.technologies (line_id);
+CREATE INDEX idx_research_technology
+	ON emp.research (technology_id);
 
-ALTER TABLE emp.technologies
-	ADD CONSTRAINT fk_technologies_empire
+ALTER TABLE emp.research
+	ADD CONSTRAINT fk_research_empire
 		FOREIGN KEY (empire_id) REFERENCES emp.empires
 			ON DELETE CASCADE ,
-	ADD CONSTRAINT fk_technologies_line
-		FOREIGN KEY (line_id) REFERENCES tech.lines;
+	ADD CONSTRAINT fk_research_technology
+		FOREIGN KEY (technology_id) REFERENCES tech.technologies
+			ON DELETE CASCADE;
+
+
+
+--
+-- Researched technologies (both implemented and pending implementation)
+--
+
+CREATE TABLE emp.researched_technologies(
+	empire_id		INT NOT NULL ,
+	technology_id	INT NOT NULL ,
+	implemented		BOOLEAN NOT NULL ,
+	PRIMARY KEY ( empire_id , technology_id )
+);
+
+CREATE INDEX idx_researched_technology
+	ON emp.researched_technologies ( technology_id );
+CREATE INDEX idx_researched_implemented
+	ON emp.researched_technologies ( empire_id , implemented );
+
+ALTER TABLE emp.researched_technologies
+	ADD CONSTRAINT fk_researched_empire
+		FOREIGN KEY ( empire_id ) REFERENCES emp.empires
+			ON DELETE CASCADE ,
+	ADD CONSTRAINT fk_researched_technology
+		FOREIGN KEY ( technology_id ) REFERENCES tech.technologies
+			ON DELETE CASCADE;
 
 
 --
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql
index 9ae9fcd..2e1a050 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/120-construction-data.sql
@@ -13,8 +13,8 @@
 
 CREATE TABLE verse.bld_queues(
 	planet_id		INT NOT NULL PRIMARY KEY ,
-	money			REAL NOT NULL CHECK( money >= 0 ),
-	work			REAL NOT NULL CHECK( work >= 0 )
+	money			DOUBLE PRECISION NOT NULL CHECK( money >= 0 ),
+	work			DOUBLE PRECISION NOT NULL CHECK( work >= 0 )
 );
 
 ALTER TABLE verse.bld_queues
@@ -51,8 +51,8 @@ ALTER TABLE verse.bld_items
 
 CREATE TABLE verse.mil_queues(
 	planet_id		INT NOT NULL PRIMARY KEY ,
-	money			REAL NOT NULL CHECK( money >= 0 ),
-	work			REAL NOT NULL CHECK( work >= 0 )
+	money			DOUBLE PRECISION NOT NULL CHECK( money >= 0 ),
+	work			DOUBLE PRECISION NOT NULL CHECK( work >= 0 )
 );
 
 ALTER TABLE verse.mil_queues
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql
index 7d7bb0f..96cd979 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/130-fleets-data.sql
@@ -46,7 +46,7 @@ CREATE TABLE fleets.ships(
 	fleet_id	BIGINT NOT NULL ,
 	ship_id		INT NOT NULL ,
 	amount		INT NOT NULL CHECK( amount >= 0 ) ,
-	damage		REAL NOT NULL ,
+	damage		DOUBLE PRECISION NOT NULL ,
 	PRIMARY KEY( fleet_id , ship_id )
 );
 
@@ -89,8 +89,8 @@ ALTER TABLE fleets.movements
 
 CREATE TABLE fleets.ms_space(
 	movement_id		BIGINT NOT NULL PRIMARY KEY ,
-	start_x			REAL NOT NULL ,
-	start_y			REAL NOT NULL
+	start_x			DOUBLE PRECISION NOT NULL ,
+	start_y			DOUBLE PRECISION NOT NULL
 );
 
 ALTER TABLE fleets.ms_space
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/170-events-data.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/170-events-data.sql
index 186a032..d1d72fd 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/170-events-data.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/data/170-events-data.sql
@@ -3,10 +3,163 @@
 --
 -- Storage of events (internal messages)
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2011, DeepClone Development
 -- --------------------------------------------------------
 
 
+--
+-- Event type definitionss
+--
+
+CREATE TABLE events.event_type_definitions(
+	etd_type			VARCHAR( 48 ) NOT NULL
+							PRIMARY KEY ,
+	etd_priority		INT NOT NULL
+							CHECK( etd_priority BETWEEN 1 AND 5 ) ,
+	etd_user_priority	BOOLEAN NOT NULL
+);
+
+
+--
+-- Types of field contents
+--
+--	INMB	integer
+--	RNMB	real number
+--	TEXT	text
+--	BOOL	boolean
+--	I18N	internationalised string
+--	EREF	game entity reference
+--
+
+CREATE TYPE events.field_content_type
+	AS ENUM ( 'INMB' , 'RNMB' , 'TEXT' , 'BOOL' , 'I18N' , 'EREF' );
+
+
+--
+-- Subtypes of reference field contents
+--
+--	EMP		empire
+--	MAP		map object
+--	FLT		fleet
+--	BAT		battle
+--	ADM		administrator
+--	MSG		message
+--	BUG		bug report
+--
+
+CREATE TYPE events.field_reference_type
+	AS ENUM ( 'EMP' , 'MAP' , 'FLT' , 'BAT' , 'ADM' , 'MSG' , 'BUG' );
+
+
+--
+-- Event field definitions
+--
+
+CREATE TABLE events.event_field_definitions(
+	etd_type			VARCHAR( 48 ) NOT NULL ,
+	efd_field			VARCHAR( 48 ) NOT NULL ,
+	efd_required		BOOLEAN NOT NULL ,
+	efd_type			events.field_content_type NOT NULL ,
+	efd_reference_type	events.field_reference_type ,
+	efd_low_boundary	DOUBLE PRECISION ,
+	efd_high_boundary	DOUBLE PRECISION ,
+	
+	PRIMARY KEY ( etd_type , efd_field ) ,
+
+	CHECK( efd_type = 'EREF' AND efd_reference_type IS NOT NULL
+			OR efd_type <> 'EREF' AND efd_reference_type IS NULL ) ,
+
+	CHECK ( efd_type IN ( 'INMB' , 'RNMB' , 'TEXT' )
+			OR  efd_type NOT IN ( 'INMB' , 'RNMB' , 'TEXT' )
+					AND efd_low_boundary IS NULL
+					AND efd_high_boundary IS NULL ) ,
+
+	CHECK ( efd_low_boundary IS NULL OR efd_high_boundary IS NULL
+			OR efd_low_boundary < efd_high_boundary )
+);
+
+ALTER TABLE events.event_field_definitions
+	ADD CONSTRAINT fk_efd_type
+		FOREIGN KEY ( etd_type ) REFERENCES events.event_type_definitions;
+
+
+
+--
+-- Event format definitions
+--
+
+CREATE TABLE events.event_format_definitions(
+	etd_type		VARCHAR( 48 ) NOT NULL ,
+	efmd_order		INT NOT NULL ,
+	efmd_template	INT NOT NULL ,
+
+	PRIMARY KEY ( etd_type , efmd_order )
+);
+
+CREATE INDEX idx_efmd_template
+	ON events.event_format_definitions ( efmd_template );
+
+ALTER TABLE events.event_format_definitions
+	ADD CONSTRAINT fk_efmd_type
+		FOREIGN KEY ( etd_type ) REFERENCES events.event_type_definitions ,
+	ADD CONSTRAINT fk_efmd_template
+		FOREIGN KEY ( efmd_template ) REFERENCES defs.strings;
+
+
+
+--
+-- Types of format conditions
+--
+--	EX		has value				all
+--	EQ		is equal to ...			all except EREF
+--	NE		is not equal to ...		all except EREF
+--	GT		> ...					INMB , RNMB
+--			length > ...			TEXT
+--	LT		< ...					INMB , RNMB
+--			shorter than ...		TEXT
+--	GE		>= ...					INMB , RNMB
+--			length >= ...			TEXT
+--	LE		<= ...					INMB , RNMB
+--			length <= ...			TEXT
+--	AV		available				EREF
+--
+
+CREATE TYPE events.format_condition_type
+	AS ENUM ( 'EX' , 'EQ' , 'NE' , 'GT' , 'LT' , 'GE' , 'LE' , 'AV' );
+
+
+--
+-- Conditions on format definitions
+--
+
+CREATE TABLE events.efmt_conditions(
+	etd_type		VARCHAR( 48 ) NOT NULL ,
+	efmd_order		INT NOT NULL ,
+	efd_field		VARCHAR( 48 ) NOT NULL ,
+	efc_type		events.format_condition_type NOT NULL ,
+	efc_boolean		BOOLEAN ,
+	efc_numeric		DOUBLE PRECISION ,
+	efc_string		TEXT ,
+	
+	PRIMARY KEY( etd_type , efmd_order , efd_field , efc_type )
+);
+
+CREATE INDEX idx_efmtc_field
+	ON events.efmt_conditions ( etd_type , efd_field );
+
+ALTER TABLE events.efmt_conditions
+	ADD CONSTRAINT fk_efmtc_format
+		FOREIGN KEY  ( etd_type , efmd_order ) REFERENCES events.event_format_definitions ,
+	ADD CONSTRAINT fk_efmtc_field
+		FOREIGN KEY  ( etd_type , efd_field ) REFERENCES events.event_field_definitions;
+
+
+
+
+-- --------------------------------------------------------
+-- OLD CODE BELOW
+--
+
 CREATE TYPE event_type
 	AS ENUM ( 'QUEUE' , 'EMPIRE' , 'FLEETS' , 'PLANET', 'ALLIANCE', 'ADMIN' , 'BUGS' );
 
@@ -101,7 +254,7 @@ ALTER TABLE events.empire_events
 		FOREIGN KEY (event_id) REFERENCES events.events
 			ON DELETE CASCADE,
 	ADD CONSTRAINT fk_empevents_tech
-		FOREIGN KEY (technology_id) REFERENCES tech.levels;
+		FOREIGN KEY (technology_id) REFERENCES tech.technologies;
 
 
 
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql
index f0c78ed..c4cd55f 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/010-constants-functions.sql
@@ -57,8 +57,8 @@ $$ LANGUAGE plpgsql;
 --	the constant's actual value
 --
 
-CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT , dval REAL )
-		RETURNS REAL
+CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT , dval DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
 		STRICT
 		VOLATILE
 		SECURITY DEFINER
@@ -66,7 +66,7 @@ CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT ,
 DECLARE
 	ccid	INT;
 	occid	INT;
-	cval	REAL;
+	cval	DOUBLE PRECISION;
 BEGIN
 	ccid := sys.cog_constant_category( ccnm );
 
@@ -97,7 +97,7 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , REAL ) TO :dbuser;
+GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , DOUBLE PRECISION ) TO :dbuser;
 
 
 
@@ -116,8 +116,8 @@ GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , REAL ) TO :dbus
 --	the constant's actual value
 --
 
-CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT , dval REAL , bval REAL , ismin BOOLEAN )
-		RETURNS REAL
+CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT , dval DOUBLE PRECISION , bval DOUBLE PRECISION , ismin BOOLEAN )
+		RETURNS DOUBLE PRECISION
 		STRICT
 		VOLATILE
 		SECURITY DEFINER
@@ -125,9 +125,9 @@ CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT ,
 DECLARE
 	ccid	INT;
 	occid	INT;
-	cval	REAL;
-	mival	REAL;
-	maval	REAL;
+	cval	DOUBLE PRECISION;
+	mival	DOUBLE PRECISION;
+	maval	DOUBLE PRECISION;
 BEGIN
 	IF ismin THEN
 		mival := bval;
@@ -175,7 +175,7 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , REAL , REAL , BOOLEAN ) TO :dbuser;
+GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , DOUBLE PRECISION , DOUBLE PRECISION , BOOLEAN ) TO :dbuser;
 
 
 
@@ -194,8 +194,8 @@ GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , REAL , REAL , B
 --	the constant's actual value
 --
 
-CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT , dval REAL , mival REAL , maval REAL )
-		RETURNS REAL
+CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT , dval DOUBLE PRECISION , mival DOUBLE PRECISION , maval DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
 		STRICT
 		VOLATILE
 		SECURITY DEFINER
@@ -203,7 +203,7 @@ CREATE OR REPLACE FUNCTION sys.uoc_constant( cnm TEXT , cdesc TEXT , ccnm TEXT ,
 DECLARE
 	ccid	INT;
 	occid	INT;
-	cval	REAL;
+	cval	DOUBLE PRECISION;
 BEGIN
 	ccid := sys.cog_constant_category( ccnm );
 
@@ -243,7 +243,7 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , REAL , REAL , REAL ) TO :dbuser;
+GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ) TO :dbuser;
 
 
 
@@ -259,7 +259,7 @@ GRANT EXECUTE ON FUNCTION sys.uoc_constant( TEXT , TEXT , TEXT , REAL , REAL , R
 --	TRUE on success, FALSE on failure
 --
 
-CREATE OR REPLACE FUNCTION sys.set_constant( cnm TEXT , nval REAL , aid INT )
+CREATE OR REPLACE FUNCTION sys.set_constant( cnm TEXT , nval DOUBLE PRECISION , aid INT )
 		RETURNS BOOLEAN
 		STRICT
 		VOLATILE
@@ -282,7 +282,7 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION sys.set_constant( TEXT , REAL , INT ) TO :dbuser;
+GRANT EXECUTE ON FUNCTION sys.set_constant( TEXT , DOUBLE PRECISION , INT ) TO :dbuser;
 
 
 
@@ -294,7 +294,7 @@ GRANT EXECUTE ON FUNCTION sys.set_constant( TEXT , REAL , INT ) TO :dbuser;
 --
 
 CREATE OR REPLACE FUNCTION sys.get_constant( cnm TEXT )
-	RETURNS REAL
+	RETURNS DOUBLE PRECISION
 	STRICT STABLE
 	SECURITY DEFINER
 AS $$
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql
index eb2c74a..df70d4e 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/030-tech-functions.sql
@@ -40,94 +40,226 @@ CREATE VIEW tech.ships_view
 		FROM tech.buildables b
 			INNER JOIN tech.ships s
 				ON b.name_id = s.buildable_id;
+				
+
+--
+-- Categories view
+--
+
+CREATE VIEW tech.categories_view
+	AS SELECT ns.name AS name , ds.name AS description
+		FROM tech.categories c
+			INNER JOIN defs.strings ns
+				ON ns.id = c.name_id
+			INNER JOIN defs.strings ds
+				ON ds.id = c.description_id;
+
+GRANT SELECT ON tech.categories_view TO :dbuser;
+				
+
+--
+-- Technologies view
+--
+
+CREATE VIEW tech.technologies_view
+	AS SELECT cs.name AS category , ns.name AS name ,
+			ds.name AS description , t.points , t.cost
+		FROM tech.technologies t
+			INNER JOIN defs.strings cs
+				ON cs.id = t.category_id
+			INNER JOIN defs.strings ns
+				ON ns.id = t.name_id
+			INNER JOIN defs.strings ds
+				ON ds.id = t.description_id;
+
+GRANT SELECT ON tech.technologies_view TO :dbuser;
+
+
+--
+-- Dependencies view
+--
+
+CREATE VIEW tech.dependencies_view
+	AS SELECT ts.name AS technology , ds.name AS dependency
+		FROM tech.dependencies d
+			INNER JOIN defs.strings ts
+				ON ts.id = d.technology_id
+			INNER JOIN defs.strings ds
+				ON ds.id = d.depends_on;
+
+GRANT SELECT ON tech.dependencies_view TO :dbuser;
 
 
 
 --
--- Creates or updates a technology line
+-- Creates or updates a technology category
 --
 -- Parameters:
---	tln		Tech line name
---	tld		Tech line description
+--	cat_name	String identifier of the category's name
+--	cat_desc	String identifier of the category's description
+--
+-- Returns:
+--	0			No error
+--	1			Name string not found
+--	2			Description string not found
 --
 
-CREATE OR REPLACE FUNCTION tech.uoc_line( tln TEXT , tld TEXT )
-		RETURNS VOID
-		STRICT
-		VOLATILE
+CREATE OR REPLACE FUNCTION tech.uoc_category( cat_name TEXT , cat_desc TEXT )
+		RETURNS INT
+		STRICT VOLATILE
 		SECURITY DEFINER
 	AS $$
 DECLARE
-	nid	INT;
-	did	INT;
+	cn_id	INT;
+	cd_id	INT;
 BEGIN
-	-- Get string identifiers
-	SELECT INTO nid id FROM defs.strings WHERE name = tln;
-	SELECT INTO did id FROM defs.strings WHERE name = tld;
-
-	-- Try creating / updating
+	-- Get name / description identifiers
+	SELECT INTO cn_id id FROM defs.strings WHERE name = cat_name;
+	IF NOT FOUND THEN
+			RETURN 1;
+	END IF;
+	SELECT INTO cd_id id FROM defs.strings WHERE name = cat_desc;
+	IF NOT FOUND THEN
+		RETURN 2;
+	END IF;
+	
+	-- Create or update the category
 	BEGIN
-		INSERT INTO tech.lines ( name_id , description_id )
-			VALUES ( nid , did );
+		INSERT INTO tech.categories ( name_id , description_id )
+			VALUES ( cn_id , cd_id );
 	EXCEPTION
 		WHEN unique_violation THEN
-			UPDATE tech.lines SET description_id = did
-				WHERE name_id = nid;
+			UPDATE tech.categories SET description_id = cd_id
+				WHERE name_id = cn_id;
 	END;
+	RETURN 0;
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION tech.uoc_line( TEXT , TEXT ) TO :dbuser;
-
+GRANT EXECUTE ON FUNCTION tech.uoc_category( TEXT , TEXT ) to :dbuser;
 
 
 --
--- Creates or updates a technology level
+-- Creates or updates a technology. If there are dependencies, clear them.
 --
 -- Parameters:
---	tln		Tech line name
---	lv		Level
---	lvn		Level name
---	lvd		Level description
---	lvp		Points
---	lvc		Cost
+--	nt_name		Name string identifier
+--	nt_category	Category string identifier
+--	nt_desc		Description string identifier
+--	nt_points	Research points for the technology
+--	nt_cost		Cost of the technology
+--
+-- Returns:
+--	0			No error
+--	1			Name string not found
+--	2			Category not found
+--	3			Description string not found
+--	4			Invalid parameters (points or cost)
 --
 
-CREATE OR REPLACE FUNCTION tech.uoc_level( tln TEXT , lv INT , lvn TEXT , lvd TEXT , lvp INT , lvc INT )
-		RETURNS VOID
-		STRICT
-		VOLATILE
+CREATE OR REPLACE FUNCTION tech.uoc_technology( nt_name TEXT , nt_category TEXT , nt_desc TEXT ,
+			nt_points INT , nt_cost INT )
+		RETURNS INT
+		STRICT VOLATILE
 		SECURITY DEFINER
 	AS $$
 DECLARE
-	lid		INT;
-	nid		INT;
-	did		INT;
+	n_id	INT;
+	c_id	INT;
+	d_id	INT;
 BEGIN
-	-- Get tech line
-	SELECT INTO lid t.name_id
-		FROM tech.lines t
-			INNER JOIN defs.strings s
-				ON s.id = t.name_id
-		WHERE s.name = tln;
+	-- Get name, category and description identifiers
+	SELECT INTO n_id id FROM defs.strings WHERE name = nt_name;
+	IF NOT FOUND THEN
+			RETURN 1;
+	END IF;
+	SELECT INTO c_id c.name_id FROM tech.categories c
+		INNER JOIN defs.strings s
+			ON s.id = c.name_id AND s.name = nt_category;
+	IF NOT FOUND THEN
+		RETURN 2;
+	END IF;
+	SELECT INTO d_id id FROM defs.strings WHERE name = nt_desc;
+	IF NOT FOUND THEN
+		RETURN 3;
+	END IF;
 
-	-- Get name / description IDs
-	SELECT INTO nid id FROM defs.strings WHERE name = lvn;
-	SELECT INTO did id FROM defs.strings WHERE name = lvd;
-
-	-- Create or update the level
+	-- Create or update the technology
 	BEGIN
-		INSERT INTO tech.levels ( line_id , level , name_id , description_id , points , cost )
-			VALUES ( lid , lv , nid , did , lvp , lvc );
+		BEGIN
+			INSERT INTO tech.technologies ( name_id , category_id , description_id , points , cost )
+				VALUES ( n_id , c_id , d_id , nt_points , nt_cost );
+		EXCEPTION
+			WHEN unique_violation THEN
+				UPDATE tech.technologies
+					SET category_id = c_id , description_id = cd_id ,
+						points = nt_points , cost = nt_cost
+					WHERE name_id = n_id;
+				DELETE FROM tech.dependencies
+					WHERE technology_id = n_id;
+		END;
+	EXCEPTION
+		WHEN check_violation THEN
+			RETURN 4;
+	END;
+	RETURN 0;
+END;
+$$ LANGUAGE plpgsql;		
+
+GRANT EXECUTE ON FUNCTION tech.uoc_technology( TEXT , TEXT , TEXT , INT , INT ) to :dbuser;
+
+
+--
+-- Adds a technology dependency
+--
+-- Parameters:
+--	nd_name		Name of the dependent technology
+--	nd_dep		Name of the dependency
+--
+-- Returns:
+--	0			No error
+--	1			Technology not found
+--	2			Dependency not found
+--	3			Duplicate dependency
+--
+CREATE OR REPLACE FUNCTION tech.add_dependency( nd_name TEXT , nd_dep TEXT )
+		RETURNS INT
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $$
+DECLARE
+	t_id	INT;
+	d_id	INT;
+BEGIN
+	-- Get technology
+	SELECT INTO t_id t.name_id FROM tech.technologies t
+		INNER JOIN defs.strings s
+			ON s.id = t.name_id AND s.name = nd_name;
+	IF NOT FOUND THEN
+		RETURN 1;
+	END IF;
+
+	-- Get dependency
+	SELECT INTO d_id t.name_id FROM tech.technologies t
+		INNER JOIN defs.strings s
+			ON s.id = t.name_id AND s.name = nd_dep;
+	IF NOT FOUND THEN
+		RETURN 2;
+	END IF;
+	
+	-- Add dependency
+	BEGIN
+		INSERT INTO tech.dependencies ( technology_id , depends_on )
+			VALUES ( t_id , d_id );
 	EXCEPTION
 		WHEN unique_violation THEN
-			UPDATE tech.levels SET name_id = nid , description_id = did , points = lvp , cost = lvc
-				WHERE line_id = lid AND level = lv;
+			RETURN 3;
 	END;
+	RETURN 0;
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION tech.uoc_level( TEXT , INT , TEXT , TEXT , INT , INT ) to :dbuser;
+GRANT EXECUTE ON FUNCTION tech.add_dependency( TEXT, TEXT ) TO :dbuser;
 
 
 
@@ -140,14 +272,13 @@ GRANT EXECUTE ON FUNCTION tech.uoc_level( TEXT , INT , TEXT , TEXT , INT , INT )
 --	bdc		Cost
 --	bdw		Work
 --	bdu		Upkeep
---	bdtn	Dependency (name)
---	bdtl	Dependency (level)
+--	bdtn	Dependency
 --
 -- Returns:
 --	the buildable's identifier
 --
 
-CREATE OR REPLACE FUNCTION tech.uoc_buildable( bdn TEXT , bdd TEXT , bdc INT , bdw INT , bdu INT , bdtn TEXT , bdtl INT )
+CREATE OR REPLACE FUNCTION tech.uoc_buildable( bdn TEXT , bdd TEXT , bdc INT , bdw INT , bdu INT , bdtn TEXT )
 		RETURNS INT
 		STRICT
 		VOLATILE
@@ -162,10 +293,10 @@ BEGIN
 	SELECT INTO nid id FROM defs.strings WHERE name = bdn;
 	SELECT INTO did id FROM defs.strings WHERE name = bdd;
 	IF bdtn <> '' THEN
-		SELECT INTO tdid tl.id FROM tech.levels tl
+		SELECT INTO tdid tl.name_id FROM tech.technologies tl
 			INNER JOIN defs.strings s
-				ON s.id = tl.line_id
-			WHERE s.name = bdtn AND tl.level = bdtl;
+				ON s.id = tl.name_id
+			WHERE s.name = bdtn;
 	END IF;
 	
 	-- Create or update the definition
@@ -181,7 +312,7 @@ BEGIN
 	-- Set dependencies
 	DELETE FROM tech.buildable_requirements WHERE buildable_id = nid;
 	IF bdtn <> '' THEN
-		INSERT INTO tech.buildable_requirements ( buildable_id , level_id )
+		INSERT INTO tech.buildable_requirements ( buildable_id , technology_id )
 			VALUES ( nid , tdid );
 	END IF;
 	
@@ -215,7 +346,7 @@ CREATE OR REPLACE FUNCTION tech.uoc_building( bdn TEXT , bdd TEXT , bdc INT , bd
 DECLARE
 	bdid	INT;
 BEGIN
-	bdid := tech.uoc_buildable( bdn , bdd , bdc , bdw , bdu , '' , 0 );
+	bdid := tech.uoc_buildable( bdn , bdd , bdc , bdw , bdu , '' );
 	
 	PERFORM buildable_id FROM tech.ships WHERE buildable_id = bdid;
 	IF FOUND THEN
@@ -249,22 +380,20 @@ GRANT EXECUTE ON FUNCTION tech.uoc_building( TEXT , TEXT , INT , INT , INT , INT
 --	bdwk	Workers
 --	bdot	Output type
 --	bdo		Output
---	bdtn	Dependency (name)
---	bdtl	Dependency (level)
+--	bdtn	Dependency
 --
 
 CREATE OR REPLACE FUNCTION tech.uoc_building( bdn TEXT , bdd TEXT , bdc INT , bdw INT ,
 											  bdu INT , bdwk INT , bdot building_output_type , bdo INT ,
-											  bdtn TEXT , bdtl INT )
+											  bdtn TEXT )
 		RETURNS VOID
-		STRICT
-		VOLATILE
+		STRICT VOLATILE
 		SECURITY DEFINER
 	AS $$
 DECLARE
 	bdid	INT;
 BEGIN
-	bdid := tech.uoc_buildable( bdn , bdd , bdc , bdw , bdu , bdtn , bdtl );
+	bdid := tech.uoc_buildable( bdn , bdd , bdc , bdw , bdu , bdtn );
 	
 	PERFORM buildable_id FROM tech.ships WHERE buildable_id = bdid;
 	IF FOUND THEN
@@ -282,7 +411,7 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION tech.uoc_building( TEXT , TEXT , INT , INT , INT , INT , building_output_type , INT , TEXT , INT ) TO :dbuser;
+GRANT EXECUTE ON FUNCTION tech.uoc_building( TEXT , TEXT , INT , INT , INT , INT , building_output_type , INT , TEXT ) TO :dbuser;
 
 
 
@@ -309,7 +438,7 @@ CREATE OR REPLACE FUNCTION tech.uoc_ship( sn TEXT , sd TEXT , sc INT , sw INT ,
 DECLARE
 	bdid	INT;
 BEGIN
-	bdid := tech.uoc_buildable( sn , sd , sc , sw , su , '' , 0 );
+	bdid := tech.uoc_buildable( sn , sd , sc , sw , su , '' );
 	
 	PERFORM buildable_id FROM tech.buildings WHERE buildable_id = bdid;
 	IF FOUND THEN
@@ -342,12 +471,11 @@ GRANT EXECUTE ON FUNCTION tech.uoc_ship( TEXT , TEXT , INT , INT , INT , INT , I
 --	su		Upkeep
 --	sp		Power
 --	sft		Orbital flight time
---	stdn	Tech line name
---	stdl	Tech level
+--	stdn	Tech name
 --
 
 CREATE OR REPLACE FUNCTION tech.uoc_ship( sn TEXT , sd TEXT , sc INT , sw INT ,
-										  su INT , sp INT , sft INT , stdn TEXT , stdl INT )
+										  su INT , sp INT , sft INT , stdn TEXT )
 		RETURNS VOID
 		STRICT
 		VOLATILE
@@ -356,7 +484,7 @@ CREATE OR REPLACE FUNCTION tech.uoc_ship( sn TEXT , sd TEXT , sc INT , sw INT ,
 DECLARE
 	bdid	INT;
 BEGIN
-	bdid := tech.uoc_buildable( sn , sd , sc , sw , su , stdn , stdl );
+	bdid := tech.uoc_buildable( sn , sd , sc , sw , su , stdn );
 	
 	PERFORM buildable_id FROM tech.buildings WHERE buildable_id = bdid;
 	IF FOUND THEN
@@ -374,6 +502,6 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION tech.uoc_ship( TEXT , TEXT , INT , INT , INT , INT , INT , TEXT , INT ) TO :dbuser;
+GRANT EXECUTE ON FUNCTION tech.uoc_ship( TEXT , TEXT , INT , INT , INT , INT , INT , TEXT ) TO :dbuser;
 
 
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql
index 8c18e73..73109da 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/040-empire-functions.sql
@@ -3,7 +3,7 @@
 --
 -- Empire management functions and views
 --
--- Copyright(C) 2004-2010, DeepClone Development
+-- Copyright(C) 2004-2011, DeepClone Development
 -- --------------------------------------------------------
 
 
@@ -15,7 +15,7 @@
 --	pid		Planet identifier
 --	icash	Initial cash
 --
-CREATE OR REPLACE FUNCTION emp.create_empire( nid INT , pid INT , icash REAL )
+CREATE OR REPLACE FUNCTION emp.create_empire( nid INT , pid INT , icash DOUBLE PRECISION )
 		RETURNS VOID
 		STRICT
 		VOLATILE
@@ -81,49 +81,6 @@ $$ LANGUAGE SQL;
 GRANT EXECUTE ON FUNCTION emp.get_current( INT ) TO :dbuser;
 
 
-
---
--- Implements a technology
---
-
-CREATE OR REPLACE FUNCTION emp.implement_tech( e_id INT , l_id INT )
-		RETURNS VOID
-		STRICT VOLATILE
-		SECURITY DEFINER
-	AS $$
-DECLARE
-	e_cash	REAL;
-	lev		INT;
-	cost	REAL;
-BEGIN
-	SELECT INTO e_cash , lev , cost e.cash , et.level , tl.cost
-		FROM emp.empires e
-			INNER JOIN emp.technologies et
-				ON et.line_id = l_id AND et.empire_id = e.name_id
-			INNER JOIN tech.levels tl
-				ON tl.line_id = l_id AND tl.level = et.level
-					AND tl.points = floor( et.accumulated )
-					AND tl.cost <= e.cash
-		WHERE e.name_id = e_id
-		FOR UPDATE OF e , et;
-
-	IF NOT FOUND THEN
-		RETURN;
-	END IF;
-
-	UPDATE emp.empires
-		SET cash = e_cash - cost
-		WHERE name_id = e_id;
-	UPDATE emp.technologies
-		SET level = lev + 1 , accumulated = 0
-		WHERE empire_id = e_id AND line_id = l_id;
-END;
-$$ LANGUAGE plpgsql;
-
-GRANT EXECUTE ON FUNCTION emp.implement_tech( INT , INT ) TO :dbuser;
-
-
-
 --
 -- Add an enemy empire
 --
@@ -413,7 +370,7 @@ CREATE OR REPLACE FUNCTION emp.get_new_planet( e_id INT , p_name TEXT , OUT err_
 DECLARE
 	plid	INT;
 	accid	INT;
-	ccash	REAL;
+	ccash	DOUBLE PRECISION;
 	f_id	BIGINT;
 	fleets	BIGINT[];
 BEGIN
@@ -484,7 +441,7 @@ CREATE VIEW emp.enemies
 --
 
 CREATE VIEW emp.general_information
-	AS SELECT e.name_id AS id , en.name AS name ,
+	AS SELECT e.name_id AS id , en.name AS name , av.language ,
 			  ( CASE
 			  		WHEN av.status = 'QUITTING' THEN 'q'
 			  		WHEN av.status = 'VACATION' THEN 'v'
@@ -600,50 +557,6 @@ CREATE VIEW emp.overview
 GRANT SELECT ON emp.overview TO :dbuser;
 
 
---
--- Empire tech lines
---
-
-CREATE VIEW emp.tech_lines_view
-	AS SELECT e.name_id AS empire , tl.name_id AS tech_line ,
-				t1.translated_string AS name ,
-				t2.translated_string AS description
-			FROM emp.empires e
-				INNER JOIN emp.technologies et ON et.empire_id = e.name_id
-				INNER JOIN tech.lines tl ON tl.name_id = et.line_id
-				INNER JOIN naming.empire_names en ON en.id = e.name_id
-				INNER JOIN users.credentials c ON c.address_id = en.owner_id
-				INNER JOIN defs.translations t1 ON t1.string_id = tl.name_id AND t1.lang_id = c.language_id
-				INNER JOIN defs.translations t2 ON t2.string_id = tl.description_id AND t2.lang_id = c.language_id
-			ORDER BY t1.translated_string;
-
-GRANT SELECT ON emp.tech_lines_view TO :dbuser;
-
-
---
--- Empire technologies
---
-
-CREATE VIEW emp.technologies_view
-	AS SELECT e.name_id AS empire , tl.name_id AS tech_line ,
-				t1.translated_string AS name ,
-				t2.translated_string AS description ,
-				( et.level > tlv.level ) AS implemented ,
-				floor( 100 * et.accumulated / tlv.points ) AS progress ,
-				tlv.cost AS cost
-			FROM emp.empires e
-				INNER JOIN emp.technologies et ON et.empire_id = e.name_id
-				INNER JOIN tech.lines tl ON tl.name_id = et.line_id
-				INNER JOIN tech.levels tlv ON tlv.line_id = tl.name_id AND tlv.level <= et.level
-				INNER JOIN naming.empire_names en ON en.id = e.name_id
-				INNER JOIN users.credentials c ON c.address_id = en.owner_id
-				INNER JOIN defs.translations t1 ON t1.string_id = tlv.name_id AND t1.lang_id = c.language_id
-				INNER JOIN defs.translations t2 ON t2.string_id = tlv.description_id AND t2.lang_id = c.language_id
-			ORDER BY tl.name_id , tlv.level;
-
-GRANT SELECT ON emp.technologies_view TO :dbuser;
-
-
 --
 -- Enemy lists
 --
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/045-research-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/045-research-functions.sql
new file mode 100644
index 0000000..4ee2133
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/045-research-functions.sql
@@ -0,0 +1,227 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Research mananagement functions and views
+--
+-- Copyright(C) 2004-2011, DeepClone Development
+-- --------------------------------------------------------
+
+
+
+
+--
+-- Implement a technology
+--
+-- Parameters:
+--	e_id	Empire identifier
+--	t_name	Technology name
+--
+-- Returns:
+--	0		on success
+--	1		if the empire does not posses the necessary resources
+--	2		if the technology or empire were not found
+--
+
+CREATE OR REPLACE FUNCTION emp.implement_tech( e_id INT , t_name TEXT )
+		RETURNS INT
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $$
+DECLARE
+	e_cash	DOUBLE PRECISION;
+	t_id	INT;
+	t_cost	DOUBLE PRECISION;
+BEGIN
+	SELECT INTO e_cash , t_id , t_cost e.cash , ns.id , td.cost
+		FROM defs.strings ns
+			INNER JOIN tech.technologies td
+				ON td.name_id = ns.id
+			INNER JOIN emp.researched_technologies rt
+				ON rt.technology_id = td.name_id
+			INNER JOIN emp.empires e
+				ON rt.empire_id = e.name_id
+		WHERE e.name_id = e_id AND ns.name = t_name
+		FOR UPDATE OF e , rt;
+	IF NOT FOUND THEN
+		RETURN 2;
+	END IF;
+	
+	IF e_cash < t_cost THEN
+		RETURN 1;
+	END IF;
+	
+	UPDATE emp.empires
+		SET cash = e_cash - t_cost
+		WHERE name_id = e_id;
+	UPDATE emp.researched_technologies
+		SET implemented = TRUE
+		WHERE empire_id = e_id AND technology_id = t_id;
+
+	RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+
+GRANT EXECUTE ON FUNCTION emp.implement_tech( INT , TEXT ) TO :dbuser;
+
+
+
+--
+-- Prepare for research priorities updates
+--
+
+CREATE OR REPLACE FUNCTION emp.prepare_research_priorities_update( )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $$
+BEGIN
+	CREATE TEMPORARY TABLE research_priorities_updates(
+		technology		TEXT ,
+		priority		INT
+	);
+	CREATE INDEX rpu_technology ON research_priorities_updates ( technology );
+	IF session_user <> current_user THEN
+		EXECUTE 'GRANT INSERT ON research_priorities_updates TO ' || session_user;
+	END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+GRANT EXECUTE ON FUNCTION emp.prepare_research_priorities_update( ) TO :dbuser;
+
+
+--
+-- Applies research priorities updates
+--
+-- Parameters:
+--	e_id		identifier of the empire the updates should be applied to
+--
+-- Returns:
+--	an error code:
+--		0		success
+--		1		list of updates does not match current research topics
+--		2		invalid priorities
+--
+
+CREATE OR REPLACE FUNCTION emp.apply_research_priorities( IN e_id INT )
+		RETURNS INT
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $$
+DECLARE
+	rec		RECORD;
+	t		INT;
+	rval	INT;
+BEGIN
+	-- Lock empire and research info
+	PERFORM er.technology_id
+		FROM emp.empires e
+			INNER JOIN emp.research er ON er.empire_id = e.name_id
+		WHERE e.name_id = e_id
+		FOR UPDATE OF e , er;
+
+	-- Check values
+	t := 0;
+	rval := 0;
+	FOR rec IN SELECT rpu.priority , r.technology_id FROM research_priorities_updates rpu
+					LEFT OUTER JOIN emp.research_view r
+						ON ( r.detailed AND r.technology = rpu.technology )
+							OR ( NOT r.detailed AND ( 'unknown-' || ( r.technology_id * e_id )::TEXT ) = rpu.technology )
+					WHERE r.empire = e_id OR r.empire IS NULL
+				UNION SELECT rpu.priority , r.technology_id FROM research_priorities_updates rpu
+					RIGHT OUTER JOIN emp.research_view r
+						ON ( r.detailed AND r.technology = rpu.technology )
+							OR ( NOT r.detailed AND ( 'unknown-' || ( r.technology_id * e_id )::TEXT ) = rpu.technology )
+					WHERE r.empire = e_id
+	LOOP
+		IF rec.priority IS NULL OR rec.technology_id IS NULL THEN
+			rval := 1;
+			EXIT;
+		ELSIF rec.priority NOT BETWEEN 0 AND 100 THEN
+			rval := 2;
+			EXIT;
+		END IF;
+		t := t + rec.priority;
+	END LOOP;
+	IF rval = 0 AND t <> 100 THEN
+		rval := 2;
+	END IF;
+
+	-- Update research info
+	IF rval = 0 THEN
+		UPDATE emp.research er SET priority = rpu.priority
+			FROM research_priorities_updates rpu , emp.research_view rv
+			WHERE ( rpu.technology = CASE
+							WHEN rv.detailed THEN
+								rv.technology
+							ELSE
+								( 'unknown-' || ( rv.technology_id * e_id )::TEXT )
+						END )
+				AND rv.empire = e_id
+				AND er.empire_id = e_id
+				AND er.technology_id = rv.technology_id;
+	END IF;
+	DROP TABLE research_priorities_updates;
+	RETURN rval;
+END;
+$$ LANGUAGE plpgsql;
+
+GRANT EXECUTE ON FUNCTION emp.apply_research_priorities( INT ) TO :dbuser;
+
+
+
+
+--
+-- Base research view
+--
+
+CREATE VIEW emp.base_research_view
+	AS SELECT er.empire_id AS empire , er.technology_id , ns.name AS technology ,
+				td.points AS required , ( CASE
+					WHEN er.accumulated > td.points THEN
+						td.points - 1
+					ELSE
+						er.accumulated
+				END ) AS accumulated , er.priority
+			FROM emp.research er
+				INNER JOIN tech.technologies td ON td.name_id = er.technology_id
+				INNER JOIN defs.strings ns ON ns.id = er.technology_id;
+
+
+--
+-- Research view
+--
+
+CREATE VIEW emp.research_view
+	AS SELECT empire , technology , technology_id ,
+				FLOOR( 100 * accumulated / required )::INT AS completion ,
+				( accumulated >= sys.get_constant( 'game.research.minPoints' )
+					OR accumulated / required >= sys.get_constant( 'game.research.minRatio' ) ) AS detailed ,
+				priority
+			FROM emp.base_research_view;
+
+
+--
+-- Researched and implemented technologies view
+
+CREATE VIEW emp.known_techs_view
+	AS SELECT et.empire_id AS empire , et.technology_id , ns.name AS technology ,
+				( CASE WHEN et.implemented THEN NULL::INT ELSE td.cost END ) AS cost
+			FROM emp.researched_technologies et
+				INNER JOIN tech.technologies td ON td.name_id = et.technology_id
+				INNER JOIN defs.strings ns ON ns.id = et.technology_id;
+
+
+--
+-- Combined research and technologies view
+--
+
+CREATE VIEW emp.technologies_view
+	AS SELECT empire , technology_id , technology ,
+				detailed , completion , priority , NULL::INT AS cost
+			FROM emp.research_view
+	UNION ALL SELECT empire , technology_id , technology ,
+				TRUE AS detailed , NULL::INT AS completion , NULL::INT AS priority , cost
+			FROM emp.known_techs_view;
+
+GRANT SELECT ON emp.technologies_view TO :dbuser;
+
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
index e5304c2..0f75a48 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/050-computation-functions.sql
@@ -11,14 +11,14 @@
 -- sigma( x ) = exp( x ) / ( 1 + exp( x ) )
 --
 
-CREATE OR REPLACE FUNCTION verse.sigma( x REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.sigma( x DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
 	SELECT ( CASE
 		WHEN $1 < -100 THEN 0
 		WHEN $1 > 100 THEN 1
-		ELSE ( exp( $1 ) / ( 1 + exp( $1 ) ) )::REAL
+		ELSE ( exp( $1 ) / ( 1 + exp( $1 ) ) )
 	END );
 $$ LANGUAGE SQL;
 
@@ -28,8 +28,8 @@ $$ LANGUAGE SQL;
 -- poly( x , a , b , c ) = ( a * x + b ) * x + c 
 --
 
-CREATE OR REPLACE FUNCTION verse.poly( x REAL , a REAL , b REAL , c REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.poly( x DOUBLE PRECISION , a DOUBLE PRECISION , b DOUBLE PRECISION , c DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
 	SELECT ( $2 * $1 + $3 ) * $1 + $4;
@@ -41,11 +41,11 @@ $$ LANGUAGE SQL;
 -- Happiness curve, K1 constant
 --
 
-CREATE OR REPLACE FUNCTION verse.hcc_const_k1( xmax REAL , ymax REAL , xlimit REAL , ylimit REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.hcc_const_k1( xmax DOUBLE PRECISION , ymax DOUBLE PRECISION , xlimit DOUBLE PRECISION , ylimit DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
-	SELECT ( ( $4 - $2 ) / ( ( $3 - $1 ) ^ 2 ) )::REAL;
+	SELECT ( ( $4 - $2 ) / ( ( $3 - $1 ) ^ 2 ) );
 $$ LANGUAGE SQL;
 
 
@@ -54,11 +54,11 @@ $$ LANGUAGE SQL;
 -- Happiness curve, K2 constant
 --
 
-CREATE OR REPLACE FUNCTION verse.hcc_const_k2( ylimit REAL , yasymptote REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.hcc_const_k2( ylimit DOUBLE PRECISION , yasymptote DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
-	SELECT ( 2 * ( $1 - $2 ) )::REAL;
+	SELECT ( 2 * ( $1 - $2 ) );
 $$ LANGUAGE SQL;
 
 
@@ -67,11 +67,11 @@ $$ LANGUAGE SQL;
 -- Happiness curve, K3 constant
 --
 
-CREATE OR REPLACE FUNCTION verse.hcc_const_k3( xmax REAL , ymax REAL , xlimit REAL , ylimit REAL , yasymptote REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.hcc_const_k3( xmax DOUBLE PRECISION , ymax DOUBLE PRECISION , xlimit DOUBLE PRECISION , ylimit DOUBLE PRECISION , yasymptote DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
-	SELECT ( verse.hcc_const_k1( $1 , $2 , $3 , $4 ) * 4 * ( $3 - $1 ) / ( $5 - $4 ) ) ::REAL;
+	SELECT ( verse.hcc_const_k1( $1 , $2 , $3 , $4 ) * 4 * ( $3 - $1 ) / ( $5 - $4 ) );
 $$ LANGUAGE SQL;
 
 
@@ -80,15 +80,15 @@ $$ LANGUAGE SQL;
 -- Happiness curve, first part
 --
 
-CREATE OR REPLACE FUNCTION verse.hcc_part_1( x REAL , ymin REAL , ymax REAL , xmax REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.hcc_part_1( x DOUBLE PRECISION , ymin DOUBLE PRECISION , ymax DOUBLE PRECISION , xmax DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
 DECLARE
-	v	REAL;
+	v	DOUBLE PRECISION;
 BEGIN
 	v := ( ymin - ymax ) / xmax;
-	RETURN verse.poly( x , ( v / xmax )::REAL , ( -2 * v )::REAL , ymin );
+	RETURN verse.poly( x , v / xmax , -2 * v , ymin );
 END;
 $$ LANGUAGE plpgsql;
 
@@ -98,15 +98,15 @@ $$ LANGUAGE plpgsql;
 -- Happiness curve, second part
 --
 
-CREATE OR REPLACE FUNCTION verse.hcc_part_2( x REAL , xmax REAL , ymax REAL , xlimit REAL , ylimit REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.hcc_part_2( x DOUBLE PRECISION , xmax DOUBLE PRECISION , ymax DOUBLE PRECISION , xlimit DOUBLE PRECISION , ylimit DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
 DECLARE
-	k1	REAL;
+	k1	DOUBLE PRECISION;
 BEGIN
 	k1 := verse.hcc_const_k1( xmax , ymax , xlimit , ylimit );
-	RETURN verse.poly( x , k1 , ( -2 * xmax * k1 )::REAL , ( ymax + k1 * xmax * xmax )::REAL );
+	RETURN verse.poly( x , k1 , -2 * xmax * k1 , ymax + k1 * xmax * xmax );
 END;
 $$ LANGUAGE plpgsql;
 
@@ -116,17 +116,17 @@ $$ LANGUAGE plpgsql;
 -- Happiness curve, third part
 --
 
-CREATE OR REPLACE FUNCTION verse.hcc_part_3( x REAL , xmax REAL , ymax REAL , xlimit REAL , ylimit REAL , yasymptote REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.hcc_part_3( x DOUBLE PRECISION , xmax DOUBLE PRECISION , ymax DOUBLE PRECISION , xlimit DOUBLE PRECISION , ylimit DOUBLE PRECISION , yasymptote DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
 DECLARE
-	k2	REAL;
-	k3	REAL;
+	k2	DOUBLE PRECISION;
+	k3	DOUBLE PRECISION;
 BEGIN
 	k2 := verse.hcc_const_k2( ylimit , yasymptote );
 	k3 := verse.hcc_const_k3( xmax , ymax , xlimit , ylimit , yasymptote );
-	RETURN yasymptote + k2 * ( 1 - verse.sigma( ( k3 * ( x - xlimit ) ) )::REAL );
+	RETURN yasymptote + k2 * ( 1 - verse.sigma( ( k3 * ( x - xlimit ) ) ) );
 END;
 $$ LANGUAGE plpgsql;
 
@@ -136,8 +136,8 @@ $$ LANGUAGE plpgsql;
 -- Happiness curve
 --
 
-CREATE OR REPLACE FUNCTION verse.happiness_curve( x REAL , ymin REAL , xmax REAL , ymax REAL , xlimit REAL , ylimit REAL , yasymptote REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.happiness_curve( x DOUBLE PRECISION , ymin DOUBLE PRECISION , xmax DOUBLE PRECISION , ymax DOUBLE PRECISION , xlimit DOUBLE PRECISION , ylimit DOUBLE PRECISION , yasymptote DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE SECURITY INVOKER
 AS $$
 	SELECT (CASE
@@ -156,37 +156,37 @@ $$ LANGUAGE SQL;
 -- Happiness computation
 --
 
-CREATE OR REPLACE FUNCTION verse.compute_happiness( population REAL , workers REAL , defence REAL , empsize INT )
-		RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.compute_happiness( population DOUBLE PRECISION , workers DOUBLE PRECISION , defence DOUBLE PRECISION , empsize INT )
+		RETURNS DOUBLE PRECISION
 		STRICT STABLE SECURITY INVOKER
 	AS $$
 DECLARE
-	whappiness	REAL;
-	dhappiness	REAL;
-	shappiness	REAL;
+	whappiness	DOUBLE PRECISION;
+	dhappiness	DOUBLE PRECISION;
+	shappiness	DOUBLE PRECISION;
 BEGIN
 	-- Work-related happiness
 	whappiness := verse.happiness_curve(
-		( workers / population )::REAL ,
+		workers / population ,
 		sys.get_constant( 'game.happiness.noEmployment' ) , 1.0 , 1.0 , 
 		sys.get_constant( 'game.happiness.employmentLimit' ) , 0.5 , 0
 	);
 
 	-- Defence-related happiness
 	dhappiness := verse.happiness_curve(
-		( sys.get_constant( 'game.happiness.popPerDefencePoint' ) * defence / population )::REAL ,
+		sys.get_constant( 'game.happiness.popPerDefencePoint' ) * defence / population ,
 		sys.get_constant( 'game.happiness.noDefence' ) , 1.0 , 1.0 , 
 		sys.get_constant( 'game.happiness.defenceLimit' ) , 0.5 , 0
 	);
 	
 	-- Influence of empire size
 	shappiness := verse.happiness_curve(
-		( empsize::REAL / sys.get_constant( 'game.happiness.idealEmpireSize' ) )::REAL ,
+		empsize / sys.get_constant( 'game.happiness.idealEmpireSize' ) ,
 		sys.get_constant( 'game.happiness.smallEmpire' ) , 1.0 , 1.0 , 
 		sys.get_constant( 'game.happiness.eSizeLimit' ) , 0.5 , 0
 	);
 	
-	RETURN ( shappiness * ( whappiness + dhappiness ) / 2.0 )::REAL;
+	RETURN shappiness * ( whappiness + dhappiness ) / 2.0;
 END;
 $$ LANGUAGE plpgsql;
 
@@ -195,14 +195,14 @@ $$ LANGUAGE plpgsql;
 --
 -- Production adjustment
 --
-CREATE OR REPLACE FUNCTION verse.adjust_production( prod REAL , happiness REAL )
-	RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.adjust_production( prod DOUBLE PRECISION , happiness DOUBLE PRECISION )
+	RETURNS DOUBLE PRECISION
 	STRICT IMMUTABLE
 	SECURITY INVOKER
 AS $$
 	SELECT ( CASE
 		WHEN $2 < sys.get_constant( 'game.happiness.strike' ) THEN
-			( $1 * ( 1 - ( $2 / sys.get_constant( 'game.happiness.strike' ) ) ) )::REAL
+			$1 * ( 1 - ( $2 / sys.get_constant( 'game.happiness.strike' ) ) )
 		ELSE
 			$1
 	END );
@@ -213,15 +213,15 @@ $$ LANGUAGE SQL;
 -- Income computation
 --
 
-CREATE OR REPLACE FUNCTION verse.compute_income( population REAL , happiness REAL , cashprod REAL )
-		RETURNS REAL
+CREATE OR REPLACE FUNCTION verse.compute_income( population DOUBLE PRECISION , happiness DOUBLE PRECISION , cashprod DOUBLE PRECISION )
+		RETURNS DOUBLE PRECISION
 		STRICT STABLE
 		SECURITY INVOKER
 	AS $$
 DECLARE
-	base	REAL;
-	badj	REAL;
-	cprod	REAL;
+	base	DOUBLE PRECISION;
+	badj	DOUBLE PRECISION;
+	cprod	DOUBLE PRECISION;
 BEGIN
 	badj := ( 1 - verse.adjust_production( 1.0 , happiness ) ) * sys.get_constant( 'game.work.strikeEffect' );
 	base := floor( population ) * sys.get_constant( 'game.work.population' ) * ( 1 - badj );
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
index 780986a..bfccac6 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/060-universe-functions.sql
@@ -19,14 +19,14 @@
 --
 
 CREATE OR REPLACE FUNCTION verse.get_raw_production( pid INT , pt building_output_type )
-		RETURNS REAL
+		RETURNS DOUBLE PRECISION
 		STRICT STABLE
 		SECURITY DEFINER
 	AS $$
 DECLARE
-	rv	REAL;
+	rv	DOUBLE PRECISION;
 BEGIN
-	SELECT INTO rv SUM( b.amount * d.output )::REAL
+	SELECT INTO rv SUM( b.amount * d.output )
 		FROM verse.planet_buildings b
 			INNER JOIN tech.buildings d
 				ON d.buildable_id = b.building_id AND d.output_type = pt
@@ -114,14 +114,14 @@ $$ LANGUAGE SQL;
 --
 
 CREATE OR REPLACE FUNCTION verse.get_planet_upkeep( pid INT )
-		RETURNS REAL
+		RETURNS DOUBLE PRECISION
 		STRICT STABLE
 		SECURITY INVOKER
 	AS $$
 DECLARE
-	rv	REAL;
+	rv	DOUBLE PRECISION;
 BEGIN
-	SELECT INTO rv SUM( b.amount * d.upkeep )::REAL
+	SELECT INTO rv SUM( b.amount * d.upkeep )
 		FROM verse.planet_buildings b
 			INNER JOIN tech.buildables d
 				ON d.name_id = b.building_id
@@ -145,7 +145,7 @@ $$ LANGUAGE plpgsql;
 --	npics	Amount of planet pictures
 --
 
-CREATE OR REPLACE FUNCTION verse.create_planet( sid INT , o INT , ipop REAL , npics INT )
+CREATE OR REPLACE FUNCTION verse.create_planet( sid INT , o INT , ipop DOUBLE PRECISION , npics INT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -156,7 +156,7 @@ DECLARE
 	bpp			INT;
 	uid			BIGINT;
 	utp			update_type;
-	happiness	REAL;
+	happiness	DOUBLE PRECISION;
 BEGIN
 	-- Planet name and planet
 	pnid := naming.create_map_name( 'P' );
@@ -217,7 +217,7 @@ $$ LANGUAGE plpgsql;
 --	npics	Amount of planet pictures
 --
 
-CREATE OR REPLACE FUNCTION verse.create_system( sx INT , sy INT , ipop REAL , npics INT )
+CREATE OR REPLACE FUNCTION verse.create_system( sx INT , sy INT , ipop DOUBLE PRECISION , npics INT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -249,7 +249,7 @@ $$ LANGUAGE plpgsql;
 --	ipop				Initial population
 --
 
-CREATE OR REPLACE FUNCTION verse.create_systems( x0 INT , y0 INT , x1 INT , y1 INT , ipop REAL )
+CREATE OR REPLACE FUNCTION verse.create_systems( x0 INT , y0 INT , x1 INT , y1 INT , ipop DOUBLE PRECISION )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -283,7 +283,7 @@ CREATE OR REPLACE FUNCTION verse.generate_initial_universe( )
 	AS $$
 DECLARE
 	sz		INT;
-	pop 	REAL;
+	pop 	DOUBLE PRECISION;
 	npics	INT;
 BEGIN
 	sz := floor( sys.get_constant( 'game.universe.initialSize' ) );
@@ -316,7 +316,7 @@ DECLARE
 	y0		INT;
 	x1		INT;
 	y1		INT;
-	pop		REAL;
+	pop		DOUBLE PRECISION;
 BEGIN
 	-- Get current bounds
 	SELECT INTO min_x , max_x , min_y , max_y
@@ -368,7 +368,7 @@ CREATE OR REPLACE FUNCTION verse.generate( )
 	AS $$
 DECLARE
 	p_count	INT;
-	f_ratio	REAL;
+	f_ratio	DOUBLE PRECISION;
 BEGIN
 	-- Get total planet count
 	SELECT INTO p_count 5 * count(*)
@@ -381,7 +381,7 @@ BEGIN
 	END IF;
 
 	-- Get available planets ratio
-	SELECT INTO f_ratio count(*)::REAL / p_count::REAL
+	SELECT INTO f_ratio count(*)::DOUBLE PRECISION / p_count::DOUBLE PRECISION
 		FROM verse.available_planets;
 
 	-- Expand universe if required
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
index 06247c1..582e52a 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/140-planets-functions.sql
@@ -175,17 +175,17 @@ CREATE OR REPLACE FUNCTION verse.get_orbital_view( e_id INT , p_id INT )
 	AS $$
 DECLARE
 	rv		planet_orbital_data;
-	happ	REAL;
+	happ	DOUBLE PRECISION;
 	e_att	BOOLEAN;
 	rec		RECORD;
 BEGIN
 	-- Get the planet's population and defence
 	SELECT INTO rv.population , happ
-			floor( p.population )::BIGINT , ( ph.current / p.population )::REAL
+			floor( p.population )::BIGINT , ( ph.current / p.population )::DOUBLE PRECISION
 		FROM verse.planets p
 			INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
 		WHERE p.name_id = p_id;
-	rv.defence := round( verse.adjust_production( verse.get_raw_production( p_id , 'DEF' ) , happ ) );
+	rv.defence := verse.adjust_production( verse.get_raw_production( p_id , 'DEF' ) , happ );
 
 	-- Get the empire's fleet mode
 	SELECT INTO e_att f.attacking
@@ -332,8 +332,7 @@ AS $$
 				( bd.workers * b.amount )::INT AS jobs ,
 				( bd.upkeep * b.amount  )::BIGINT AS upkeep ,
 				bd.output_type AS p_type ,
-				floor( verse.adjust_production( ( bd.output * b.amount )::REAL ,
-						( ph.current / p.population )::REAL )
+				floor( verse.adjust_production( bd.output * b.amount , ph.current / p.population )
 				)::BIGINT AS p_value
 		FROM verse.planet_buildings b
 			INNER JOIN verse.planets p ON p.name_id = b.planet_id
@@ -373,9 +372,9 @@ AS $$
 				ELSE floor( qi.amount * bd.cost - ( CASE WHEN qi.queue_order = 0 THEN q.money ELSE 0 END ) )
 			END )::BIGINT AS investment ,
 			( CASE
-				WHEN ceil( verse.adjust_production( ( p.population * sys.get_constant( 'game.work.wuPerPopUnit' ) )::REAL , ( ph.current / p.population )::REAL ) ) = 0 THEN NULL
+				WHEN ceil( verse.adjust_production( p.population * sys.get_constant( 'game.work.wuPerPopUnit' ) , ph.current / p.population ) ) = 0 THEN NULL
 				ELSE ceil( ( qi.amount * bd.work * ( CASE WHEN qi.destroy THEN sys.get_constant( 'game.work.destructionWork' ) ELSE 1 END ) - ( CASE WHEN qi.queue_order = 0 THEN q.work ELSE 0 END ) )
-							/ verse.adjust_production( ( p.population * sys.get_constant( 'game.work.wuPerPopUnit' ) )::REAL , ( ph.current / p.population )::REAL ) )
+							/ verse.adjust_production( p.population * sys.get_constant( 'game.work.wuPerPopUnit' ) , ph.current / p.population ) )
 			END )::BIGINT AS time_left
 		FROM verse.planets p
 			INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
@@ -414,9 +413,9 @@ AS $$
 			qi.amount AS amount , FALSE AS destroy ,
 			floor( qi.amount * bd.cost - ( CASE WHEN qi.queue_order = 0 THEN q.money ELSE 0 END ) )::BIGINT AS investment ,
 			( CASE
-				WHEN ceil( verse.adjust_production( verse.get_raw_production( $1 , 'WORK' ) , ( ph.current / p.population )::REAL ) ) = 0 THEN NULL
+				WHEN ceil( verse.adjust_production( verse.get_raw_production( $1 , 'WORK' ) , ph.current / p.population ) ) = 0 THEN NULL
 				ELSE ceil( ( qi.amount * bd.work - ( CASE WHEN qi.queue_order = 0 THEN q.work ELSE 0 END ) )
-							/ verse.adjust_production( verse.get_raw_production( $1 , 'WORK' ) , ( ph.current / p.population )::REAL ) )
+							/ verse.adjust_production( verse.get_raw_production( $1 , 'WORK' ) , ph.current / p.population ) )
 			END )::BIGINT AS time_left
 		FROM verse.planets p
 			INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
@@ -465,12 +464,12 @@ AS $$
 			UNION SELECT bv.*
 			    FROM tech.buildings_view bv
 					INNER JOIN tech.buildable_requirements r ON r.buildable_id = bv.name_id
-					INNER JOIN tech.levels l ON l.id = r.level_id
+					INNER JOIN tech.technologies l ON l.name_id = r.technology_id
 					INNER JOIN emp.planets ep ON ep.planet_id = $1
-					INNER JOIN emp.technologies t
-					    ON t.empire_id = ep.empire_id AND t.line_id = l.line_id AND t.level > l.level
+					INNER JOIN emp.researched_technologies t
+					    ON t.empire_id = ep.empire_id AND t.technology_id = l.name_id
 		    ) AS bv , (
-			SELECT verse.adjust_production( ( p.population * sys.get_constant( 'game.work.wuPerPopUnit' ) )::REAL , ( ph.current / p.population )::REAL ) AS p_work ,
+			SELECT verse.adjust_production( p.population * sys.get_constant( 'game.work.wuPerPopUnit' ) , ph.current / p.population ) AS p_work ,
 				c.language_id AS language
 			    FROM verse.planets p
 					INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
@@ -517,12 +516,12 @@ AS $$
 			UNION SELECT bv.*
 			    FROM tech.ships_view bv
 					INNER JOIN tech.buildable_requirements r ON r.buildable_id = bv.name_id
-					INNER JOIN tech.levels l ON l.id = r.level_id
+					INNER JOIN tech.technologies l ON l.name_id = r.technology_id
 					INNER JOIN emp.planets ep ON ep.planet_id = $1
-					INNER JOIN emp.technologies t
-					    ON t.empire_id = ep.empire_id AND t.line_id = l.line_id AND t.level > l.level
+					INNER JOIN emp.researched_technologies t
+					    ON t.empire_id = ep.empire_id AND t.technology_id = l.name_id
 		    ) AS bv , (
-			SELECT verse.adjust_production( verse.get_raw_production( $1 , 'WORK' ) , ( ph.current / p.population )::REAL ) AS p_work ,
+			SELECT verse.adjust_production( verse.get_raw_production( $1 , 'WORK' ) , ph.current / p.population ) AS p_work ,
 				c.language_id AS language
 			    FROM verse.planets p
 					INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
@@ -594,7 +593,7 @@ CREATE OR REPLACE FUNCTION verse.flush_build_queue( p_id INT )
 	AS $$
 DECLARE
 	e_id	INT;
-	q_cash	REAL;
+	q_cash	DOUBLE PRECISION;
 BEGIN
 	SELECT INTO e_id , q_cash e.name_id , q.money
 		FROM verse.planets p
@@ -633,7 +632,7 @@ CREATE OR REPLACE FUNCTION verse.flush_military_queue( p_id INT )
 	AS $$
 DECLARE
 	e_id	INT;
-	q_cash	REAL;
+	q_cash	DOUBLE PRECISION;
 BEGIN
 	SELECT INTO e_id , q_cash e.name_id , q.money
 		FROM verse.planets p
@@ -699,10 +698,10 @@ BEGIN
 		FROM tech.ships s
 			LEFT OUTER JOIN tech.buildable_requirements r
 				ON r.buildable_id = s.buildable_id
-			LEFT OUTER JOIN tech.levels l
-				ON l.id = r.level_id
-			LEFT OUTER JOIN emp.technologies t
-				ON t.empire_id = e_id AND t.line_id = l.line_id AND t.level > l.level
+			LEFT OUTER JOIN tech.technologies l
+				ON l.name_id = r.technology_id
+			LEFT OUTER JOIN emp.researched_technologies t
+				ON t.empire_id = e_id AND t.technology_id = l.name_id
 		WHERE s.buildable_id = s_id;
 	IF NOT FOUND OR ( has_level IS NULL AND dep_level IS NOT NULL ) THEN
 		RETURN;
@@ -765,10 +764,10 @@ BEGIN
 		FROM tech.buildings b
 			LEFT OUTER JOIN tech.buildable_requirements r
 				ON r.buildable_id = b.buildable_id
-			LEFT OUTER JOIN tech.levels l
-				ON l.id = r.level_id
-			LEFT OUTER JOIN emp.technologies t
-				ON t.empire_id = e_id AND t.line_id = l.line_id AND t.level > l.level
+			LEFT OUTER JOIN tech.technologies l
+				ON l.name_id = r.technology_id
+			LEFT OUTER JOIN emp.researched_technologies t
+				ON t.empire_id = e_id AND t.technology_id = l.name_id
 		WHERE b.buildable_id = b_id;
 	IF NOT FOUND OR ( has_level IS NULL AND dep_level IS NOT NULL ) THEN
 		RETURN;
@@ -979,7 +978,7 @@ $$ LANGUAGE plpgsql;
 --	tick		Current tick
 --
 
-CREATE OR REPLACE FUNCTION verse.inflict_battle_damage( p_id INT , t_power BIGINT , dmg REAL , b_id BIGINT , tick BIGINT )
+CREATE OR REPLACE FUNCTION verse.inflict_battle_damage( p_id INT , t_power BIGINT , dmg DOUBLE PRECISION , b_id BIGINT , tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -987,7 +986,7 @@ CREATE OR REPLACE FUNCTION verse.inflict_battle_damage( p_id INT , t_power BIGIN
 DECLARE
 	rec		RECORD;
 	bp_id	BIGINT;
-	st_dmg	REAL;
+	st_dmg	DOUBLE PRECISION;
 	n_dest	INT;
 BEGIN
 	PERFORM sys.write_log( 'BattleUpdate' , 'TRACE'::log_level , 'Inflicting ' || dmg
@@ -1038,18 +1037,18 @@ $$ LANGUAGE plpgsql;
 --	d_ratio		Debt damage ratio
 --
 
-CREATE OR REPLACE FUNCTION verse.handle_debt( e_id INT , t_upkeep REAL , debt REAL , d_ratio REAL )
+CREATE OR REPLACE FUNCTION verse.handle_debt( e_id INT , t_upkeep DOUBLE PRECISION , debt DOUBLE PRECISION , d_ratio DOUBLE PRECISION )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
 	AS $$
 DECLARE
 	tick		BIGINT;
-	tot_damage	REAL;
+	tot_damage	DOUBLE PRECISION;
 	p_rec		RECORD;
 	b_rec		RECORD;
 	bp_id		BIGINT;
-	b_damage	REAL;
+	b_damage	DOUBLE PRECISION;
 	n_destroy	INT;
 BEGIN
 	tick := sys.get_tick( ) - 1;
@@ -1066,7 +1065,7 @@ BEGIN
 		bp_id := NULL;
 		
 		FOR b_rec IN SELECT b.building_id AS building , b.amount AS amount ,
-							( b.amount * bb.upkeep )::REAL AS upkeep ,
+							b.amount * bb.upkeep AS upkeep ,
 							b.damage AS damage , ( bd.output_type = 'DEF' ) AS is_def
 						FROM verse.planet_buildings b
 							INNER JOIN tech.buildables bb ON bb.name_id = b.building_id
@@ -1099,4 +1098,4 @@ BEGIN
 		END LOOP;
 	END LOOP;
 END;
-$$ LANGUAGE plpgsql;
+$$ LANGUAGE plpgsql;
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
index ef34b5e..94827f0 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/150-battle-functions.sql
@@ -626,7 +626,7 @@ $$ LANGUAGE plpgsql;
 --	tick	Current tick identifier
 --
 
-CREATE OR REPLACE FUNCTION battles.inflict_damage( b_id BIGINT , dmg REAL , att BOOLEAN , tick BIGINT )
+CREATE OR REPLACE FUNCTION battles.inflict_damage( b_id BIGINT , dmg DOUBLE PRECISION , att BOOLEAN , tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -654,9 +654,9 @@ BEGIN
 		st_power := battles.get_defence_power( b_id , tick );
 		tot_power := tot_power + st_power;
 		PERFORM sys.write_log( 'BattleUpdate' , 'TRACE'::log_level , 'About to inflict planet damage; total power: ' || tot_power
-			|| '; planet power: ' || st_power || '; computed damage: ' || ( dmg * st_power / tot_power )::REAL );
+			|| '; planet power: ' || st_power || '; computed damage: ' || ( dmg * st_power / tot_power ) );
 		IF st_power <> 0 THEN
-			PERFORM verse.inflict_battle_damage( planet , st_power , ( dmg * st_power / tot_power )::REAL , b_id , tick );
+			PERFORM verse.inflict_battle_damage( planet , st_power , dmg * st_power / tot_power , b_id , tick );
 		END IF;
 	END IF;
 	
@@ -668,7 +668,7 @@ BEGIN
 					LEFT OUTER JOIN fleets.movements m ON m.fleet_id = f.id
 				WHERE b.id = b_id AND m.fleet_id IS NULL AND f.attacking = att
 	LOOP
-		PERFORM fleets.inflict_battle_damage( rec.id , ( dmg * rec.power / tot_power )::REAL , b_id , tick );
+		PERFORM fleets.inflict_battle_damage( rec.id , dmg * rec.power / tot_power , b_id , tick );
 	END LOOP;
 END;
 $$ LANGUAGE plpgsql;
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql
index c649e10..4309c03 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/160-battle-views.sql
@@ -256,8 +256,8 @@ CREATE VIEW battles.buildings_history
 					WHEN rbp.raw_power = 0 THEN
 						rbh.raw_power
 					ELSE
-						rbh.raw_power::REAL * rbp.actual_power::REAL / rbp.raw_power::REAL
-				END )::REAL AS power
+						rbh.raw_power * rbp.actual_power / rbp.raw_power
+				END ) AS power
 			FROM battles.empire_list_view elv
 				INNER JOIN battles.raw_buildings_history rbh USING (battle)
 				INNER JOIN battles.raw_buildings_power rbp USING (battle,tick)
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
index f7d802c..4d2e02b 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/165-fleets-functions.sql
@@ -55,7 +55,7 @@ $$ LANGUAGE plpgsql;
 --	the in-system movement's duration
 --
 
-CREATE OR REPLACE FUNCTION fleets.compute_insystem_duration( f_time INT , s_orbit REAL , d_orbit REAL )
+CREATE OR REPLACE FUNCTION fleets.compute_insystem_duration( f_time INT , s_orbit DOUBLE PRECISION , d_orbit DOUBLE PRECISION )
 		RETURNS INT
 		STRICT IMMUTABLE
 		SECURITY INVOKER
@@ -84,7 +84,7 @@ $$ LANGUAGE plpgsql;
 --	the outer space movement's duration
 --
 
-CREATE OR REPLACE FUNCTION fleets.compute_outerspace_duration( f_time INT , s_x REAL , s_y REAL , d_x REAL , d_y REAL )
+CREATE OR REPLACE FUNCTION fleets.compute_outerspace_duration( f_time INT , s_x DOUBLE PRECISION , s_y DOUBLE PRECISION , d_x DOUBLE PRECISION , d_y DOUBLE PRECISION )
 		RETURNS INT
 		STRICT IMMUTABLE
 		SECURITY INVOKER
@@ -115,16 +115,16 @@ $$ LANGUAGE plpgsql;
 
 CREATE OR REPLACE FUNCTION fleets.compute_current_orbit(
 			f_time INT , rp_orbit INT , outwards BOOLEAN , past_rp BOOLEAN , ft_left INT )
-		RETURNS REAL
+		RETURNS DOUBLE PRECISION
 		STRICT IMMUTABLE
 		SECURITY INVOKER
 	AS $$
 DECLARE
-	dist	REAL;
-	dir		REAL;
-	rloc	REAL;
+	dist	DOUBLE PRECISION;
+	dir		DOUBLE PRECISION;
+	rloc	DOUBLE PRECISION;
 BEGIN
-	dist := 1.0 - ft_left::REAL / f_time::REAL;
+	dist := 1.0 - ft_left::DOUBLE PRECISION / f_time::DOUBLE PRECISION;
 	dir := ( CASE WHEN outwards THEN 0.5 ELSE -0.5 END );
 	IF past_rp THEN
 		rloc := rp_orbit;
@@ -157,13 +157,14 @@ $$ LANGUAGE plpgsql;
 --
 
 CREATE OR REPLACE FUNCTION fleets.compute_current_location(
-			f_time INT , s_x REAL , s_y REAL , d_x REAL , d_y REAL , r_time INT ,
-			OUT c_x REAL , OUT c_y REAL )
+			f_time INT , s_x DOUBLE PRECISION , s_y DOUBLE PRECISION ,
+			d_x DOUBLE PRECISION , d_y DOUBLE PRECISION , r_time INT ,
+			OUT c_x DOUBLE PRECISION , OUT c_y DOUBLE PRECISION )
 		STRICT IMMUTABLE
 		SECURITY INVOKER
 	AS $$
 DECLARE
-	tot_time	REAL;
+	tot_time	DOUBLE PRECISION;
 BEGIN
 	tot_time := fleets.compute_outerspace_duration( f_time , s_x , s_y , d_x , d_y );
 	c_x := s_x + ( d_x - s_x ) * ( 1 - r_time / tot_time );
@@ -251,7 +252,7 @@ $$ LANGUAGE plpgsql;
 --
 
 CREATE OR REPLACE FUNCTION fleets.compute_insystem_redirect(
-			f_time INT , s_sys INT , s_orbit REAL , d_id INT ,
+			f_time INT , s_sys INT , s_orbit DOUBLE PRECISION , d_id INT ,
 			OUT duration INT , OUT direction BOOLEAN , OUT s_duration INT ,
 			OUT ref_point INT , OUT past_rp BOOLEAN )
 		STRICT IMMUTABLE
@@ -260,7 +261,7 @@ CREATE OR REPLACE FUNCTION fleets.compute_insystem_redirect(
 DECLARE
 	s_rec	RECORD;
 	d_rec	RECORD;
-	torb	REAL;
+	torb	DOUBLE PRECISION;
 	rporb	INT;
 BEGIN
 	-- Get destination planet coordinates, orbit and system ID
@@ -294,9 +295,9 @@ BEGIN
 		ELSE
 			torb := floor( torb );
 		END IF;
-		s_duration := fleets.compute_insystem_duration( f_time , s_orbit , ( torb / 2 )::REAL );
+		s_duration := fleets.compute_insystem_duration( f_time , s_orbit , torb / 2 );
 		rporb := round( s_orbit );
-		past_rp := ( CASE WHEN direction THEN ( rporb::REAL <= s_orbit ) ELSE ( rporb::REAL >= s_orbit ) END );
+		past_rp := ( CASE WHEN direction THEN ( rporb <= s_orbit ) ELSE ( rporb >= s_orbit ) END );
 	END IF;
 	SELECT INTO ref_point name_id FROM verse.planets p
 		WHERE p.system_id = s_sys AND orbit = rporb;
@@ -322,7 +323,7 @@ $$ LANGUAGE plpgsql;
 --
 
 CREATE OR REPLACE FUNCTION fleets.compute_outerspace_redirect(
-			f_time INT , s_x REAL , s_y REAL , d_id INT ,
+			f_time INT , s_x DOUBLE PRECISION , s_y DOUBLE PRECISION , d_id INT ,
 			OUT duration INT , OUT s_duration INT )
 		STRICT IMMUTABLE
 		SECURITY INVOKER
@@ -365,8 +366,8 @@ DECLARE
 	dir		BOOLEAN;
 	rpid	INT;
 	prp		BOOLEAN;
-	cx		REAL;
-	cy		REAL;
+	cx		DOUBLE PRECISION;
+	cy		DOUBLE PRECISION;
 BEGIN
 	-- Lock fleets and planets
 	PERFORM * FROM fleets.fleets f
@@ -676,10 +677,10 @@ DECLARE
 	old_ft	INT;
 	new_ft	INT;
 	sp_ft	INT;
-	x		REAL;
-	y		REAL;
-	cx		REAL;
-	cy		REAL;
+	x		DOUBLE PRECISION;
+	y		DOUBLE PRECISION;
+	cx		DOUBLE PRECISION;
+	cy		DOUBLE PRECISION;
 	sid		INT;
 BEGIN
 	SELECT INTO main * FROM fleet_split_main;
@@ -705,7 +706,7 @@ BEGIN
 		IF new_ft <> old_ft THEN
 			IF ism_rec IS NULL THEN
 				-- Outer space movement
-				SELECT INTO x , y s.x::REAL , s.y::REAL
+				SELECT INTO x , y s.x , s.y
 					FROM verse.planets p
 						INNER JOIN verse.systems s ON s.id = p.system_id
 					WHERE p.name_id = main.location;
@@ -754,7 +755,7 @@ BEGIN
 				IF sp_ft <> old_ft THEN
 					IF ism_rec IS NULL THEN
 						-- Outer space movement
-						SELECT INTO x , y s.x::REAL , s.y::REAL
+						SELECT INTO x , y s.x , s.y
 							FROM verse.planets p
 								INNER JOIN verse.systems s ON s.id = p.system_id
 							WHERE p.name_id = main.location;
@@ -983,7 +984,7 @@ GRANT EXECUTE ON FUNCTION fleets.disband( INT , BIGINT[] ) TO :dbuser;
 --	tick	Current tick
 --
 
-CREATE OR REPLACE FUNCTION fleets.inflict_battle_damage( f_id BIGINT , dmg REAL , b_id BIGINT , tick BIGINT )
+CREATE OR REPLACE FUNCTION fleets.inflict_battle_damage( f_id BIGINT , dmg DOUBLE PRECISION , b_id BIGINT , tick BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
@@ -995,7 +996,7 @@ DECLARE
 	bp_id	BIGINT;
 	bf_id	BIGINT;
 	rec		RECORD;
-	st_dmg	REAL;
+	st_dmg	DOUBLE PRECISION;
 	n_dest	INT;
 	found	INT;
 	deleted	INT;
@@ -1066,19 +1067,19 @@ $$ LANGUAGE plpgsql;
 --	d_ratio		Debt damage ratio
 --
 
-CREATE OR REPLACE FUNCTION fleets.handle_debt( e_id INT , t_upkeep REAL , debt REAL , d_ratio REAL )
+CREATE OR REPLACE FUNCTION fleets.handle_debt( e_id INT , t_upkeep DOUBLE PRECISION , debt DOUBLE PRECISION , d_ratio DOUBLE PRECISION )
 		RETURNS VOID
 		STRICT VOLATILE
 		SECURITY INVOKER
 	AS $$
 DECLARE
-	tot_damage	REAL;
+	tot_damage	DOUBLE PRECISION;
 	f_rec		RECORD;
 	s_rec		RECORD;
 	n_found		INT;
 	n_killed	INT;
 	s_killed	INT;
-	s_damage	REAL;
+	s_damage	DOUBLE PRECISION;
 	n_ships		INT;
 	tick		BIGINT;
 	bp_id		BIGINT;
@@ -1086,10 +1087,10 @@ DECLARE
 	mv_rec		fleets.movements%ROWTYPE;
 	ism_rec		fleets.ms_system%ROWTYPE;
 	osm_rec		fleets.ms_space%ROWTYPE;
-	x			REAL;
-	y			REAL;
-	cx			REAL;
-	cy			REAL;
+	x			DOUBLE PRECISION;
+	y			DOUBLE PRECISION;
+	cx			DOUBLE PRECISION;
+	cy			DOUBLE PRECISION;
 	sid			INT;
 BEGIN
 	tick := sys.get_tick( ) - 1;
@@ -1120,7 +1121,7 @@ BEGIN
 		n_killed := 0;
 		s_killed := 0;
 		FOR s_rec IN SELECT s.ship_id AS ship , s.amount AS amount , s.damage AS damage ,
-							( d.upkeep * s.amount )::REAL AS upkeep
+							d.upkeep * s.amount AS upkeep
 						FROM fleets.ships s
 							INNER JOIN tech.buildables d ON d.name_id = s.ship_id
 						WHERE s.fleet_id = f_rec.fleet
@@ -1174,7 +1175,7 @@ BEGIN
 
 			IF ism_rec IS NULL THEN
 				-- Outer space movement
-				SELECT INTO x , y s.x::REAL , s.y::REAL
+				SELECT INTO x , y s.x , s.y
 					FROM verse.planets p
 						INNER JOIN verse.systems s ON s.id = p.system_id
 					WHERE p.name_id = f_rec.location;
@@ -1386,7 +1387,7 @@ GRANT SELECT ON fleets.static_fleets TO :dbuser;
 CREATE VIEW fleets.outer_space_fleets
 	AS SELECT s.movement_id AS id , m.state_time_left AS time_left ,
 				s.start_x AS x0 , s.start_y AS y0 ,
-				ts.x::REAL AS x1 , ts.y::REAL AS y1
+				ts.x::DOUBLE PRECISION AS x1 , ts.y::DOUBLE PRECISION AS y1
 			FROM fleets.ms_space s
 				INNER JOIN fleets.movements m ON m.fleet_id = s.movement_id
 				INNER JOIN fleets.fleets f ON m.fleet_id = f.id
@@ -1422,14 +1423,14 @@ CREATE VIEW fleets.moving_fleets
 				f.location_id AS to_id , dn.name AS to_name ,
 				( CASE
 					WHEN osf.id IS NULL THEN isf.x
-					ELSE ( osf.x1 - osf.time_left::REAL * ( osf.x1 - osf.x0 )
+					ELSE ( osf.x1 - osf.time_left * ( osf.x1 - osf.x0 )
 						/ fleets.compute_outerspace_duration( fs.flight_time , osf.x0 , osf.y0 , osf.x1 , osf.y1 ) )
-				END )::REAL AS cx ,
+				END )::DOUBLE PRECISION AS cx ,
 				( CASE
 					WHEN osf.id IS NULL THEN isf.y
-					ELSE ( osf.y1 - osf.time_left::REAL * ( osf.y1 - osf.y0 )
+					ELSE ( osf.y1 - osf.time_left * ( osf.y1 - osf.y0 )
 						/ fleets.compute_outerspace_duration( fs.flight_time , osf.x0 , osf.y0 , osf.x1 , osf.y1 ) )
-				END )::REAL AS cy ,
+				END )::DOUBLE PRECISION AS cy ,
 				( CASE
 					WHEN osf.id IS NULL THEN isf.planet
 					ELSE NULL
@@ -1487,7 +1488,7 @@ GRANT SELECT ON fleets.ships_view TO :dbuser;
 CREATE VIEW fleets.short_static_fleets
 	AS SELECT sf.empire , sf.location AS location_id ,
 				fl.name AS location_name ,
-				fl.x::REAL AS x , fl.y::REAL AS y ,
+				fl.x::DOUBLE PRECISION AS x , fl.y::DOUBLE PRECISION AS y ,
 				sf.id , sf.name , sf.status , sf.penalty ,
 				fl.attacking , sf.power , sf.flight_time
 			FROM fleets.static_fleets sf
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql
index ca6ed36..0219833 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/167-planet-list.sql
@@ -15,7 +15,7 @@ CREATE VIEW emp.planets_list_basic
 	AS SELECT e.name_id AS empire ,
 				p.name_id AS id , n.name ,
 				s.x , s.y , p.orbit ,
-				p.population , ph.current / p.population::REAL AS happiness ,
+				p.population , ph.current / p.population AS happiness ,
 				floor( pm.income )::BIGINT AS income ,
 				floor( pm.upkeep )::BIGINT AS upkeep
 			FROM emp.empires e
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql
index 24f5e41..57c4d81 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/170-event-functions.sql
@@ -860,7 +860,7 @@ CREATE VIEW events.empire_events_view
 	AS SELECT e.event_id AS id , e.evt_type , e.evt_subtype , e.tick , e.real_time , s.name AS technology
 			FROM events.events e
 				LEFT OUTER JOIN events.empire_events ed USING (event_id)
-				LEFT OUTER JOIN tech.levels tl ON tl.id = ed.technology_id
+				LEFT OUTER JOIN tech.technologies tl ON tl.name_id = ed.technology_id
 				LEFT OUTER JOIN defs.strings s ON s.id = tl.name_id
 			WHERE e.evt_type = 'EMPIRE';
 
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql
index f8d7282..184f20b 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/functions/200-bugs-functions.sql
@@ -1196,18 +1196,24 @@ GRANT SELECT ON bugs.dump_main_view TO :dbuser;
 
 
 CREATE VIEW bugs.dump_research_view
-	AS SELECT et.empire_id , et.line_id AS line_id , et.level AS level ,
-				tst.name AS name , et.accumulated AS accumulated
-			FROM emp.technologies et
-				LEFT OUTER JOIN tech.levels tlv ON tlv.line_id = et.line_id AND tlv.level = et.level
-				LEFT OUTER JOIN defs.strings tst ON tst.id = tlv.name_id;
+	AS SELECT r.empire_id , ns.name , r.accumulated , r.priority
+		FROM emp.research r
+			INNER JOIN defs.strings ns ON ns.id = r.technology_id;
 
 GRANT SELECT ON bugs.dump_research_view TO :dbuser;
 
 
+CREATE VIEW bugs.dump_technologies_view
+	AS SELECT et.empire_id , ns.name , et.implemented
+		FROM emp.researched_technologies et
+			INNER JOIN defs.strings ns ON ns.id = et.technology_id;
+
+GRANT SELECT ON bugs.dump_technologies_view TO :dbuser;
+
+
 CREATE VIEW bugs.dump_planets_view
 	AS SELECT ep.empire_id , ep.planet_id , p.population ,
-				( ph.current / p.population )::REAL AS current_happiness , ph.target AS target_happiness ,
+				ph.current / p.population AS current_happiness , ph.target AS target_happiness ,
 				cq.money AS civ_money , cq.work AS civ_work ,
 				mq.money AS mil_money , mq.work AS mil_work
 			FROM emp.planets ep
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql
index 9133bf8..4622684 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/000-updates-ctrl.sql
@@ -52,7 +52,7 @@ GRANT EXECUTE ON FUNCTION sys.start_tick( ) TO :dbuser;
 CREATE OR REPLACE FUNCTION sys.end_tick( IN tick_id BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
-		SECURITY INVOKER
+		SECURITY DEFINER
 	AS $$
 BEGIN
 	UPDATE events.events SET status = 'READY'
@@ -62,6 +62,8 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
+GRANT EXECUTE ON FUNCTION sys.end_tick( BIGINT ) TO :dbuser;
+
 
 
 --
@@ -103,70 +105,78 @@ GRANT EXECUTE ON FUNCTION sys.check_stuck_tick( ) TO :dbuser;
 
 
 --
--- Process game updates
+-- Prepare game updates
 --
 -- Parameters:
---	c_tick	Current tick
+--  u_id		Current update identifier
+--	u_type		Type of game updates to prepare
 --
 -- Returns:
---	TRUE if the function must be called again, FALSE otherwise
+--	has_more	TRUE if there are more updates, FALSE otherwise
 --
 
-CREATE OR REPLACE FUNCTION sys.process_updates( IN c_tick BIGINT , OUT has_more BOOLEAN )
+CREATE OR REPLACE FUNCTION sys.prepare_updates( IN u_id BIGINT , IN u_type update_type , OUT has_more BOOLEAN )
 		STRICT VOLATILE
 		SECURITY DEFINER
 	AS $$
-DECLARE
-	b_size	INT;
-	p_utype	update_type;
-	utype	update_type;
-	uid		BIGINT;
 BEGIN
-	b_size := sys.get_constant( 'game.batchSize' );
-	p_utype := NULL;
+	UPDATE sys.updates SET status = 'PROCESSING'
+		WHERE id IN (
+			SELECT id FROM sys.updates
+				WHERE gu_type = u_type
+					AND last_tick = u_id
+					AND status = 'FUTURE'
+				ORDER BY id
+				LIMIT sys.get_constant( 'game.batchSize' )::BIGINT
+			);
 
-	-- Mark at most b_size entries as being updated
-	FOR uid , utype IN SELECT id , gu_type FROM sys.updates
-						WHERE last_tick = c_tick AND status = 'FUTURE'
-						ORDER BY gu_type LIMIT b_size
-	LOOP
-		IF p_utype IS NULL THEN
-			p_utype := utype;
-		END IF;
-		EXIT WHEN utype <> p_utype;
-		UPDATE sys.updates SET status = 'PROCESSING' WHERE id = uid;
-	END LOOP;
-
-	has_more := p_utype IS NOT NULL;
-	IF has_more THEN
-		-- Execute actual updates
-		EXECUTE 'SELECT sys.process_' || lower( p_utype::TEXT ) || '_updates( $1 )'
-			USING c_tick;
-		UPDATE sys.updates SET status = 'PROCESSED'
-			WHERE status = 'PROCESSING' AND last_tick = c_tick;
-	ELSE
-		-- If nothing was found, we're done
-		PERFORM sys.end_tick( c_tick );
-	END IF;
+	has_more := FOUND;
 END;
 $$ LANGUAGE plpgsql;
 
-GRANT EXECUTE ON FUNCTION sys.process_updates( BIGINT ) TO :dbuser;
-
-
-
-
-
-
-
-
-
-
-
+GRANT EXECUTE ON FUNCTION sys.prepare_updates( BIGINT , update_type ) TO :dbuser;
 
 
 
+--
+-- Execute procedural game updates
+--
+-- Parameters:
+--	c_tick	Current tick identifier
+--	u_type	Type of updates to execute
+--
+
+CREATE OR REPLACE FUNCTION sys.exec_update_proc( IN c_tick BIGINT , IN u_type update_type )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $$
+BEGIN
+	EXECUTE 'SELECT sys.process_' || lower( u_type::TEXT ) || '_updates( $1 )'
+			USING c_tick;
+END; 
+$$ LANGUAGE plpgsql;
+
+GRANT EXECUTE ON FUNCTION sys.exec_update_proc( BIGINT , update_type ) TO :dbuser;
 
 
+--
+-- Mark updates as processed
+--
+-- Parameters:
+--	c_tick	Current tick identifier
+--	u_type	Type of updates to execute
+--
 
+CREATE OR REPLACE FUNCTION sys.updates_processed( IN c_tick BIGINT , IN u_type update_type )
+	RETURNS VOID
+	STRICT VOLATILE
+	SECURITY DEFINER
+AS $$
+		UPDATE sys.updates SET status = 'PROCESSED'
+			WHERE status = 'PROCESSING'
+				AND last_tick = $1
+				AND gu_type = $2
+$$ LANGUAGE SQL;
 
+GRANT EXECUTE ON FUNCTION sys.updates_processed( BIGINT , update_type ) TO :dbuser;
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql
index a0bb8b1..9cd715e 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/010-empire-money.sql
@@ -14,8 +14,8 @@ CREATE OR REPLACE FUNCTION sys.process_empire_money_updates( c_tick BIGINT )
 	AS $$
 DECLARE
 	rec		RECORD;
-	c_cash	REAL;
-	c_debt	REAL;
+	c_cash	DOUBLE PRECISION;
+	c_debt	DOUBLE PRECISION;
 BEGIN
 	-- Lock empires for update
 	PERFORM e.name_id FROM sys.updates su
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql
index d9e3cfe..f17ce33 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/020-empire-research.sql
@@ -7,15 +7,22 @@
 -- --------------------------------------------------------
 
 
-CREATE OR REPLACE FUNCTION sys.process_empire_research_updates( c_tick BIGINT )
+
+--
+-- Prepare the research update
+--
+-- Parameters:
+--	update_id	The current update's identifier
+--
+-- Returns:
+--	a set of tech._research_update_input records
+--
+
+CREATE OR REPLACE FUNCTION emp.prepare_research_update( update_id BIGINT )
 		RETURNS VOID
 		STRICT VOLATILE
-		SECURITY INVOKER
+		SECURITY DEFINER
 	AS $$
-DECLARE
-	rec			RECORD;
-	r_points	REAL;
-	tu_rec		RECORD;
 BEGIN
 	-- Lock empires for update and planets for share
 	PERFORM e.name_id FROM sys.updates su
@@ -23,64 +30,137 @@ BEGIN
 			INNER JOIN emp.empires e ON eu.empire_id = e.name_id
 			INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
 			INNER JOIN verse.planets p ON p.name_id = ep.planet_id
-		WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
+		WHERE su.last_tick = update_id AND su.status = 'PROCESSING'
 			AND su.gu_type = 'EMPIRE_RESEARCH'
 		FOR UPDATE OF e
 		FOR SHARE OF ep , p;
 
-	-- Process empires
-	FOR rec IN SELECT e.name_id AS id , ( v.status = 'PROCESSED' ) AS on_vacation ,
-						sum( p.population ) AS population
-					FROM sys.updates su
-						INNER JOIN emp.updates eu ON eu.update_id = su.id
-						INNER JOIN emp.empires e ON eu.empire_id = e.name_id
-						INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
-						INNER JOIN verse.planets p ON p.name_id = ep.planet_id
-						INNER JOIN naming.empire_names en ON en.id = e.name_id
-						LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
-					WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
-						AND su.gu_type = 'EMPIRE_RESEARCH'
-					GROUP BY e.name_id , v.status
-	LOOP
-		-- Insert any missing tech line
-		INSERT INTO emp.technologies ( empire_id , line_id )
-			SELECT rec.id , l.name_id
-				FROM tech.lines l
-					LEFT OUTER JOIN emp.technologies t
-						ON t.line_id = l.name_id AND t.empire_id = rec.id
-				WHERE t.empire_id IS NULL;
-
-		-- Compute research output
-		r_points := rec.population * sys.get_constant( 'game.work.rpPerPopUnit' ) / 1440.0;
-		IF rec.on_vacation
-		THEN
-			r_points := r_points / sys.get_constant( 'vacation.researchDivider' );
-		END IF;
-
-		-- Update technologies where:
-		-- 1) the level actually exists and
-		-- 2) accumulated points haven't reach the level's
-		FOR tu_rec IN SELECT t.line_id AS line_id , t.accumulated AS accumulated ,
-							l.points AS points , ( l.points - t.accumulated ) AS diff ,
-							l.id AS level_id
-						FROM emp.technologies t
-							INNER JOIN tech.levels l ON l.line_id = t.line_id
-								AND l.level = t.level AND t.accumulated < l.points
-						WHERE t.empire_id = rec.id
-						FOR UPDATE OF t
-		LOOP
-			UPDATE emp.technologies t SET accumulated = ( CASE
-						WHEN tu_rec.diff <= r_points THEN tu_rec.points
-						ELSE tu_rec.accumulated + r_points
-					END )
-				WHERE t.line_id = tu_rec.line_id AND t.empire_id = rec.id;
-
-			-- Send message
-			IF tu_rec.diff <= r_points
-			THEN
-				PERFORM events.tech_ready_event( rec.id , tu_rec.level_id );
-			END IF;
-		END LOOP;
-	END LOOP;
+	-- Create temporary table for update output and grant INSERT privilege
+	-- to session user.
+	CREATE TEMPORARY TABLE research_update_output(
+		empire_id		INT ,
+		technology		TEXT ,
+		creation		BOOLEAN ,
+		points			DOUBLE PRECISION ,
+		priority		INT
+	);
+	IF session_user <> current_user THEN
+		EXECUTE 'GRANT INSERT ON research_update_output TO ' || session_user;
+	END IF;
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+GRANT EXECUTE ON FUNCTION emp.prepare_research_update( update_id BIGINT ) TO :dbuser;
+
+
+--
+-- Research update input views
+--
+
+CREATE VIEW emp.rui_inprogress_view
+	AS SELECT su.last_tick AS update_id , er.empire_id , ns.name AS technology ,
+			er.accumulated AS points , er.priority AS priority
+		FROM sys.updates su
+			INNER JOIN emp.updates eu ON eu.update_id = su.id
+			INNER JOIN emp.research er ON er.empire_id = eu.empire_id
+			INNER JOIN defs.strings ns ON ns.id = er.technology_id
+		WHERE su.status = 'PROCESSING'
+			AND su.gu_type = 'EMPIRE_RESEARCH';
+
+
+CREATE VIEW emp.rui_researched_view
+	AS SELECT su.last_tick AS update_id , er.empire_id , ns.name AS technology ,
+			er.implemented AS implemented
+		FROM sys.updates su
+			INNER JOIN emp.updates eu ON eu.update_id = su.id
+			INNER JOIN emp.researched_technologies er
+				ON er.empire_id = eu.empire_id
+			INNER JOIN defs.strings ns ON ns.id = er.technology_id
+		WHERE su.status = 'PROCESSING'
+			AND su.gu_type = 'EMPIRE_RESEARCH';
+
+CREATE VIEW emp.research_update_input_view
+	AS SELECT update_id , empire_id , technology ,
+				NULL::BOOLEAN AS implemented , points , priority
+			FROM emp.rui_inprogress_view
+		UNION ALL SELECT update_id , empire_id , technology ,
+				implemented , NULL::DOUBLE PRECISION AS points ,
+				NULL::INT AS priority
+			FROM emp.rui_researched_view;
+
+GRANT SELECT ON emp.research_update_input_view TO :dbuser;
+
+
+--
+-- Research points production view
+--
+
+CREATE VIEW emp.research_points_production
+	AS SELECT su.last_tick AS update_id , e.name_id AS empire_id ,
+			( sum( p.population ) * sys.get_constant( 'game.research.perPopUnit' )
+				/ ( sys.get_constant( 'game.updatesPerDay' ) * ( CASE
+							WHEN v.status = 'PROCESSED' THEN
+								sys.get_constant( 'game.research.perPopUnit' )
+							ELSE
+								1.0
+					END ) ) ) AS points
+		FROM sys.updates su
+			INNER JOIN emp.updates eu ON eu.update_id = su.id
+			INNER JOIN emp.empires e ON eu.empire_id = e.name_id
+			INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
+			INNER JOIN verse.planets p ON p.name_id = ep.planet_id
+			INNER JOIN naming.empire_names en ON en.id = e.name_id
+			LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
+		WHERE su.status = 'PROCESSING' AND su.gu_type = 'EMPIRE_RESEARCH'
+		GROUP BY su.last_tick , e.name_id , v.status;
+
+GRANT SELECT ON emp.research_points_production TO :dbuser;
+
+
+
+--
+-- Submit the contents of the research update table
+--
+
+CREATE OR REPLACE FUNCTION emp.submit_research_update( )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY DEFINER
+	AS $$
+BEGIN
+	-- Delete finished research topics
+	DELETE FROM emp.research er
+		USING research_update_output ruo , defs.strings ns
+		WHERE er.empire_id = ruo.empire_id
+			AND er.technology_id = ns.id AND ns.name = ruo.technology
+			AND ruo.points IS NULL;
+
+	-- Insert researched technologies
+	INSERT INTO emp.researched_technologies ( empire_id , technology_id , implemented )
+		SELECT ruo.empire_id , ns.id , FALSE
+			FROM research_update_output ruo
+				INNER JOIN defs.strings ns ON ns.name = ruo.technology
+			WHERE ruo.points IS NULL;
+
+	-- Insert new research topics
+	INSERT INTO emp.research ( empire_id , technology_id , accumulated , priority )
+		SELECT ruo.empire_id , ns.id , ruo.points , ruo.priority
+			FROM research_update_output ruo
+				INNER JOIN defs.strings ns ON ns.name = ruo.technology
+			WHERE ruo.points IS NOT NULL AND ruo.creation;
+
+	-- Update existing research topics
+	UPDATE emp.research er
+		SET accumulated = ruo.points , priority = ruo.priority
+		FROM research_update_output ruo , defs.strings ns
+		WHERE ruo.points IS NOT NULL AND NOT ruo.creation
+			AND ns.name = ruo.technology
+			AND er.technology_id = ns.id
+			AND er.empire_id = ruo.empire_id;
+
+	-- Drop temporary table
+	DROP TABLE research_update_output;
+END;
+$$ LANGUAGE plpgsql;
+
+GRANT EXECUTE ON FUNCTION emp.submit_research_update( ) TO :dbuser;
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql
index 7bcaa9f..89a7fae 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/025-empire-debt.sql
@@ -15,11 +15,11 @@ CREATE OR REPLACE FUNCTION sys.process_empire_debt_updates( c_tick BIGINT )
 		SECURITY INVOKER
 	AS $$
 DECLARE
-	fleet_dr	REAL;
-	bld_dr		REAL;
+	fleet_dr	DOUBLE PRECISION;
+	bld_dr		DOUBLE PRECISION;
 	empire		INT;
-	debt		REAL;
-	upkeep		REAL;
+	debt		DOUBLE PRECISION;
+	upkeep		DOUBLE PRECISION;
 BEGIN
 	fleet_dr := sys.get_constant( 'game.debt.fleet');
 	bld_dr := sys.get_constant( 'game.debt.buildings');
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql
index d944f65..c2c8338 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/060-planet-battle.sql
@@ -42,17 +42,17 @@ CREATE OR REPLACE FUNCTION sys.process_planet_battle_main_updates( c_tick BIGINT
 	AS $$
 DECLARE
 	ttfi		INT;
-	initi		REAL;
-	dbonus		REAL;
-	dmg			REAL;
-	rdmg		REAL;
+	initi		DOUBLE PRECISION;
+	dbonus		DOUBLE PRECISION;
+	dmg			DOUBLE PRECISION;
+	rdmg		DOUBLE PRECISION;
 	rec			RECORD;
 	a_power		BIGINT;
 	d_power		BIGINT;
 	p_power		BIGINT;
-	bmod		REAL;
-	a_dmg		REAL;
-	d_dmg		REAL;
+	bmod		DOUBLE PRECISION;
+	a_dmg		DOUBLE PRECISION;
+	d_dmg		DOUBLE PRECISION;
 BEGIN
 	ttfi := floor( sys.get_constant( 'game.battle.timeToFullIntensity' ) )::INT;
 	initi := sys.get_constant( 'game.battle.initialIntensity' );
@@ -61,7 +61,7 @@ BEGIN
 	rdmg := sys.get_constant( 'game.battle.randomDamage' );
 
 	FOR rec IN SELECT b.id AS battle , b.first_tick AS first_tick ,
-					b.location_id AS location , ( ph.current / p.population )::REAL AS happiness
+					b.location_id AS location , ph.current / p.population AS happiness
 				FROM sys.updates su
 					INNER JOIN verse.updates vu ON vu.update_id = su.id
 					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql
index 37470af..d2c7341 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/080-planet-construction.sql
@@ -14,20 +14,20 @@ CREATE OR REPLACE FUNCTION sys.process_planet_construction_updates( c_tick BIGIN
 	AS $$
 DECLARE
 	rec			RECORD;
-	wu_per_pop	REAL;
-	dest_work	REAL;
-	dest_rec	REAL;
+	wu_per_pop	DOUBLE PRECISION;
+	dest_work	DOUBLE PRECISION;
+	dest_rec	DOUBLE PRECISION;
 	cur_empire	INT;
-	cur_cash	REAL;
+	cur_cash	DOUBLE PRECISION;
 	cur_planet	INT;
 	p_finished	BOOLEAN;
-	cur_wus		REAL;
-	cur_acc_c	REAL;
+	cur_wus		DOUBLE PRECISION;
+	cur_acc_c	DOUBLE PRECISION;
 	n_found		INT;
 	n_removed	INT;
-	i_work		REAL;
-	i_cost		REAL;
-	can_do		REAL;
+	i_work		DOUBLE PRECISION;
+	i_cost		DOUBLE PRECISION;
+	can_do		DOUBLE PRECISION;
 	must_do		INT;
 BEGIN
 	-- Get constants
@@ -39,7 +39,7 @@ BEGIN
 	cur_empire := NULL;
 	cur_planet := NULL;
 	FOR rec IN SELECT p.name_id AS id , p.population AS pop ,
-					( ph.current / p.population )::REAL AS happiness ,
+					ph.current / p.population AS happiness ,
 					e.name_id AS owner , e.cash AS cash ,
 					q.money AS acc_cash , q.work AS acc_work ,
 					qi.queue_order AS qorder , qi.amount AS amount ,
@@ -100,10 +100,7 @@ BEGIN
 		IF cur_planet IS NULL THEN
 			cur_planet := rec.id;
 			cur_cash := cur_cash + rec.acc_cash;
-			cur_wus := rec.acc_work + verse.adjust_production(
-				( rec.pop * wu_per_pop )::REAL ,
-				rec.happiness
-			);
+			cur_wus := rec.acc_work + verse.adjust_production( rec.pop * wu_per_pop , rec.happiness );
 			n_found := 1;
 			n_removed := 0;
 			cur_acc_c := 0;
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql
index f3c250a..b1bc4eb 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/090-planet-military.sql
@@ -15,14 +15,14 @@ CREATE OR REPLACE FUNCTION sys.process_planet_military_updates( c_tick BIGINT )
 DECLARE
 	rec			RECORD;
 	cur_empire	INT;
-	cur_cash	REAL;
+	cur_cash	DOUBLE PRECISION;
 	cur_planet	INT;
 	p_finished	BOOLEAN;
-	cur_wus		REAL;
-	cur_acc_c	REAL;
+	cur_wus		DOUBLE PRECISION;
+	cur_acc_c	DOUBLE PRECISION;
 	n_found		INT;
 	n_removed	INT;
-	can_do		REAL;
+	can_do		DOUBLE PRECISION;
 	must_do		INT;
 	fl_id		BIGINT;
 BEGIN
@@ -38,7 +38,7 @@ BEGIN
 	cur_empire := NULL;
 	cur_planet := NULL;
 	FOR rec IN SELECT p.name_id AS id ,
-					( ph.current / p.population )::REAL AS happiness ,
+					ph.current / p.population AS happiness ,
 					e.name_id AS owner , e.cash AS cash ,
 					q.money AS acc_cash , q.work AS acc_work ,
 					qi.queue_order AS qorder , qi.amount AS amount ,
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql
index 2a805f8..52d2d91 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/100-planet-population.sql
@@ -14,16 +14,16 @@ CREATE OR REPLACE FUNCTION sys.process_planet_population_updates( c_tick BIGINT
 	AS $$
 DECLARE
 	rec		RECORD;
-	rel_ch	REAL;
-	abs_ch	REAL;
-	g_fact	REAL;
-	gf_inc	REAL;
-	n_happ	REAL;
-	t_happ	REAL;
-	temp	REAL;
-	growth	REAL;
-	workers	REAL;
-	str_thr	REAL;
+	rel_ch	DOUBLE PRECISION;
+	abs_ch	DOUBLE PRECISION;
+	g_fact	DOUBLE PRECISION;
+	gf_inc	DOUBLE PRECISION;
+	n_happ	DOUBLE PRECISION;
+	t_happ	DOUBLE PRECISION;
+	temp	DOUBLE PRECISION;
+	growth	DOUBLE PRECISION;
+	workers	DOUBLE PRECISION;
+	str_thr	DOUBLE PRECISION;
 BEGIN
 	-- Get constants
 	rel_ch := sys.get_constant( 'game.happiness.relativeChange' );
@@ -35,7 +35,7 @@ BEGIN
 	-- Process planets
 	FOR rec IN SELECT p.name_id AS id , p.population AS pop ,
 					ph.target AS target , ph.current AS happy_pop ,
-					( ph.current / p.population )::REAL AS current
+					ph.current / p.population AS current
 				FROM sys.updates su
 					INNER JOIN verse.updates vu ON vu.update_id = su.id
 					INNER JOIN verse.planets p ON vu.planet_id = p.name_id
diff --git a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql
index cbcc432..c22c86a 100644
--- a/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql
+++ b/legacyworlds-server/legacyworlds-server-data/db-structure/parts/updates/110-planet-money.sql
@@ -14,10 +14,10 @@ CREATE OR REPLACE FUNCTION sys.process_planet_money_updates( c_tick BIGINT )
 	AS $$
 DECLARE
 	rec		RECORD;
-	incme	REAL;
+	incme	DOUBLE PRECISION;
 BEGIN
 	FOR rec IN SELECT p.name_id AS id , p.population AS pop ,
-					( ph.current / p.population )::REAL AS happiness ,
+					ph.current / p.population AS happiness ,
 					( ea.planet_id IS NULL ) AS produces_income
 				FROM sys.updates su
 					INNER JOIN verse.updates vu ON vu.update_id = su.id
diff --git a/legacyworlds-server/legacyworlds-server-data/pom.xml b/legacyworlds-server/legacyworlds-server-data/pom.xml
index a84f721..be98172 100644
--- a/legacyworlds-server/legacyworlds-server-data/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-data/pom.xml
@@ -4,13 +4,13 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-data</artifactId>
 	<name>Legacy Worlds server data</name>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<description>This package contains all data access classes for the Legacy Worlds server.</description>
 
 	<dependencies>
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
deleted file mode 100644
index 7ab9d68..0000000
--- a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechLine.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.deepclone.lw.sqld.game;
-
-
-import java.util.LinkedList;
-import java.util.List;
-
-
-
-public class EmpireTechLine
-{
-	private int id;
-
-	private String name;
-
-	private String description;
-
-	private List< EmpireTechnology > technologies = new LinkedList< EmpireTechnology >( );
-
-
-	public int getId( )
-	{
-		return id;
-	}
-
-
-	public void setId( int id )
-	{
-		this.id = id;
-	}
-
-
-	public String getName( )
-	{
-		return name;
-	}
-
-
-	public void setName( String name )
-	{
-		this.name = name;
-	}
-
-
-	public String getDescription( )
-	{
-		return description;
-	}
-
-
-	public void setDescription( String description )
-	{
-		this.description = description;
-	}
-
-
-	public List< EmpireTechnology > getTechnologies( )
-	{
-		return technologies;
-	}
-
-
-	public void addTechnology( EmpireTechnology eTech )
-	{
-		this.technologies.add( eTech );
-	}
-}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
deleted file mode 100644
index b36d4dd..0000000
--- a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/EmpireTechnology.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.deepclone.lw.sqld.game;
-
-
-public class EmpireTechnology
-{
-	private int line;
-
-	private String name;
-
-	private String description;
-
-	private boolean implemented;
-
-	private int progress;
-
-	private int cost;
-
-
-	public int getLine( )
-	{
-		return line;
-	}
-
-
-	public void setLine( int line )
-	{
-		this.line = line;
-	}
-
-
-	public String getName( )
-	{
-		return name;
-	}
-
-
-	public void setName( String name )
-	{
-		this.name = name;
-	}
-
-
-	public String getDescription( )
-	{
-		return description;
-	}
-
-
-	public void setDescription( String description )
-	{
-		this.description = description;
-	}
-
-
-	public boolean isImplemented( )
-	{
-		return implemented;
-	}
-
-
-	public void setImplemented( boolean implemented )
-	{
-		this.implemented = implemented;
-	}
-
-
-	public int getProgress( )
-	{
-		return progress;
-	}
-
-
-	public void setProgress( int progress )
-	{
-		this.progress = progress;
-	}
-
-
-	public int getCost( )
-	{
-		return cost;
-	}
-
-
-	public void setCost( int cost )
-	{
-		this.cost = cost;
-	}
-
-}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
index 82b7270..1b727f5 100644
--- a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/GeneralInformation.java
@@ -4,24 +4,28 @@ package com.deepclone.lw.sqld.game;
 public class GeneralInformation
 {
 
-	private Character status;
+	private final Character status;
 
-	private String name;
+	private final String name;
 
-	private String tag;
+	private final String tag;
 
-	private long cash;
+	private final String language;
 
-	private long nextTick;
+	private final long cash;
 
-	private int accountId;
+	private final long nextTick;
+
+	private final int accountId;
 
 
-	public GeneralInformation( Character status , String name , String tag , long cash , long nextTick , int accountId )
+	public GeneralInformation( Character status , String name , String tag , String language , long cash ,
+			long nextTick , int accountId )
 	{
 		this.status = status;
 		this.name = name;
 		this.tag = tag;
+		this.language = language;
 		this.cash = cash;
 		this.nextTick = nextTick;
 		this.accountId = accountId;
@@ -46,6 +50,12 @@ public class GeneralInformation
 	}
 
 
+	public String getLanguage( )
+	{
+		return language;
+	}
+
+
 	public long getCash( )
 	{
 		return cash;
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/Category.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/Category.java
new file mode 100644
index 0000000..61b400c
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/Category.java
@@ -0,0 +1,169 @@
+package com.deepclone.lw.sqld.game.techs;
+
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+
+
+/**
+ * A technology category for the technology graph.
+ * 
+ * @author tseeker
+ */
+public class Category
+{
+
+	/** The string identifier of the category's name */
+	private final String name;
+
+	/** The string identifier of the category's description */
+	private String description;
+
+	/**
+	 * Map of the technologies belonging to the category, with their name string identifier as the
+	 * index.
+	 */
+	private Map< String , Technology > technologies;
+
+
+	/**
+	 * Initialise a category's representation.
+	 * 
+	 * @param name
+	 *            string identifier of the category's name
+	 * @param description
+	 *            string identifier of the category's description
+	 */
+	Category( String name , String description )
+	{
+		this.name = name;
+		this.description = description;
+		this.technologies = new HashMap< String , Technology >( );
+	}
+
+
+	/**
+	 * Copy a category.
+	 * 
+	 * @param cat
+	 *            the category being copied.
+	 */
+	Category( Category cat )
+	{
+		this.name = cat.name;
+		this.description = cat.description;
+		this.technologies = new HashMap< String , Technology >( );
+		for ( Technology tech : cat.technologies.values( ) ) {
+			this.technologies.put( tech.getName( ) , new Technology( tech ) );
+		}
+	}
+
+
+	/** @return the string identifier of the category's name */
+	public String getName( )
+	{
+		return name;
+	}
+
+
+	/** @return the string identifier of the category's description */
+	public String getDescription( )
+	{
+		return description;
+	}
+
+
+	/**
+	 * Update the category's description.
+	 * 
+	 * @param description
+	 *            the string identifier of the category's description
+	 */
+	void setDescription( String description )
+	{
+		this.description = description;
+	}
+
+
+	/**
+	 * List technologies in the category.
+	 * 
+	 * @return an alphabetically ordered list of technology names
+	 */
+	public List< String > getTechnologies( )
+	{
+		List< String > names = new LinkedList< String >( this.technologies.keySet( ) );
+		Collections.sort( names );
+		return names;
+	}
+
+
+	/**
+	 * Find technology objects stored in the category.
+	 * 
+	 * @return the list of technology objects.
+	 */
+	Collection< Technology > getTechnologyObjects( )
+	{
+		return this.technologies.values( );
+	}
+
+
+	/**
+	 * Add a new technology to the category's record.
+	 * 
+	 * @param name
+	 *            string identifier of the technology's name
+	 * @param description
+	 *            string identifier of the technology's description
+	 * @param points
+	 *            research points required to implement the technology
+	 * @param cost
+	 *            cost of implementing the technology
+	 * @return the newly created {@link Technology} instance
+	 */
+	Technology addTechnology( String name , String description , int points , int cost )
+	{
+		Technology tech = new Technology( this , name , description , points , cost );
+		this.technologies.put( name , tech );
+		return tech;
+	}
+
+
+	/**
+	 * Move a technology from another category into the current category.
+	 * 
+	 * @param tech
+	 *            the technology to move
+	 */
+	void addExistingTechnology( Technology tech )
+	{
+		Category previous = tech.getCategory( );
+		if ( previous.name.equals( this.name ) ) {
+			return;
+		}
+
+		previous.technologies.remove( tech.getName( ) );
+		this.technologies.put( tech.getName( ) , tech );
+		tech.setCategory( this );
+	}
+
+
+	/**
+	 * Remove a technology from the category. This method is meant to be called from
+	 * {@link Technology#delete()}.
+	 * 
+	 * @param technology
+	 *            the technology to remove.
+	 */
+	void removeTechnology( Technology technology )
+	{
+		this.technologies.remove( technology );
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/EmpireTechnology.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/EmpireTechnology.java
new file mode 100644
index 0000000..b5dee91
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/EmpireTechnology.java
@@ -0,0 +1,175 @@
+package com.deepclone.lw.sqld.game.techs;
+
+
+/**
+ * Empire technology and research record.
+ * 
+ * @author tseeker
+ */
+public class EmpireTechnology
+{
+
+	/** Numeric identifier used for unknown technologies */
+	private final Integer numericId;
+
+	/** Technology string name */
+	private final String identifier;
+
+	/** Status of the technology */
+	private final ResearchStatus status;
+
+	/** Whether additional details should be provided */
+	private final boolean detailed;
+
+	/** Research completion percentage */
+	private final Integer percentage;
+
+	/** Research priority */
+	private final Integer priority;
+
+	/** Implementation cost */
+	private final Integer cost;
+
+
+	/**
+	 * Initialise the record for an implemented technology.
+	 * 
+	 * @param identifier
+	 *            string identifier of the technology's name.
+	 */
+	public EmpireTechnology( String identifier )
+	{
+		this.numericId = null;
+		this.identifier = identifier;
+		this.status = ResearchStatus.IMPLEMENTED;
+		this.detailed = true;
+		this.percentage = null;
+		this.priority = null;
+		this.cost = null;
+	}
+
+
+	/**
+	 * Initialise the record for an unidentified technology being researched.
+	 * 
+	 * @param numericId
+	 *            numeric identifier of the technology
+	 * @param identifier
+	 *            string identifier of the technology's name
+	 * @param percentage
+	 *            research completion percentage
+	 * @param priority
+	 *            research priority
+	 */
+	public EmpireTechnology( int numericId , String identifier , int percentage , int priority )
+	{
+		this.numericId = numericId;
+		this.identifier = identifier;
+		this.status = ResearchStatus.IN_PROGRESS;
+		this.detailed = false;
+		this.percentage = percentage;
+		this.priority = priority;
+		this.cost = null;
+	}
+
+
+	/**
+	 * Initialise the record for an identified technology being researched.
+	 * 
+	 * @param identifier
+	 *            string identifier of the technology's name
+	 * @param percentage
+	 *            research completion percentage
+	 * @param priority
+	 *            research priority
+	 */
+	public EmpireTechnology( String identifier , int percentage , int priority )
+	{
+		this.numericId = null;
+		this.identifier = identifier;
+		this.status = ResearchStatus.IN_PROGRESS;
+		this.detailed = true;
+		this.percentage = percentage;
+		this.priority = priority;
+		this.cost = null;
+	}
+
+
+	/**
+	 * Initialise the record for a researched technology.
+	 * 
+	 * @param identifier
+	 *            string identifier of the technology's name
+	 * @param cost
+	 *            implementation cost
+	 */
+	public EmpireTechnology( String identifier , int cost )
+	{
+		this.numericId = null;
+		this.identifier = identifier;
+		this.status = ResearchStatus.RESEARCHED;
+		this.detailed = true;
+		this.percentage = null;
+		this.priority = null;
+		this.cost = cost;
+	}
+
+
+	/** @return the numeric identifier used for unknown technologies */
+	public Integer getNumericId( )
+	{
+		return this.numericId;
+	}
+
+
+	/** @return the string identifier of the technology's name */
+	public String getIdentifier( )
+	{
+		return this.identifier;
+	}
+
+
+	/** @return the technology's research status */
+	public ResearchStatus getStatus( )
+	{
+		return this.status;
+	}
+
+
+	/** @return whether details are to be displayed */
+	public boolean isDetailed( )
+	{
+		return this.detailed;
+	}
+
+
+	/**
+	 * @return the technology's research completion percentage or <code>null</code> if the
+	 *         technology is not under research.
+	 */
+	public Integer getPercentage( )
+	{
+		return this.percentage;
+	}
+
+
+	/**
+	 * @return the technology's research priority or <code>null</code> if the technology is not under
+	 *         research
+	 */
+	public Integer getPriority( )
+	{
+		return priority;
+	}
+
+
+	/**
+	 * @return the technology's implementation cost, or <code>null</code> if the technology is
+	 *         either implemented or being researched.
+	 */
+	public Integer getCost( )
+	{
+		return this.cost;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchStatus.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchStatus.java
new file mode 100644
index 0000000..db2b066
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchStatus.java
@@ -0,0 +1,20 @@
+package com.deepclone.lw.sqld.game.techs;
+
+
+/**
+ * Research status for empire technologies.
+ * 
+ * @author tseeker
+ */
+public enum ResearchStatus {
+
+	/** Research in progress */
+	IN_PROGRESS ,
+
+	/** Technology researched but unimplemented */
+	RESEARCHED ,
+
+	/** Technology researched and implemented */
+	IMPLEMENTED
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchUpdateInput.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchUpdateInput.java
new file mode 100644
index 0000000..fe50a45
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchUpdateInput.java
@@ -0,0 +1,90 @@
+package com.deepclone.lw.sqld.game.techs;
+
+
+/**
+ * Record used as the input of the research update handler.
+ * 
+ * @author tseeker
+ */
+public class ResearchUpdateInput
+{
+
+	private final int empireId;
+	private final String technology;
+	private final ResearchStatus status;
+	private final Double points;
+	private final Integer priority;
+
+
+	/**
+	 * Initialise the record for a researched technology
+	 * 
+	 * @param empireId
+	 *            empire identifier
+	 * @param technology
+	 *            technology name
+	 * @param implemented
+	 *            whether the technology has been implemented or not
+	 */
+	public ResearchUpdateInput( int empireId , String technology , boolean implemented )
+	{
+		this.empireId = empireId;
+		this.technology = technology;
+		this.status = implemented ? ResearchStatus.IMPLEMENTED : ResearchStatus.RESEARCHED;
+		this.points = null;
+		this.priority = null;
+	}
+
+
+	/**
+	 * Initialise the record for an ongoing research process.
+	 * 
+	 * @param empireId
+	 *            empire identifier
+	 * @param technology
+	 *            technology name
+	 * @param points
+	 *            accumulated research points
+	 * @param priority
+	 *            current priority
+	 */
+	public ResearchUpdateInput( int empireId , String technology , double points , int priority )
+	{
+		this.empireId = empireId;
+		this.technology = technology;
+		this.status = ResearchStatus.IN_PROGRESS;
+		this.points = points;
+		this.priority = priority;
+	}
+
+
+	public int getEmpireId( )
+	{
+		return this.empireId;
+	}
+
+
+	public String getTechnology( )
+	{
+		return this.technology;
+	}
+
+
+	public ResearchStatus getStatus( )
+	{
+		return this.status;
+	}
+
+
+	public Double getPoints( )
+	{
+		return this.points;
+	}
+
+
+	public Integer getPriority( )
+	{
+		return this.priority;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchUpdateOutput.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchUpdateOutput.java
new file mode 100644
index 0000000..6e338d2
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/ResearchUpdateOutput.java
@@ -0,0 +1,134 @@
+package com.deepclone.lw.sqld.game.techs;
+
+
+/**
+ * Record that describes the output of the research update.
+ * 
+ * @author tseeker
+ */
+public class ResearchUpdateOutput
+{
+
+	private final int empireId;
+	private final String technology;
+	private final Boolean creation;
+	private Double points;
+	private Integer priority;
+
+
+	/**
+	 * Create a research update output record which indicates that a technology has been fully
+	 * researched.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param technology
+	 *            the name of the technology
+	 */
+	public ResearchUpdateOutput( int empireId , String technology )
+	{
+		this.empireId = empireId;
+		this.technology = technology;
+		this.creation = null;
+		this.points = null;
+		this.priority = null;
+	}
+
+
+	/**
+	 * Create a research update output record which indicates that a research progress row should be
+	 * either created or updated.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param technology
+	 *            the name of the technology
+	 * @param creation
+	 *            whether the research process for this technology is a new one
+	 * @param points
+	 *            amount of research points accumulated in the record
+	 * @param priority
+	 *            priority of the research process
+	 */
+	public ResearchUpdateOutput( int empireId , String technology , boolean creation , double points , int priority )
+	{
+		this.empireId = empireId;
+		this.technology = technology;
+		this.creation = creation;
+		this.points = points;
+		this.priority = priority;
+	}
+
+
+	/** @return the empire's identifier */
+	public int getEmpireId( )
+	{
+		return empireId;
+	}
+
+
+	/** @return the name of the technology */
+	public String getTechnology( )
+	{
+		return technology;
+	}
+
+
+	/**
+	 * @return whether the research process for this technology is a new one, or <code>null</code>
+	 *         if the record is about a fully researched technology
+	 */
+	public Boolean getCreation( )
+	{
+		return creation;
+	}
+
+
+	/**
+	 * @return the amount of accumulated research points for the research process or
+	 *         <code>null</code> if the record is about a fully researched technology
+	 */
+	public Double getPoints( )
+	{
+		return points;
+	}
+
+
+	/**
+	 * Update the accumulated research points
+	 * 
+	 * @param points
+	 *            the amount of points to add
+	 */
+	public void addPoints( Double points )
+	{
+		this.points = this.points + points;
+	}
+
+
+	/**
+	 * @return the research topic's priority or <code>null</code> if the record is about a fully
+	 *         researched technology
+	 */
+	public Integer getPriority( )
+	{
+		return priority;
+	}
+
+
+	/**
+	 * Set the priority of the research
+	 * 
+	 * @param priority
+	 *            the new priority
+	 */
+	public void setPriority( int priority )
+	{
+		if ( this.priority == null ) {
+			throw new IllegalArgumentException( "trying to set priority on known technology " + this.technology
+					+ " for empire #" + this.empireId );
+		}
+		this.priority = priority;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/TechGraph.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/TechGraph.java
new file mode 100644
index 0000000..0d398a1
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/TechGraph.java
@@ -0,0 +1,366 @@
+package com.deepclone.lw.sqld.game.techs;
+
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.deepclone.lw.sqld.game.techs.TechGraphException.Code;
+
+
+
+/**
+ * Representation of the whole technology graph.
+ */
+public class TechGraph
+{
+
+	/** List of all technologies */
+	private List< Technology > technologies;
+
+	/** Technologies indexed by name */
+	private Map< String , Technology > techsByName;
+
+	/** Technology categories indexed by name */
+	private Map< String , Category > categoriesByName;
+
+
+	/** Initialise an empty tech graph */
+	public TechGraph( )
+	{
+		this.technologies = new LinkedList< Technology >( );
+		this.techsByName = new HashMap< String , Technology >( );
+		this.categoriesByName = new HashMap< String , Category >( );
+	}
+
+
+	/**
+	 * Create a copy of an existing tech graph
+	 * 
+	 * @param graph
+	 *            the original tech graph
+	 */
+	public TechGraph( TechGraph graph )
+	{
+		this.technologies = new LinkedList< Technology >( );
+		this.techsByName = new HashMap< String , Technology >( );
+		this.categoriesByName = new HashMap< String , Category >( );
+
+		// Copy category and technology fields
+		for ( Category cat : graph.categoriesByName.values( ) ) {
+			Category copyCat = new Category( cat );
+			this.categoriesByName.put( cat.getName( ) , copyCat );
+
+			for ( Technology tech : copyCat.getTechnologyObjects( ) ) {
+				this.technologies.add( tech );
+				this.techsByName.put( tech.getName( ) , tech );
+			}
+		}
+
+		// Copy dependencies
+		for ( Technology tech : this.technologies ) {
+			for ( String depName : graph.techsByName.get( tech.getName( ) ).getDependencies( ) ) {
+				tech.addDependency( this.techsByName.get( depName ) );
+			}
+		}
+	}
+
+
+	/**
+	 * Register a new technology category.
+	 * 
+	 * @param name
+	 *            the name of the new category
+	 * @param description
+	 *            the description of the new category
+	 * 
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#DUPLICATE_CATEGORY} if the category already
+	 *             exists.
+	 */
+	public void addCategory( String name , String description )
+			throws TechGraphException
+	{
+		if ( this.categoriesByName.containsKey( name ) ) {
+			throw new TechGraphException( Code.DUPLICATE_CATEGORY );
+		}
+		this.categoriesByName.put( name , new Category( name , description ) );
+	}
+
+
+	/**
+	 * List all categories in alphabetic order
+	 * 
+	 * @return the list of category names
+	 */
+	public List< String > getCategories( )
+	{
+		List< String > result = new LinkedList< String >( this.categoriesByName.keySet( ) );
+		Collections.sort( result );
+		return result;
+	}
+
+
+	/**
+	 * Access a registered category.
+	 * 
+	 * @param name
+	 *            the name of the category
+	 * 
+	 * @return the category's record
+	 * 
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_CATEGORY} if the category does not
+	 *             exist.
+	 */
+	public Category getCategory( String name )
+			throws TechGraphException
+	{
+		Category cat = this.categoriesByName.get( name );
+		if ( cat == null ) {
+			throw new TechGraphException( Code.MISSING_CATEGORY , name );
+		}
+		return cat;
+	}
+
+
+	/**
+	 * Set a category's description.
+	 * 
+	 * @param name
+	 *            the name of the category
+	 * 
+	 * @param description
+	 *            the string identifier of the new description
+	 * 
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_CATEGORY} if the category does not
+	 *             exist.
+	 */
+	public void updateCategory( String name , String description )
+			throws TechGraphException
+	{
+		this.getCategory( name ).setDescription( description );
+	}
+
+
+	/**
+	 * Delete a category from the graph's information.
+	 * 
+	 * @param name
+	 *            the name of the category
+	 * 
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_CATEGORY} if the category does not
+	 *             exist, or with {@link TechGraphException.Code#DEPENDENCY_ERROR} if the category
+	 *             contains technologies.
+	 */
+	public void deleteCategory( String name )
+			throws TechGraphException
+	{
+		Category cat = this.getCategory( name );
+		if ( !cat.getTechnologies( ).isEmpty( ) ) {
+			throw new TechGraphException( Code.DEPENDENCY_ERROR );
+		}
+		this.categoriesByName.remove( name );
+	}
+
+
+	/**
+	 * Register a technology into the graph.
+	 * 
+	 * @param category
+	 *            the name of the category the new technology will be added to
+	 * @param name
+	 *            the string identifier of name of the new technology
+	 * @param description
+	 *            the string identifier of description of the new technology
+	 * @param points
+	 *            the amount of research points required to research the technology
+	 * @param cost
+	 *            the monetary cost of implementing the technology
+	 * 
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_CATEGORY} if the category does not
+	 *             exist, or with {@link TechGraphException.Code#DUPLICATE_TECHNOLOGY} if the
+	 *             technology already exists.
+	 */
+	public void addTechnology( String category , String name , String description , int points , int cost )
+			throws TechGraphException
+	{
+		if ( this.techsByName.containsKey( name ) ) {
+			throw new TechGraphException( Code.DUPLICATE_TECHNOLOGY , name );
+		}
+
+		Technology tech = this.getCategory( category ).addTechnology( name , description , points , cost );
+		this.technologies.add( tech );
+		this.techsByName.put( name , tech );
+	}
+
+
+	/**
+	 * Access a technology's record.
+	 * 
+	 * @param technology
+	 *            the name of the technology
+	 * 
+	 * @return the {@link Technology} instance describing the technology
+	 * 
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_TECHNOLOGY} if the technology does
+	 *             not exist
+	 */
+	public Technology getTechnology( String technology )
+			throws TechGraphException
+	{
+		Technology tech = this.techsByName.get( technology );
+		if ( tech == null ) {
+			throw new TechGraphException( Code.MISSING_TECHNOLOGY , technology );
+		}
+		return tech;
+	}
+
+
+	/**
+	 * Update a technology's record.
+	 * 
+	 * @param technology
+	 *            the technology's name
+	 * @param description
+	 *            the string identifier of the new description, or <code>null</code> if the
+	 *            description must be kept.
+	 * @param points
+	 *            the new amount of research points for the technology, or <code>null</code> if the
+	 *            research points are not to be modified.
+	 * @param cost
+	 *            the new implementation cost of the technology, or <code>null</code> if the cost
+	 *            does not change.
+	 * 
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_TECHNOLOGY} if the technology does
+	 *             not exist
+	 */
+	public void updateTechnology( String technology , String description , Integer points , Integer cost )
+			throws TechGraphException
+	{
+		Technology tech = this.getTechnology( technology );
+		if ( description != null ) {
+			tech.setDescription( description );
+		}
+		if ( points != null ) {
+			tech.setPoints( points );
+		}
+		if ( cost != null ) {
+			tech.setCost( cost );
+		}
+	}
+
+
+	/**
+	 * Modify the category of a technology.
+	 * 
+	 * @param technology
+	 *            the name of the technology
+	 * @param category
+	 *            the name of the category the technology should be moved into
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_TECHNOLOGY} if the technology does
+	 *             not exist or with {@link TechGraphException.Code#MISSING_CATEGORY} if the
+	 *             category does not exist
+	 */
+	public void setCategory( String technology , String category )
+			throws TechGraphException
+	{
+		this.getCategory( category ).addExistingTechnology( this.getTechnology( technology ) );
+	}
+
+
+	/**
+	 * Add a dependency to a technology.
+	 * 
+	 * @param technology
+	 *            the name of the technology to add a dependency to
+	 * @param dependsOn
+	 *            the name of the new dependency
+	 * 
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_TECHNOLOGY} if the dependent
+	 *             technology does not exist, with
+	 *             {@link TechGraphException.Code#MISSING_DEPENDENCY} if the dependency does not
+	 *             exist, with {@link TechGraphException.Code#RECURSIVE_DEPENDENCY} if adding the
+	 *             dependency would cause a recursion in the graph, or with
+	 *             {@link TechGraphException.Code#DUPLICATE_DEPENDENCY} if the technology already
+	 *             depends on the specified technology (either directly or indirectly)
+	 */
+	public void addDependency( String technology , String dependsOn )
+			throws TechGraphException
+	{
+		Technology tech = this.getTechnology( technology );
+		Technology dep;
+		try {
+			dep = this.getTechnology( dependsOn );
+		} catch ( TechGraphException e ) {
+			throw new TechGraphException( Code.MISSING_DEPENDENCY );
+		}
+
+		if ( technology.equals( dependsOn ) || dep.hasDependency( technology ) ) {
+			throw new TechGraphException( Code.RECURSIVE_DEPENDENCY );
+		}
+		if ( tech.hasDependency( dependsOn ) ) {
+			throw new TechGraphException( Code.DUPLICATE_DEPENDENCY );
+		}
+
+		tech.addDependency( dep );
+	}
+
+
+	/**
+	 * Remove a dependency from a technology's definition.
+	 * 
+	 * @param technology
+	 *            the name of the dependent technology
+	 * @param dependency
+	 *            the name of the dependency to remove
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_TECHNOLOGY} if the dependent
+	 *             technology does not exist or with
+	 *             {@link TechGraphException.Code#MISSING_DEPENDENCY} if the technology does not
+	 *             have the specified dependency.
+	 */
+	public void removeDependency( String technology , String dependency )
+			throws TechGraphException
+	{
+		Technology tech = this.getTechnology( technology );
+		if ( !tech.hasDirectDependency( dependency ) ) {
+			throw new TechGraphException( Code.MISSING_DEPENDENCY );
+		}
+		tech.removeDependency( dependency );
+	}
+
+
+	/**
+	 * Delete a technology's definition.
+	 * 
+	 * @param technology
+	 *            the name of the technology to delete.
+	 * @throws TechGraphException
+	 *             with {@link TechGraphException.Code#MISSING_TECHNOLOGY} if the technology does
+	 *             not exist or with {@link TechGraphException.Code#DEPENDENCY_ERROR} if other
+	 *             technologies depend on the technology to delete.
+	 */
+	public void deleteTechnology( String technology )
+			throws TechGraphException
+	{
+		Technology tech = this.getTechnology( technology );
+		if ( tech.hasReverseDependencies( ) ) {
+			throw new TechGraphException( Code.DEPENDENCY_ERROR );
+		}
+
+		tech.delete( );
+		this.techsByName.remove( technology );
+		this.technologies.remove( tech );
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/TechGraphException.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/TechGraphException.java
new file mode 100644
index 0000000..12cf534
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/TechGraphException.java
@@ -0,0 +1,69 @@
+package com.deepclone.lw.sqld.game.techs;
+
+
+/**
+ * Exceptions thrown by the {@link TechGraph} class when operations on the graph would result in an
+ * incorrect graph.
+ * 
+ * @author tseeker
+ * 
+ */
+@SuppressWarnings( "serial" )
+public class TechGraphException
+		extends Exception
+{
+
+	/** Error codes associated with tech graph exceptions */
+	public static enum Code {
+		/** Trying to add a duplicate category */
+		DUPLICATE_CATEGORY ,
+
+		/** Trying to access a category that does not exist */
+		MISSING_CATEGORY ,
+
+		/** Trying to delete a category or technology on which other technologies depend */
+		DEPENDENCY_ERROR ,
+
+		/** Trying to add a duplicate technology */
+		DUPLICATE_TECHNOLOGY ,
+
+		/** Trying to access a technology that does not exist */
+		MISSING_TECHNOLOGY ,
+
+		/** Trying to access a dependency that does not exist */
+		MISSING_DEPENDENCY ,
+
+		/** Trying to add a recursive dependency */
+		RECURSIVE_DEPENDENCY ,
+
+		/**
+		 * Trying to add a dependency that is already present (whether directly or through other
+		 * dependencies)
+		 */
+		DUPLICATE_DEPENDENCY ,
+	}
+
+	/** Error code for the exception */
+	private final Code code;
+
+
+	public TechGraphException( Code code )
+	{
+		super( code.toString( ) );
+		this.code = code;
+	}
+
+
+	public TechGraphException( Code code , String identifier )
+	{
+		super( code.toString( ) + " : " + identifier );
+		this.code = code;
+	}
+
+
+	public Code getCode( )
+	{
+		return code;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/Technology.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/Technology.java
new file mode 100644
index 0000000..eda341b
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/game/techs/Technology.java
@@ -0,0 +1,285 @@
+package com.deepclone.lw.sqld.game.techs;
+
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+
+/**
+ * Representation of a technology from the technology graph.
+ * 
+ * @author tseeker
+ */
+public class Technology
+{
+
+	/** The string identifier of the technology's name */
+	private final String name;
+
+	/** The category the technology belongs to */
+	private Category category;
+
+	/** The string identifier of the technology's description */
+	private String description;
+
+	/** The amount of research points required before the technology can be implemented */
+	private double points;
+
+	/** The cost of implementing the technology */
+	private double cost;
+
+	/** Map of technologies the current technology depends on */
+	private final Map< String , Technology > dependencies;
+
+	/** Map of technologies that depend on the current technology */
+	private final Map< String , Technology > dependencyOf;
+
+	/** Names of all technologies the current technology depends on, whether directly or indirectly. */
+	private Set< String > fullDependencies;
+
+
+	/**
+	 * Initialise the technology's record. This method is meant to be called from
+	 * {@link Category#addTechnology(String, String, int, int)}.
+	 * 
+	 * @param category
+	 *            the category the technology belongs to
+	 * @param name
+	 *            string identifier of the technology's name
+	 * @param description
+	 *            string identifier of the technology's description
+	 * @param points
+	 *            amount of research points required before the technology can be implemented
+	 * @param cost
+	 *            cost of implementing the technology
+	 */
+	Technology( Category category , String name , String description , double points , double cost )
+	{
+		this.category = category;
+		this.name = name;
+		this.description = description;
+		this.points = points;
+		this.cost = cost;
+		this.dependencies = new HashMap< String , Technology >( );
+		this.dependencyOf = new HashMap< String , Technology >( );
+	}
+
+
+	/**
+	 * Create a partial copy of an existing technology. Dependencies will not be copied as they need
+	 * to be resolved after a full graph copy has been performed.
+	 * 
+	 * @param tech
+	 *            the technology to copy
+	 */
+	Technology( Technology tech )
+	{
+		this.category = tech.category;
+		this.name = tech.name;
+		this.description = tech.description;
+		this.points = tech.points;
+		this.cost = tech.cost;
+		this.dependencies = new HashMap< String , Technology >( );
+		this.dependencyOf = new HashMap< String , Technology >( );
+	}
+
+
+	/**
+	 * Check if the technology depends on another, whether directly or indirectly.
+	 * 
+	 * @param technology
+	 *            the name of the dependency to look for
+	 * @return <code>true</code> if the current technology depends on the specified technology,
+	 *         <code>false</code> otherwise
+	 */
+	public boolean hasDependency( String technology )
+	{
+		if ( this.fullDependencies == null ) {
+			this.buildDependencies( );
+		}
+		return this.fullDependencies.contains( technology );
+	}
+
+
+	/**
+	 * Build the contents of the {@link #fullDependencies} property based on the list of direct
+	 * dependencies.
+	 */
+	private void buildDependencies( )
+	{
+		Set< String > deps = new HashSet< String >( );
+		for ( Technology dep : this.dependencies.values( ) ) {
+			deps.add( dep.name );
+			if ( dep.fullDependencies == null ) {
+				dep.buildDependencies( );
+			}
+			deps.addAll( dep.fullDependencies );
+		}
+		this.fullDependencies = deps;
+	}
+
+
+	/**
+	 * Check if the current technology has another technology in its list of direct dependencies.
+	 * 
+	 * @param dependency
+	 *            the dependency to look for
+	 * @return <code>true</code> if the current technology depends on the specified technology
+	 *         directly, <code>false</code> otherwise.
+	 */
+	public boolean hasDirectDependency( String dependency )
+	{
+		return this.dependencies.containsKey( dependency );
+	}
+
+
+	/**
+	 * Check if there are technologies which depend on the current technology.
+	 * 
+	 * @return <code>true</code> if at least one technology depends on the current technology,
+	 *         <code>false</code> otherwise.
+	 */
+	public boolean hasReverseDependencies( )
+	{
+		return !this.dependencyOf.isEmpty( );
+	}
+
+
+	/**
+	 * Add a new dependency to the current technology.
+	 * 
+	 * @param dep
+	 *            the dependency to add
+	 */
+	void addDependency( Technology dep )
+	{
+		this.dependencies.put( dep.name , dep );
+		dep.dependencyOf.put( this.name , this );
+		this.fullDependencies = null;
+	}
+
+
+	/**
+	 * Remove an existing dependency from the current technology.
+	 * 
+	 * @param dependency
+	 *            the name of the dependency to remove
+	 */
+	void removeDependency( String dependency )
+	{
+		Technology dep = this.dependencies.get( dependency );
+		dep.dependencyOf.remove( this.name );
+		this.dependencies.remove( dependency );
+		this.fullDependencies = null;
+	}
+
+
+	/** @return the list of technology names the current technology directly depends on */
+	public List< String > getDependencies( )
+	{
+		return new LinkedList< String >( this.dependencies.keySet( ) );
+	}
+
+
+	/**
+	 * Delete the current technology, removing it from the reverse dependency lists of its
+	 * dependencies and from its category.
+	 */
+	void delete( )
+	{
+		List< String > deps = new LinkedList< String >( this.dependencies.keySet( ) );
+		for ( String dep : deps ) {
+			this.removeDependency( dep );
+		}
+		this.category.removeTechnology( this );
+	}
+
+
+	/** @return the string identifier of the technology's name */
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	/** @return the category the technology belongs to. */
+	public Category getCategory( )
+	{
+		return category;
+	}
+
+
+	/** @return the string identifier of the technology's description */
+	public String getDescription( )
+	{
+		return description;
+	}
+
+
+	/**
+	 * Update the technology's category
+	 * 
+	 * @param category
+	 *            the new category
+	 */
+	void setCategory( Category category )
+	{
+		this.category = category;
+	}
+
+
+	/**
+	 * Update the string identifier of the technology's description
+	 * 
+	 * @param description
+	 *            the new description string ID
+	 */
+	void setDescription( String description )
+	{
+		this.description = description;
+	}
+
+
+	/** @return the amount of research points required to implement the technology */
+	public double getPoints( )
+	{
+		return points;
+	}
+
+
+	/**
+	 * Update the amount of research points required to implement the technology.
+	 * 
+	 * @param points
+	 *            the new amount of points
+	 */
+	void setPoints( int points )
+	{
+		this.points = points;
+	}
+
+
+	/** @return the implementation cost */
+	public double getCost( )
+	{
+		return cost;
+	}
+
+
+	/**
+	 * Update the implementation cost.
+	 * 
+	 * @param cost
+	 *            the new implementation cost.
+	 */
+	void setCost( int cost )
+	{
+		this.cost = cost;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Constant.java b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Constant.java
index 971b5ef..7d184eb 100644
--- a/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Constant.java
+++ b/legacyworlds-server/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/Constant.java
@@ -71,13 +71,6 @@ public class Constant
 	}
 
 
-	public void setMinValue( Float minValue )
-	{
-
-		this.minValue = ( minValue == null ? null : minValue.doubleValue( ) );
-	}
-
-
 	public Double getMaxValue( )
 	{
 		return maxValue;
@@ -90,13 +83,6 @@ public class Constant
 	}
 
 
-	public void setMaxValue( Float maxValue )
-	{
-
-		this.maxValue = ( maxValue == null ? null : maxValue.doubleValue( ) );
-	}
-
-
 	public double getValue( )
 	{
 		return value;
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/pom.xml b/legacyworlds-server/legacyworlds-server-interfaces/pom.xml
index 1a0f426..d8b5772 100644
--- a/legacyworlds-server/legacyworlds-server-interfaces/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-interfaces/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-interfaces</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 
 	<name>Legacy Worlds server interfaces</name>
 	<description>This package contains interfaces for all beans provided by the various server components.</description>
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
index 59342d4..68f9159 100644
--- a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireDAO.java
@@ -7,7 +7,6 @@ import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.PlanetListData;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
-import com.deepclone.lw.sqld.game.EmpireTechLine;
 import com.deepclone.lw.sqld.game.GeneralInformation;
 
 
@@ -24,12 +23,6 @@ public interface EmpireDAO
 	public OverviewData getOverview( int empireId );
 
 
-	public List< EmpireTechLine > getTechnology( int empireId );
-
-
-	public void implementTechnology( int empireId , int lineId );
-
-
 	public List< PlanetListData > getPlanetList( int empireId );
 
 
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
index eb92be9..bf4d1b3 100644
--- a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/EmpireManagement.java
@@ -6,6 +6,7 @@ import com.deepclone.lw.cmd.player.ListPlanetsResponse;
 import com.deepclone.lw.cmd.player.EmpireResponse;
 import com.deepclone.lw.cmd.player.elist.EnemyListResponse;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.interfaces.i18n.LanguageTranslator;
 import com.deepclone.lw.utils.EmailAddress;
 
 
@@ -19,12 +20,19 @@ public interface EmpireManagement
 	public GamePageData getGeneralInformation( int empireId );
 
 
+	/**
+	 * Generate a translation interface in the language used by the account who controls an empire.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @return the translation interface
+	 */
+	public LanguageTranslator getTranslator( int empireId );
+
+
 	public EmpireResponse getOverview( int empireId );
 
 
-	public EmpireResponse implementTechnology( int empireId , int techId );
-
-
 	public ListPlanetsResponse getPlanetList( int empireId );
 
 
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
deleted file mode 100644
index 86736b4..0000000
--- a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.deepclone.lw.interfaces.game;
-
-
-public interface UpdatesDAO
-{
-	public boolean processUpdates( long tickId );
-}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/EmpireTechnologyDAO.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/EmpireTechnologyDAO.java
new file mode 100644
index 0000000..0274fac
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/EmpireTechnologyDAO.java
@@ -0,0 +1,74 @@
+package com.deepclone.lw.interfaces.game.techs;
+
+
+import java.util.List;
+import java.util.Map;
+
+import com.deepclone.lw.sqld.game.techs.EmpireTechnology;
+
+
+
+/**
+ * Interface for the empire technology and research data access component.
+ * 
+ * @author tseeker
+ */
+public interface EmpireTechnologyDAO
+{
+	/**
+	 * List the technologies (implemented, researched and being researched) for the empire.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier.
+	 * @return the list of technology records for the empire.
+	 */
+	public List< EmpireTechnology > getTechnologies( int empireId );
+
+
+	/**
+	 * Implement a technology for an empire.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @param technology
+	 *            the technology's name
+	 * @return an error code:
+	 *         <ul>
+	 *         <li>0 on success,</li>
+	 *         <li>1 if the technology does not exist or is not in the correct status,</li>
+	 *         <li>2 if the empire does not have sufficient resources.</li>
+	 *         </ul>
+	 */
+	public int implementTechnology( int empireId , String technology );
+
+
+	/**
+	 * Prepare the temporary table used for a research priorities update.
+	 */
+	public void startPrioritiesUpdate( );
+
+
+	/**
+	 * Upload new research priority values into the temporary table used for priority updates.
+	 * 
+	 * @param priorities
+	 *            the map associating relative technology identifiers with priority values
+	 */
+	public void uploadPriorities( Map< String , Integer > priorities );
+
+
+	/**
+	 * Execute prepared research priority updates for an empire.
+	 * 
+	 * @param empireId
+	 *            the empire's identifier
+	 * @return an error code:
+	 *         <ul>
+	 *         <li>0 on success,</li>
+	 *         <li>1 if the listed technologies do not match the empire's current research,</li>
+	 *         <li>2 if the new priorities were invalid.</li>
+	 *         </ul>
+	 */
+	public int finishPrioritiesUpdate( int empireId );
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/EmpireTechnologyManager.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/EmpireTechnologyManager.java
new file mode 100644
index 0000000..ac71688
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/EmpireTechnologyManager.java
@@ -0,0 +1,53 @@
+package com.deepclone.lw.interfaces.game.techs;
+
+
+import java.util.Map;
+
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse;
+import com.deepclone.lw.cmd.player.research.ViewResearchResponse;
+
+
+
+/**
+ * Interface for the empire research and technology management component.
+ * 
+ * @author tseeker
+ */
+public interface EmpireTechnologyManager
+{
+
+	/**
+	 * Generate the response to a research view request.
+	 * 
+	 * @param empireId
+	 *            the empire for whom the research view is being displayed.
+	 * @return the response containing the view
+	 */
+	public ViewResearchResponse getResearchData( int empireId );
+
+
+	/**
+	 * Implement a technology for an empire.
+	 * 
+	 * @param empireId
+	 *            the empire trying to implement a technology.
+	 * @param technology
+	 *            the name of the technology to implement
+	 * 
+	 * @return the response describing the result of the operation
+	 */
+	public ResearchOperationResponse implementTechnology( int empireId , String technology );
+
+
+	/**
+	 * Set an empire's research priorities.
+	 * 
+	 * @param empireId
+	 *            the empire trying to set its research priorities.
+	 * @param priorities
+	 *            a map associating relative technology identifiers to priorities.
+	 * 
+	 * @return the response describing the result of the operation
+	 */
+	public ResearchOperationResponse setResearchPriorities( int empireId , Map< String , Integer > priorities );
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/ResearchUpdateDAO.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/ResearchUpdateDAO.java
new file mode 100644
index 0000000..4b8537b
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/ResearchUpdateDAO.java
@@ -0,0 +1,56 @@
+package com.deepclone.lw.interfaces.game.techs;
+
+
+import java.util.List;
+import java.util.Map;
+
+import com.deepclone.lw.sqld.game.techs.ResearchUpdateInput;
+import com.deepclone.lw.sqld.game.techs.ResearchUpdateOutput;
+
+
+
+/**
+ * Data access component for research updates.
+ * 
+ * @author tseeker
+ */
+public interface ResearchUpdateDAO
+{
+
+	/**
+	 * Prepare the database for a research update batch.
+	 * 
+	 * @param updateId
+	 *            the current update's identifier
+	 */
+	public void prepareUpdate( long updateId );
+
+
+	/**
+	 * Access the update's information.
+	 * 
+	 * @param updateId
+	 *            the current update's identifier
+	 * @return the current research status for the empires in the current batch.
+	 */
+	public List< ResearchUpdateInput > getUpdateData( long updateId );
+
+
+	/**
+	 * Load the amount of research points for each empire in the update.
+	 * 
+	 * @param updateId
+	 *            the current update's identifier.
+	 * @return a map which associates empire identifiers to amounts of research points.
+	 */
+	public Map< Integer , Double > getResearchPoints( long updateId );
+
+
+	/**
+	 * Submit the output of the research update.
+	 * 
+	 * @param output
+	 *            a list of changes to commit to the database
+	 */
+	public void submitUpdateData( List< ResearchUpdateOutput > output );
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/TechnologyGraphDAO.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/TechnologyGraphDAO.java
new file mode 100644
index 0000000..9ce624e
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/TechnologyGraphDAO.java
@@ -0,0 +1,27 @@
+package com.deepclone.lw.interfaces.game.techs;
+
+
+import com.deepclone.lw.sqld.game.techs.TechGraph;
+import com.deepclone.lw.sqld.game.techs.TechGraphException;
+
+
+
+/**
+ * Data access component for the technology graph.
+ * 
+ * @author tseeker
+ */
+public interface TechnologyGraphDAO
+{
+
+	/**
+	 * Load the technology graph from the database.
+	 * 
+	 * @return the technology graph's representation
+	 * @throws TechGraphException
+	 *             if some logic error occurs while the graph is being loaded (for example, circular
+	 *             dependencies).
+	 */
+	public TechGraph loadGraph( )
+			throws TechGraphException;
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/TechnologyGraphManager.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/TechnologyGraphManager.java
new file mode 100644
index 0000000..3fcefa3
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/techs/TechnologyGraphManager.java
@@ -0,0 +1,36 @@
+package com.deepclone.lw.interfaces.game.techs;
+
+
+import java.util.List;
+
+import com.deepclone.lw.cmd.admin.techs.TechCategory;
+import com.deepclone.lw.sqld.game.techs.TechGraph;
+
+
+
+/**
+ * Interface for the technology graph manager, which allows queries on the technology graph, as well
+ * as updates.
+ * 
+ * @author tseeker
+ */
+public interface TechnologyGraphManager
+{
+
+	/**
+	 * Access a copy of the tech graph as it exists in the server's memory. This copy includes all
+	 * local changes.
+	 * 
+	 * @return a copy of the tech graph
+	 */
+	public TechGraph getGraph( );
+
+
+	/**
+	 * List all technology categories from the tech graph.
+	 * 
+	 * @return the list of technology categories
+	 */
+	public List< TechCategory > listCategories( );
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/DuplicateUpdateHandler.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/DuplicateUpdateHandler.java
new file mode 100644
index 0000000..9df38a5
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/DuplicateUpdateHandler.java
@@ -0,0 +1,30 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * Runtime exception indicating a duplicate game update phase handler registration.
+ * 
+ * @author tseeker
+ */
+public class DuplicateUpdateHandler
+		extends RuntimeException
+{
+
+	private static final long serialVersionUID = 1L;
+
+	private final GameUpdatePhase phase;
+
+
+	public DuplicateUpdateHandler( GameUpdatePhase phase )
+	{
+		super( "duplicate handler for phase " + phase.toString( ) );
+		this.phase = phase;
+	}
+
+
+	public GameUpdatePhase getPhase( )
+	{
+		return phase;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdate.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdate.java
new file mode 100644
index 0000000..db8d796
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdate.java
@@ -0,0 +1,25 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * Interface to the main game update component.
+ * 
+ * @author tseeker
+ */
+public interface GameUpdate
+{
+
+	/**
+	 * Register a handler component for a game update phase.
+	 * 
+	 * @param handler
+	 *            the handler to register
+	 * 
+	 * @throws DuplicateUpdateHandler
+	 *             if a handler has already been registered for the game update phase returned by
+	 *             the new handler's {@link GameUpdatePhaseHandler#getPhase()} method.
+	 */
+	public void registerHandler( GameUpdatePhaseHandler handler )
+			throws DuplicateUpdateHandler;
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdatePhase.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdatePhase.java
new file mode 100644
index 0000000..9e49fa0
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdatePhase.java
@@ -0,0 +1,69 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * The various phases of a game update. This enumeration should reflect the <em>update_type</em> SQL
+ * type.
+ * 
+ * @author tseeker
+ */
+public enum GameUpdatePhase {
+
+	/** Empire income and upkeep computation */
+	EMPIRE_MONEY( GameUpdateTarget.EMPIRE ) ,
+
+	/** Empire research update */
+	EMPIRE_RESEARCH( GameUpdateTarget.EMPIRE ) ,
+
+	/** Debt effects computation */
+	EMPIRE_DEBT( GameUpdateTarget.EMPIRE ) ,
+
+	/** Arrival of fleets on planets */
+	PLANET_FLEET_ARRIVALS( GameUpdateTarget.PLANET ) ,
+
+	/** Fleet movement computation */
+	PLANET_FLEET_MOVEMENTS( GameUpdateTarget.PLANET ) ,
+
+	/** Fleet status counter decrease */
+	PLANET_FLEET_STATUS( GameUpdateTarget.PLANET ) ,
+
+	/** Check for new battles */
+	PLANET_BATTLE_START( GameUpdateTarget.PLANET ) ,
+
+	/** Main battle computation */
+	PLANET_BATTLE_MAIN( GameUpdateTarget.PLANET ) ,
+
+	/** Check for battles that have ended */
+	PLANET_BATTLE_END( GameUpdateTarget.PLANET ) ,
+
+	/** Abandon planets */
+	PLANET_ABANDON( GameUpdateTarget.PLANET ) ,
+
+	/** Construct/destroy buildings */
+	PLANET_CONSTRUCTION( GameUpdateTarget.PLANET ) ,
+
+	/** Construct/destroy ships */
+	PLANET_MILITARY( GameUpdateTarget.PLANET ) ,
+
+	/** Update planetary population */
+	PLANET_POPULATION( GameUpdateTarget.PLANET ) ,
+
+	/** Compute planet income */
+	PLANET_MONEY( GameUpdateTarget.PLANET );
+
+	private final GameUpdateTarget target;
+
+
+	private GameUpdatePhase( GameUpdateTarget target )
+	{
+		this.target = target;
+	}
+
+
+	/** @return the type of target affected by the update */
+	public GameUpdateTarget getTarget( )
+	{
+		return target;
+	}
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdatePhaseHandler.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdatePhaseHandler.java
new file mode 100644
index 0000000..19ccf3e
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdatePhaseHandler.java
@@ -0,0 +1,40 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * Interface for handlers that implement a phase of the game update.
+ * 
+ * @author tseeker
+ */
+public interface GameUpdatePhaseHandler
+{
+
+	/**
+	 * @return the game update phase this handler is responsible for.
+	 */
+	public GameUpdatePhase getPhase( );
+
+
+	/**
+	 * Prepare the handler.
+	 * 
+	 * This method is called outside of any transaction before the update starts.
+	 * 
+	 * @param updateId
+	 *            the update's identifier
+	 */
+	public void onPhaseStart( long updateId );
+
+
+	/**
+	 * Update the game.
+	 * 
+	 * @param updateId
+	 *            the update's identifier.
+	 * 
+	 * @return <code>true</code> if there are more updates of this type to process,
+	 *         <code>false</code> if the phase is complete
+	 */
+	public boolean updateGame( long updateId );
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateTarget.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateTarget.java
new file mode 100644
index 0000000..4baf750
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateTarget.java
@@ -0,0 +1,17 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * The type of item targeted by a game update phase.
+ * 
+ * @author tseeker
+ */
+public enum GameUpdateTarget {
+
+	/** Empire update */
+	EMPIRE ,
+
+	/** Planet update */
+	PLANET
+
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java
new file mode 100644
index 0000000..8ee39da
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java
@@ -0,0 +1,46 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * Interface of the game update data access component.
+ * 
+ * @author tseeker
+ */
+public interface UpdatesDAO
+{
+
+	/**
+	 * Prepare a batch of game updates.
+	 * 
+	 * @param updateId
+	 *            game update identifier
+	 * @param phase
+	 *            phase of the update
+	 * 
+	 * @return <code>true</code> if there are game updates to process in the specified phase,
+	 *         <code>false</code> if all updates in the current phase have been handled.
+	 */
+	public boolean prepareUpdates( long updateId , GameUpdatePhase phase );
+
+
+	/**
+	 * Execute a game update phase that is implemented as a stored procedure.
+	 * 
+	 * @param updateId
+	 *            game update identifier
+	 * @param phase
+	 *            phase of the game update to execute
+	 */
+	public void executeProceduralUpdate( long updateId , GameUpdatePhase phase );
+
+
+	/**
+	 * Mark a set of update records as processed.
+	 * 
+	 * @param updateId
+	 *            game update identifier
+	 * @param phase
+	 *            current phase of the game update
+	 */
+	public void validateUpdatedRecords( long updateId , GameUpdatePhase phase );
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/LanguageTranslator.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/LanguageTranslator.java
new file mode 100644
index 0000000..d7eb180
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/LanguageTranslator.java
@@ -0,0 +1,31 @@
+package com.deepclone.lw.interfaces.i18n;
+
+
+/**
+ * Translation interface specific to a language
+ * 
+ * @author tseeker
+ */
+public interface LanguageTranslator
+{
+
+	/** @return the code of the interface's language */
+	public String getLanguage( );
+
+
+	/** @return the name of the interface's language */
+	public String getLanguageName( );
+
+
+	/**
+	 * Translate a string.
+	 * 
+	 * @param string
+	 *            the string identifier
+	 * @return the string's translation in the interface's language
+	 * @throws UnknownStringException
+	 *             if the string does not exist
+	 */
+	public String translate( String string )
+			throws UnknownStringException;
+}
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
index 9c5eb39..1990617 100644
--- a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/i18n/Translator.java
@@ -62,4 +62,17 @@ public interface Translator
 	 */
 	public String translate( String language , String string )
 			throws UnknownStringException , UnknownLanguageException;
+
+
+	/**
+	 * Initialise a translator for a specific language
+	 * 
+	 * @param language
+	 *            the identifier of the language
+	 * @return a translation interface for the language
+	 * @throws UnknownLanguageException
+	 *             if the specified language does not exist or is not supported
+	 */
+	public LanguageTranslator getLanguageTranslator( String language )
+			throws UnknownLanguageException;
 }
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java
index 825e886..7b003b1 100644
--- a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/ConstantsManager.java
@@ -2,7 +2,6 @@ package com.deepclone.lw.interfaces.sys;
 
 
 import java.util.Collection;
-import java.util.Set;
 
 
 
@@ -32,15 +31,15 @@ public interface ConstantsManager
 
 
 	/**
-	 * Registers a constants user, which will need to be informed of the constants' changes. If the
+	 * Register a constants user, which will need to be informed of the constants' changes. If the
 	 * required constants have not been registered yet, the user instance will not be notified.
 	 * 
 	 * @param user
 	 *            the constants user component to register
 	 * @param constants
-	 *            set of constant names the user wants to be informed about
+	 *            constant names the user wants to be informed about
 	 */
-	public void registerUser( ConstantsUser user , Set< String > constants );
+	public void registerUser( ConstantsUser user , String... constants );
 
 
 	/**
diff --git a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java
index d8cc5d2..62fa5d0 100644
--- a/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java
+++ b/legacyworlds-server/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/sys/SystemStatus.java
@@ -50,4 +50,11 @@ public interface SystemStatus
 	public Long checkStuckTick( )
 			throws MaintenanceStatusException;
 
+
+	/**
+	 * Update the status when a game update has been completed.
+	 */
+	public void endTick( )
+			throws TickStatusException , MaintenanceStatusException;
+
 }
diff --git a/legacyworlds-server/legacyworlds-server-main/data/buildables-test.xml b/legacyworlds-server/legacyworlds-server-main/data/buildables-test.xml
deleted file mode 100644
index 69bc47a..0000000
--- a/legacyworlds-server/legacyworlds-server-main/data/buildables-test.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<buildables xmlns="http://www.deepclone.com/lw/b6/m1/buildables" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/buildables buildables.xsd">
-
-	<building name="milFactory" description="milFactoryDescription" type="WORK" output="5" workers="200">
-		<cost build="100" upkeep="10" work="4800" />
-	</building>
-	<building name="turret" description="turretDescription" type="DEF" output="10" workers="5">
-		<cost build="40" upkeep="2" work="600" />
-	</building>
-	<building name="indFactory" description="indFactoryDescription" type="CASH" output="4" workers="500">
-		<cost build="500" upkeep="20" work="28800" />
-		<tech name="civTech" level="1" />
-	</building>
-	<building name="reanimationCentre" description="reanimationCentreDescription" type="POP" output="1" workers="300">
-		<cost build="4000" upkeep="200" work="57600" />
-		<tech name="civTech" level="2" />
-	</building>
-	<building name="superTurret" description="superTurretDescription" type="DEF" output="500" workers="1">
-		<cost build="4000" upkeep="10" work="20000" />
-		<tech name="civTech" level="3" />
-	</building>
-
-	<ship name="fighter" description="fighterDescription" time="3" power="10">
-		<cost build="100" upkeep="20" work="100" />
-	</ship>
-	<ship name="cruiser" description="cruiserDescription" time="5" power="100">
-		<cost build="500" upkeep="80" work="500" />
-		<tech name="milTech" level="1" />
-	</ship>
-	<ship name="bCruiser" description="bCruiserDescription" time="4" power="335">
-		<cost build="2500" upkeep="320" work="2500" />
-		<tech name="milTech" level="2" />
-	</ship>
-	<ship name="dreadnought" description="dreadnoughtDescription" time="6" power="5000">
-		<cost build="12500" upkeep="1280" work="12500" />
-		<tech name="milTech" level="3" />
-	</ship>
-
-</buildables>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/data/buildables.xml b/legacyworlds-server/legacyworlds-server-main/data/buildables.xml
index 5ba9554..9b81696 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/buildables.xml
+++ b/legacyworlds-server/legacyworlds-server-main/data/buildables.xml
@@ -10,15 +10,15 @@
 	</building>
 	<building name="indFactory" description="indFactoryDescription" type="CASH" output="1" workers="500">
 		<cost build="500" upkeep="20" work="28800" />
-		<tech name="civTech" level="1" />
+		<tech>indFactTech</tech>
 	</building>
 	<building name="reanimationCentre" description="reanimationCentreDescription" type="POP" output="1" workers="300">
 		<cost build="4000" upkeep="200" work="57600" />
-		<tech name="civTech" level="2" />
+		<tech>reanimationTech</tech>
 	</building>
 	<building name="superTurret" description="superTurretDescription" type="DEF" output="500" workers="1">
 		<cost build="4000" upkeep="10" work="20000" />
-		<tech name="civTech" level="3" />
+		<tech>superTurretTech</tech>
 	</building>
 
 	<ship name="fighter" description="fighterDescription" time="3" power="10">
@@ -26,15 +26,15 @@
 	</ship>
 	<ship name="cruiser" description="cruiserDescription" time="5" power="100">
 		<cost build="500" upkeep="80" work="5000" />
-		<tech name="milTech" level="1" />
+		<tech>cruisersTech</tech>
 	</ship>
 	<ship name="bCruiser" description="bCruiserDescription" time="4" power="335">
 		<cost build="2500" upkeep="320" work="25000" />
-		<tech name="milTech" level="2" />
+		<tech>bCruisersTech</tech>
 	</ship>
 	<ship name="dreadnought" description="dreadnoughtDescription" time="6" power="5000">
 		<cost build="12500" upkeep="1280" work="125000" />
-		<tech name="milTech" level="3" />
+		<tech>dreadnoughtsTech</tech>
 	</ship>
 
 </buildables>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/data/buildables.xsd b/legacyworlds-server/legacyworlds-server-main/data/buildables.xsd
index a3e80ab..7fad623 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/buildables.xsd
+++ b/legacyworlds-server/legacyworlds-server-main/data/buildables.xsd
@@ -25,7 +25,7 @@
 	<xs:complexType name="buildable" abstract="true">
 		<xs:sequence>
 			<xs:element name="cost" type="cost" />
-			<xs:element name="tech" type="tech" minOccurs="0" />
+			<xs:element name="tech" type="xs:token" minOccurs="0" />
 		</xs:sequence>
 		<xs:attribute name="name" use="required" type="xs:string" />
 		<xs:attribute name="description" use="required" type="xs:string" />
@@ -56,9 +56,4 @@
 		<xs:attribute name="upkeep" use="required" type="xs:nonNegativeInteger" />
 	</xs:complexType>
 
-	<xs:complexType name="tech">
-		<xs:attribute name="name" use="required" type="xs:string" />
-		<xs:attribute name="level" use="required" type="xs:positiveInteger" />
-	</xs:complexType>
-
 </xs:schema>
diff --git a/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xml b/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xml
index c2ef4e9..955d560 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xml
+++ b/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xml
@@ -1,24 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text"
-xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text
-i18n-text.xsd">
+<lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text i18n-text.xsd">
+	
+	<include>text/buildings.xml</include>
+	<include>text/mail.xml</include>
+	<include>text/preferences.xml</include>
+	<include>text/technologies.xml</include>
 
 	<language id="en" name="English">
-		<from-file id="registrationMail" source="data/registrationMail-en.txt" />
-		<from-file id="passwordRecoveryMail" source="data/passwordRecoveryMail-en.txt" />
-		<from-file id="reactivationMail" source="data/reactivationMail-en.txt" />
-		<from-file id="addressChangeMail" source="data/addressChangeMail-en.txt" />
-		<from-file id="adminRecapMail" source="data/adminRecapMail.txt" />
-		<from-file id="messageMail" source="data/messageMail-en.txt" />
-		<from-file id="recapMail" source="data/recapMail-en.txt" />
-		<from-file id="quitMail" source="data/quitMail-en.txt" />
-		<from-file id="bannedMail" source="data/bannedMail-en.txt" />
-		<from-file id="banLiftedMail" source="data/banLiftedMail-en.txt" />
-		<from-file id="adminErrorMail" source="data/adminErrorMail.txt" />
-		<from-file id="inactivityWarningMail" source="data/inactivityWarningMail-en.txt" />
-		<from-file id="inactivityQuitMail" source="data/inactivityQuitMail-en.txt" />
-		
+
 		<inline-string id="instantNotification">
 			<value>
 ***
@@ -48,89 +38,6 @@ ${text}
 			<value>Messages from administrators:</value>
 		</inline-string>
 
-		<inline-string id="civTech">
-			<value>Civilian technologies</value>
-		</inline-string>
-		<inline-string id="civTechDescription">
-			<value>They're not just your slaves, they're your scientists, poets and workers. Make the poets work first, then use these technologies to increase their productivity! Useless poets.</value>
-		</inline-string>
-
-		<inline-string id="indFactTech">
-			<value>Universal assemblers</value>
-		</inline-string>
-		<inline-string id="indFactTechDescription">
-			<value>You know how it is when you have a thingy-bob that you need to build but you just don't have the right tool. Well, fear no more, Universal Assemblers will solve all your problems! Build anything and everything with these clever machines, they are 1337.</value>
-		</inline-string>
-		<inline-string id="reanimationTech">
-			<value>Corpse reanimation</value>
-		</inline-string>
-		<inline-string id="reanimationTechDescription">
-			<value>Tired of workers dying too early? Want a little less perspiration in your corporation? Zombies will work, won't complain and best of all, they can be fed on almost anything! Feed them your enemies! But mostly, use them to increase factory productivity by a rather nice amount.</value>
-		</inline-string>
-		<inline-string id="superTurretTech">
-			<value>Biological generators</value>
-		</inline-string>
-		<inline-string id="superTurretTechDescription">
-			<value>For every turret a military commander wants, there is a bigger, stronger turret he wants more. Now you too can have such a turret! It will defend your planet with ease while you venture out into the galaxy, bending it to your will. Available in all good hardware stores.</value>
-		</inline-string>
-
-		<inline-string id="milTech">
-			<value>Military technologies</value>
-		</inline-string>
-		<inline-string id="milTechDescription">
-			<value>It is only one who is thoroughly acquainted with the evils of war that can thoroughly understand the profitable way of carrying it on. - Sun Tzu</value>
-		</inline-string>
-
-		<inline-string id="cruisersTech">
-			<value>Orbital construction</value>
-		</inline-string>
-		<inline-string id="cruisersTechDescription">
-			<value>Ships built on the ground must endure the stress of atmospheric flight before they are even able to dominate the vast emptiness of space. Build them in space and they will be sleeker, more powerful and now free of the need for windows. Use your brand new Cruisers to dominate the known universe.</value>
-		</inline-string>
-		<inline-string id="bCruisersTech">
-			<value>Structural reinforcement</value>
-		</inline-string>
-		<inline-string id="bCruisersTechDescription">
-			<value>The power of your Cruisers can be augmented by an improved design! Take advantage of a more structurally sound space vehicle that can bring empires to their knees with its speed and technological grace.</value>
-		</inline-string>
-		<inline-string id="dreadnoughtsTech">
-			<value>Automated space docks</value>
-		</inline-string>
-		<inline-string id="dreadnoughtsTechDescription">
-			<value>Technology has advanced. The ultimate weapon is now available. Claim the awesome power of the Dreadnought and crush your enemies. Ever wanted a tank in space? Well, now you have it. All their base are belong to you.</value>
-		</inline-string>
-
-		<inline-string id="milFactory">
-			<value>Ship parts factory</value>
-		</inline-string>
-		<inline-string id="milFactoryDescription">
-			<value>A Ship parts factory is a mass production factory for the creation of components used in your space faring vessels. They are essential to any space empire; Properly managed factories produce new vehicles quickly and efficiently.</value>
-		</inline-string>
-		<inline-string id="turret">
-			<value>Defence turret</value>
-		</inline-string>
-		<inline-string id="turretDescription">
-			<value>Exploration without vigilance is the action of a fool. Turrets are the last line of defence for the planet, super massive weapons capable of destroying orbital vehicles from the ground. They can be placed anywhere, on any building or vacant land in your domain and will defend your planets from attack.</value>
-		</inline-string>
-		<inline-string id="indFactory">
-			<value>Generic assembly line</value>
-		</inline-string>
-		<inline-string id="indFactoryDescription">
-			<value>Factories are the backbone of a thriving economy, providing goods and matériel to buy and sell on the free market. Your brand new Generic assembly lines will enhance your wealth and keep the population employed. Properly managed factories produce many economic benefits for an empire.</value>
-		</inline-string>
-		<inline-string id="reanimationCentre">
-			<value>Reanimation centre</value>
-		</inline-string>
-		<inline-string id="reanimationCentreDescription">
-			<value>People are frail but cheap and robots are hard wearing but expensive. Renew the life of your workers and you can take advantage of a cheap zombie resource, giving you a third option for keeping your empire thriving.</value>
-		</inline-string>
-		<inline-string id="superTurret">
-			<value>Biological turret</value>
-		</inline-string>
-		<inline-string id="superTurretDescription">
-			<value>The perfect union of man and machine, Biological Turrets are a blending of the technological and the biological to provide the ultimate defence for your planets. More powerful and accurate than Turrets, in ground based defence they are unmatched. Keep your people away from them, however, as they tend to hunger for human flesh.</value>
-		</inline-string>
-
 		<inline-string id="fighter">
 			<value>Fighter</value>
 		</inline-string>
@@ -156,64 +63,6 @@ ${text}
 			<value>Bring the Dread. The Dreadnought is a large capital ship with awesome power and capabilities, expensive and slow but the ultimate in space domination.</value>
 		</inline-string>
 
-		<inline-string id="pgDisplay">
-			<value>Display preferences</value>
-		</inline-string>
-		<inline-string id="pUseRLTime">
-			<value>Real-life time</value>
-		</inline-string>
-		<inline-string id="pUseRLTimeDescription">
-			<value>Selecting this option will cause all durations to be displayed using real-life minutes.</value>
-		</inline-string>
-		<inline-string id="pgMap">
-			<value>Map defaults</value>
-		</inline-string>
-		<inline-string id="pMapX">
-			<value>Map centre (X)</value>
-		</inline-string>
-		<inline-string id="pMapXDescription">
-			<value>The abscissa of the default map centre.</value>
-		</inline-string>
-		<inline-string id="pMapY">
-			<value>Map centre (Y)</value>
-		</inline-string>
-		<inline-string id="pMapYDescription">
-			<value>The ordinates of the default map centre.</value>
-		</inline-string>
-		<inline-string id="pMapSize">
-			<value>Map size</value>
-		</inline-string>
-		<inline-string id="pMapSizeDescription">
-			<value>The default size of the map.</value>
-		</inline-string>
-		<inline-string id="pgMail">
-			<value>E-mail settings</value>
-		</inline-string>
-		<inline-string id="pMailOnPM">
-			<value>Private messages</value>
-		</inline-string>
-		<inline-string id="pMailOnPMDescription">
-			<value>Select the type of e-mail notifications you will get for private messages sent by other empires.</value>
-		</inline-string>
-		<inline-string id="pMailOnAlliance">
-			<value>Alliance messages</value>
-		</inline-string>
-		<inline-string id="pMailOnAllianceDescription">
-			<value>Select the type of e-mail notifications you will get for alliance-wise messages.</value>
-		</inline-string>
-		<inline-string id="pMailOnIM">
-			<value>Internal messages</value>
-		</inline-string>
-		<inline-string id="pMailOnIMDescription">
-			<value>Select the type of e-mail notifications you will get for internal game messages.</value>
-		</inline-string>
-		<inline-string id="pMailOnAdmin">
-			<value>Messages from administrators</value>
-		</inline-string>
-		<inline-string id="pMailOnAdminDescription">
-			<value>Select the type of e-mail notifications you will get for messages sent by the game's administrators.</value>
-		</inline-string>
-
 		<inline-string id="mapSizeSmall">
 			<value>Small (3x3)</value>
 		</inline-string>
@@ -276,7 +125,7 @@ ${text}
 		<inline-string id="imEmptyMilQueues">
 			<value>Some of your planets have finished constructing ships:</value>
 		</inline-string>
-		
+
 		<inline-string id="imtEmptyMilQueue">
 			<value>Empty military queue at ${location}</value>
 		</inline-string>
@@ -291,7 +140,7 @@ ${text}
 		<inline-string id="imBattleStart">
 			<value>{{battle:${battleId} Battle #${battleId}}} has started at ${location}.</value>
 		</inline-string>
-		
+
 		<inline-string id="imtBattleEnd">
 			<value>Battle ended at ${location}</value>
 		</inline-string>
@@ -306,7 +155,7 @@ ${text}
 		<inline-string id="imStrikeStart">
 			<value>The citizens of ${location} are in a really bad mood and have started leaving their posts... We'd better do something about this.</value>
 		</inline-string>
-		
+
 		<inline-string id="imtStrikeEnd">
 			<value>Situation back to normal on ${location}</value>
 		</inline-string>
@@ -321,14 +170,14 @@ ${text}
 		<inline-string id="imLostPlanet">
 			<value>We have lost control of ${location}, which was taken from us by ${taker}.</value>
 		</inline-string>
-		
+
 		<inline-string id="imtAbandonPlanet">
 			<value>Planet ${location} abandoned</value>
 		</inline-string>
 		<inline-string id="imAbandonPlanet">
 			<value>Our forces have completed the evacuation of ${location}; the citizens of this world are left to fend for themselves.</value>
 		</inline-string>
-		
+
 		<!-- Internal messages - planet taken -->
 		<inline-string id="imtTakePlanet">
 			<value>Planet ${location} conquered</value>
@@ -339,7 +188,7 @@ ${text}
 		<inline-string id="imTakePlanet">
 			<value>We have seized control of planet ${location} from the clutches of ${owner}.</value>
 		</inline-string>
-		
+
 		<!-- Empire messages -->
 		<inline-string id="imtTechAvailable">
 			<value>${tech} available</value>
@@ -359,7 +208,7 @@ ${text}
 		<inline-string id="imDebtEnd">
 			<value>Good to see that our money problems are over, Sir. Good job. Well, of course it'd been better if these problems had never started... Sir? What are you doing with this handgu-</value>
 		</inline-string>
-		
+
 		<!-- Alliance messages -->
 		<inline-string id="imtPendingRequest">
 			<value>Pending alliance request</value>
@@ -409,7 +258,7 @@ ${text}
 		<inline-string id="imAllianceDisbanded">
 			<value>The leader has left and the alliance was disbanded.</value>
 		</inline-string>
-		
+
 		<!-- Fleets -->
 		<inline-string id="imtFleetArrival">
 			<value>Fleets have arrived at ${location}</value>
@@ -468,7 +317,7 @@ ${text}
 		<inline-string id="imfSwitchDefence">
 			<value>: switched to defence</value>
 		</inline-string>
-		
+
 		<!-- Administration messages -->
 		<inline-string id="imAdminWarning">
 			<value>You currently have ${warnings} warning(s). Please note that, upon reaching 3 warnings, the administration team will consider banning you.</value>
@@ -503,7 +352,7 @@ Your alliance, ${oldName}, had a name that was considered either vulgar, disresp
 
 It was disbanded.</value>
 		</inline-string>
-		
+
 		<!-- Bug tracker -->
 		<inline-string id="imtBugReportUpdate">
 			<value>Bug report #${id} updated</value>
@@ -518,20 +367,6 @@ It was disbanded.</value>
 	</language>
 
 	<language id="fr" name="Français">
-		<from-file id="registrationMail" source="data/registrationMail-fr.txt" />
-		<from-file id="passwordRecoveryMail" source="data/passwordRecoveryMail-fr.txt" />
-		<from-file id="reactivationMail" source="data/reactivationMail-fr.txt" />
-		<from-file id="addressChangeMail" source="data/addressChangeMail-fr.txt" />
-		<from-file id="adminRecapMail" source="data/adminRecapMail.txt" />
-		<from-file id="messageMail" source="data/messageMail-fr.txt" />
-		<from-file id="recapMail" source="data/recapMail-fr.txt" />
-		<from-file id="quitMail" source="data/quitMail-fr.txt" />
-		<from-file id="bannedMail" source="data/bannedMail-fr.txt" />
-		<from-file id="banLiftedMail" source="data/banLiftedMail-fr.txt" />
-		<from-file id="adminErrorMail" source="data/adminErrorMail.txt" />
-		<from-file id="inactivityWarningMail" source="data/inactivityWarningMail-fr.txt" />
-		<from-file id="inactivityQuitMail" source="data/inactivityQuitMail-fr.txt" />
-
 		<inline-string id="instantNotification">
 			<value>
 ***
@@ -561,89 +396,6 @@ ${text}
 			<value>Messages des administrateurs :</value>
 		</inline-string>
 
-		<inline-string id="civTech">
-			<value>Technologies civiles</value>
-		</inline-string>
-		<inline-string id="civTechDescription">
-			<value>Ce ne sont pas uniquement vos esclaves, ce sont vos chercheurs, poètes et ouvriers. Faites travailler d'abord les poètes, puis utilisez ces technologies pour augmenter leur productivité! Poètes inutiles...</value>
-		</inline-string>
-
-		<inline-string id="indFactTech">
-			<value>Assembleurs universels</value>
-		</inline-string>
-		<inline-string id="indFactTechDescription">
-			<value>Vous savez ce que c'est d'avoir un truc à construire, alors que malheureusement vous ne diposez pas du bon outil. Eh bien, plus de peur : les Assembleurs Universels vont résoudre tous vos problèmes! Construisez tout et n'importe quoi avec ces machines intelligentes, elles sont 1337.</value>
-		</inline-string>
-		<inline-string id="reanimationTech">
-			<value>Réanimation de cadavres</value>
-		</inline-string>
-		<inline-string id="reanimationTechDescription">
-			<value>Fatigué de ces ouvriers qui passent l'arme à gauche trop tôt? Vous voulez un peu plus de transpiration dans vos ateliers ? Les zombies vont travailler, ne vont pas se plaindre, et encore mieux ils peuvent être nourris avec n'importe quoi - y compris avec vos énemis! Mais surtout, utilisez les pour augmenter de manière significative la productivité de vos usines.</value>
-		</inline-string>
-		<inline-string id="superTurretTech">
-			<value>Générateurs biologiques</value>
-		</inline-string>
-		<inline-string id="superTurretTechDescription">
-			<value>Pour chaque tourelle qu'un commandant militaire réclame, il en est une qu'il désire encore plus. Maintenant, vous aussi pouvez avoir de telles tourelles! Elles défendront vos planètes avec aisance, pendant que vous vous aventurerez dans la galaxie, la pliant à votre volonté. Disponible chez tous les bons quincaillers.</value>
-		</inline-string>
-
-		<inline-string id="milTech">
-			<value>Technologies militaires</value>
-		</inline-string>
-		<inline-string id="milTechDescription">
-			<value>Ceux qui ne comprennent pas les dommages que la guerre peut causer n'en comprendront jamais les avantages. - Sun Tzu</value>
-		</inline-string>
-
-		<inline-string id="cruisersTech">
-			<value>Construction orbitale</value>
-		</inline-string>
-		<inline-string id="cruisersTechDescription">
-			<value>Les vaisseaux construits à la surface doivent subir le stress du vol atmosphérique avant même d'être lancés à assaut du grand vide interstellaire. Construisez-les dans l'espace et ils seront plus gracieux, plus puissants, et ne nécessiteront plus de fenêtres. Utilisez vos tout nouveaux croiseurs pour dominer l'univers connu.</value>
-		</inline-string>
-		<inline-string id="bCruisersTech">
-			<value>Consolidation structurelle</value>
-		</inline-string>
-		<inline-string id="bCruisersTechDescription">
-			<value>La puissance de vos croiseurs peut être augmentée par une conception améliorée! Profitez d'un véhicule à la structure mieux adaptée à l'espace qui peut mettre des empires à genoux grâce à sa vitesse et sa finesse technologique.</value>
-		</inline-string>
-		<inline-string id="dreadnoughtsTech">
-			<value>Docks orbitaux automatisés</value>
-		</inline-string>
-		<inline-string id="dreadnoughtsTechDescription">
-			<value>La technologie a évolué. L'arme ultime est maintenant disponible. Revendiquez la puissance écrasante du cuirassé et pulvérisez vos opposants. Déjà rêvé d'un tank de l'espace ? Eh bien, maintenant, vous l'avez. All their base are belong to you.</value>
-		</inline-string>
-
-		<inline-string id="milFactory">
-			<value>Fabrique de pièces de vaisseaux</value>
-		</inline-string>
-		<inline-string id="milFactoryDescription">
-			<value>Une fabrique de pièces de vaisseaux est une usine de production de masse pour la création des composants utilisés dans vos vaisseaux spatiaux. Elles sont essentielles à tout empire spatial ; bien gérées, elles produisent de nouveaux vaisseaux rapidement et efficacement.</value>
-		</inline-string>
-		<inline-string id="turret">
-			<value>Tourelle défensive</value>
-		</inline-string>
-		<inline-string id="turretDescription">
-			<value>Explorer sans être sur ses gardes est une statégie de lunatique. Les tourelles défensives sont la dernière ligne de défense d'une planète, énormes armes capables de détruire des véhicules en orbite depuis le sol. Elles peuvent être placées n'importe où, sur n'importe quel bâtiment ou espace dégagé de votre domaine, et défendront vos planètes contre les attaques.</value>
-		</inline-string>
-		<inline-string id="indFactory">
-			<value>Ligne de production générique</value>
-		</inline-string>
-		<inline-string id="indFactoryDescription">
-			<value>Les usines sont l'épine dorsale d'une économie florissante, fournissant des biens et pièces détachées qui peuvent être vendues sur le marché. Vos Lignes de production génériques flambant neuves vont augmenter votre richesse et conserver votre population dans l'emploi. Des usines bien gérées fournissent de nombreux bénéfices économiques à un empire.</value>
-		</inline-string>
-		<inline-string id="reanimationCentre">
-			<value>Centre de réanimation</value>
-		</inline-string>
-		<inline-string id="reanimationCentreDescription">
-			<value>Les humains sont frêles mais peu coûteux et les robots sont endurants mais très chers. Renouvelez la vie de vos travailleurs et vous pourrez tirer partie d'une ressource de zombies bon marché, vous permettant d'explorer une troisième voie pour conserver un empire florissant.</value>
-		</inline-string>
-		<inline-string id="superTurret">
-			<value>Tourelle biologique</value>
-		</inline-string>
-		<inline-string id="superTurretDescription">
-			<value>L'union parfaite de l'homme et de la machine, les tourelles biologiques sont un mélange du technologique et du biologique pour fournir la défense ultime à vos planètes. Plus puissantes et précises que les tourelles, au niveau défense au sol, elles ne peuvent être surclassées. Mais gardez votre population à distance, car elles ont tendance à avoir faim de chair humaine!</value>
-		</inline-string>
-
 		<inline-string id="fighter">
 			<value>Chasseur</value>
 		</inline-string>
@@ -669,64 +421,6 @@ ${text}
 			<value>Amenez la cuirasse. Le cuirassé est un énorme vaisseau aux performances et à la puissance impressionantes, cher et lent, mais le nec plus ultra de la domination spatiale.</value>
 		</inline-string>
 
-		<inline-string id="pgDisplay">
-			<value>Préférences d'affichage</value>
-		</inline-string>
-		<inline-string id="pUseRLTime">
-			<value>Temps réel</value>
-		</inline-string>
-		<inline-string id="pUseRLTimeDescription">
-			<value>Les durées seront affichées en utilisant de "vraies" mesures si cette option est sélectionnée.</value>
-		</inline-string>
-		<inline-string id="pgMap">
-			<value>Carte</value>
-		</inline-string>
-		<inline-string id="pMapX">
-			<value>Centre de la carte (X)</value>
-		</inline-string>
-		<inline-string id="pMapXDescription">
-			<value>L'abscisse par défaut du centre de la carte.</value>
-		</inline-string>
-		<inline-string id="pMapY">
-			<value>Centre de la carte (Y)</value>
-		</inline-string>
-		<inline-string id="pMapYDescription">
-			<value>L'ordonnée par défaut du centre de la carte.</value>
-		</inline-string>
-		<inline-string id="pMapSize">
-			<value>Taille de la carte</value>
-		</inline-string>
-		<inline-string id="pMapSizeDescription">
-			<value>La taille par défaut de la carte.</value>
-		</inline-string>
-		<inline-string id="pgMail">
-			<value>Envoi de courrier électronique</value>
-		</inline-string>
-		<inline-string id="pMailOnPM">
-			<value>Messages privés</value>
-		</inline-string>
-		<inline-string id="pMailOnPMDescription">
-			<value>Sélectionnez le type de notifications par courier électronique que vous recevrez lorsque d'autres empires vous envoient des messages privés.</value>
-		</inline-string>
-		<inline-string id="pMailOnAlliance">
-			<value>Messages d'alliance</value>
-		</inline-string>
-		<inline-string id="pMailOnAllianceDescription">
-			<value>Sélectionnez le type de notifications par courier électronique que vous recevrez lorsque vous recevez un message d'alliance.</value>
-		</inline-string>
-		<inline-string id="pMailOnIM">
-			<value>Messages internes</value>
-		</inline-string>
-		<inline-string id="pMailOnIMDescription">
-			<value>Sélectionnez le type de notifications par courier électronique que vous recevrez lorsque vous recevez un message interne du jeu.</value>
-		</inline-string>
-		<inline-string id="pMailOnAdmin">
-			<value>Messages des administrateurs</value>
-		</inline-string>
-		<inline-string id="pMailOnAdminDescription">
-			<value>Sélectionnez le type de notifications par courier électronique que vous recevrez lorsque vous recevez un message des administrateurs du jeu.</value>
-		</inline-string>
-
 		<inline-string id="mapSizeSmall">
 			<value>Petite (3x3)</value>
 		</inline-string>
@@ -789,7 +483,7 @@ ${text}
 		<inline-string id="imEmptyMilQueues">
 			<value>Certaines de vos planètes ont fini de construire des vaisseaux :</value>
 		</inline-string>
-		
+
 		<inline-string id="imtEmptyMilQueue">
 			<value>Liste de construction militaire vide sur ${location}</value>
 		</inline-string>
@@ -804,7 +498,7 @@ ${text}
 		<inline-string id="imBattleStart">
 			<value>{{battle:${battleId} La bataille #${battleId}}} a commencé sur ${location}.</value>
 		</inline-string>
-		
+
 		<inline-string id="imtBattleEnd">
 			<value>Bataille terminée sur ${location}</value>
 		</inline-string>
@@ -819,7 +513,7 @@ ${text}
 		<inline-string id="imStrikeStart">
 			<value>Les habitants de ${location} sont de très mauvaise humeur et ont commencé à quitter leurs postes... Nous devrions faire quelque chose à ce sujet.</value>
 		</inline-string>
-		
+
 		<inline-string id="imtStrikeEnd">
 			<value>Situation revenue à la normale sur ${location}</value>
 		</inline-string>
@@ -834,14 +528,14 @@ ${text}
 		<inline-string id="imLostPlanet">
 			<value>Nous avons perdu le contrôle de ${location}, qui nous a été prise par ${taker}.</value>
 		</inline-string>
-		
+
 		<inline-string id="imtAbandonPlanet">
 			<value>Abandon de la planète ${location}</value>
 		</inline-string>
 		<inline-string id="imAbandonPlanet">
 			<value>Nos forces ont terminé l'évacutation de ${location}; les habitants de ce monde sont livrés à eux-mêmes.</value>
 		</inline-string>
-		
+
 		<!-- Internal messages - planet taken -->
 		<inline-string id="imtTakePlanet">
 			<value>Conquête de la planète${location}</value>
@@ -852,7 +546,7 @@ ${text}
 		<inline-string id="imTakePlanet">
 			<value>Nous avons pris le contrôle de la planète ${location} des griffes de ${owner}.</value>
 		</inline-string>
-		
+
 		<!-- Empire messages -->
 		<inline-string id="imtTechAvailable">
 			<value>${tech} disponible</value>
@@ -872,7 +566,7 @@ ${text}
 		<inline-string id="imDebtEnd">
 			<value>C'est bon de voir que nos problèmes d'argent sont résolus, Chef. Bien joué. Bien sûr, ça aurait été mieux si ces problèmes n'avaient jamais eu lieu... Chef, qu'est ce que vous faites avec ce pistol-</value>
 		</inline-string>
-		
+
 		<!-- Alliance messages -->
 		<inline-string id="imtPendingRequest">
 			<value>Demande d'alliance en attente</value>
@@ -922,7 +616,7 @@ ${text}
 		<inline-string id="imAllianceDisbanded">
 			<value>Le dirigeant est parti et l'alliance a été dissoute.</value>
 		</inline-string>
-		
+
 		<!-- Fleets -->
 		<inline-string id="imtFleetArrival">
 			<value>Arrivée de flottes sur ${location}</value>
@@ -981,7 +675,7 @@ ${text}
 		<inline-string id="imfSwitchDefence">
 			<value>: est passée en défensee</value>
 		</inline-string>
-		
+
 		<!-- Administration messages -->
 		<inline-string id="imAdminWarning">
 			<value>Vous avez actuellement ${warnings} avertissement(s). Veuillez noter que, lorsque vous atteindrez 3 avertissements, l'équipe d'administration va envisager votre expulsion.</value>
@@ -1016,7 +710,7 @@ Votre alliance, ${oldName}, avait un nom qui a été considéré comme vulgaire,
 
 Elle a été dissoute.</value>
 		</inline-string>
-		
+
 		<!-- Bug tracker -->
 		<inline-string id="imtBugReportUpdate">
 			<value>Rapport de bug #${id} mis à jour</value>
diff --git a/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xsd b/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xsd
index 8f8932d..599b13d 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xsd
+++ b/legacyworlds-server/legacyworlds-server-main/data/i18n-text.xsd
@@ -5,6 +5,7 @@
 	<xs:element name="lw-text-data">
 		<xs:complexType>
 			<xs:sequence>
+				<xs:element name="include" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded" />
 				<xs:element name="language" type="language" minOccurs="0" maxOccurs="unbounded" />
 			</xs:sequence>
 		</xs:complexType>
diff --git a/legacyworlds-server/legacyworlds-server-main/data/techs-test.xml b/legacyworlds-server/legacyworlds-server-main/data/techs-test.xml
deleted file mode 100644
index f2b8f44..0000000
--- a/legacyworlds-server/legacyworlds-server-main/data/techs-test.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<technologies xmlns="http://www.deepclone.com/lw/b6/m1/techs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/techs techs.xsd">
-
-	<tech-line name="milTech" description="milTechDescription">
-		<level name="cruisersTech" description="cruisersTechDescription"
-			points="5000" cost="10000" />
-		<level name="bCruisersTech" description="bCruisersTechDescription"
-			points="20000" cost="30000" />
-		<level name="dreadnoughtsTech" description="dreadnoughtsTechDescription"
-			points="48000" cost="100000" />
-	</tech-line>
-
-	<tech-line name="civTech" description="civTechDescription">
-		<level name="indFactTech" description="indFactTechDescription"
-			points="2000" cost="5000" />
-		<level name="reanimationTech" description="reanimationTechDescription"
-			points="10000" cost="25000" />
-		<level name="superTurretTech" description="superTurretTechDescription"
-			points="30000" cost="125000" />
-	</tech-line>
-
-</technologies>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/data/techs.xml b/legacyworlds-server/legacyworlds-server-main/data/techs.xml
index aef6fc6..c15f5da 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/techs.xml
+++ b/legacyworlds-server/legacyworlds-server-main/data/techs.xml
@@ -1,23 +1,27 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <technologies xmlns="http://www.deepclone.com/lw/b6/m1/techs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/techs techs.xsd">
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/techs techs-m2.xsd">
 
-	<tech-line name="milTech" description="milTechDescription">
-		<level name="cruisersTech" description="cruisersTechDescription"
-			points="25000" cost="10000" />
-		<level name="bCruisersTech" description="bCruisersTechDescription"
-			points="900000" cost="400000" />
-		<level name="dreadnoughtsTech" description="dreadnoughtsTechDescription"
-			points="2250000" cost="1012500" />
-	</tech-line>
+	<category name="milTech" description="milTechDescription" />
+	<category name="civTech" description="civTechDescription" />
 
-	<tech-line name="civTech" description="civTechDescription">
-		<level name="indFactTech" description="indFactTechDescription"
-			points="10000" cost="5000" />
-		<level name="reanimationTech" description="reanimationTechDescription"
-			points="562500" cost="281250" />
-		<level name="superTurretTech" description="superTurretTechDescription"
-			points="1350000" cost="607500" />
-	</tech-line>
+	<technology name="cruisersTech" category="milTech" description="cruisersTechDescription" points="5000" cost="10000" />
+	<technology name="bCruisersTech" category="milTech" description="bCruisersTechDescription" points="20000"
+		cost="30000">
+		<depends on="cruisersTech" />
+	</technology>
+	<technology name="dreadnoughtsTech" category="milTech" description="dreadnoughtsTechDescription" points="48000"
+		cost="100000">
+		<depends on="bCruisersTech" />
+	</technology>
 
+	<technology name="indFactTech" category="civTech" description="indFactTechDescription" points="2000" cost="5000" />
+	<technology name="reanimationTech" category="civTech" description="reanimationTechDescription" points="10000"
+		cost="25000">
+		<depends on="indFactTech" />
+	</technology>
+	<technology name="superTurretTech" category="civTech" description="superTurretTechDescription" points="30000"
+		cost="125000">
+		<depends on="reanimationTech" />
+	</technology>
 </technologies>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/data/techs.xsd b/legacyworlds-server/legacyworlds-server-main/data/techs.xsd
index 84698c3..cab088c 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/techs.xsd
+++ b/legacyworlds-server/legacyworlds-server-main/data/techs.xsd
@@ -5,24 +5,30 @@
 	<xs:element name="technologies">
 		<xs:complexType>
 			<xs:sequence>
-				<xs:element name="tech-line" type="tech-line" minOccurs="1" maxOccurs="unbounded" />
+				<xs:element name="category" type="category" minOccurs="1" maxOccurs="unbounded" />
+				<xs:element name="technology" type="technology" minOccurs="1" maxOccurs="unbounded" />
 			</xs:sequence>
 		</xs:complexType>
 	</xs:element>
 
-	<xs:complexType name="tech-line">
-		<xs:sequence>
-			<xs:element name="level" type="tech-level" minOccurs="1" maxOccurs="unbounded" />
-		</xs:sequence>
+	<xs:complexType name="category">
 		<xs:attribute name="name" use="required" type="xs:token" />
 		<xs:attribute name="description" use="required" type="xs:token" />
 	</xs:complexType>
 
-	<xs:complexType name="tech-level">
+	<xs:complexType name="technology">
+		<xs:sequence>
+			<xs:element name="depends" type="dependency" minOccurs="0" maxOccurs="unbounded" />
+		</xs:sequence>
 		<xs:attribute name="name" use="required" type="xs:token" />
+		<xs:attribute name="category" use="required" type="xs:token" />
 		<xs:attribute name="description" use="required" type="xs:token" />
 		<xs:attribute name="points" use="required" type="xs:positiveInteger" />
 		<xs:attribute name="cost" use="required" type="xs:positiveInteger" />
 	</xs:complexType>
+	
+	<xs:complexType name="dependency">
+		<xs:attribute name="on" use="required" type="xs:token" />
+	</xs:complexType>
 
 </xs:schema>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/data/text/buildings.xml b/legacyworlds-server/legacyworlds-server-main/data/text/buildings.xml
new file mode 100644
index 0000000..81cdcf2
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/buildings.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text ../i18n-text.xsd">
+
+	<language id="en" name="English">
+
+		<inline-string id="milFactory">
+			<value>Ship parts factory</value>
+		</inline-string>
+		<inline-string id="milFactoryDescription">
+			<value>A Ship parts factory is a mass production factory for the creation of components used in your space faring vessels. They are essential to any space empire; Properly managed factories produce new vehicles quickly and efficiently.</value>
+		</inline-string>
+		<inline-string id="turret">
+			<value>Defence turret</value>
+		</inline-string>
+		<inline-string id="turretDescription">
+			<value>Exploration without vigilance is the action of a fool. Turrets are the last line of defence for the planet, super massive weapons capable of destroying orbital vehicles from the ground. They can be placed anywhere, on any building or vacant land in your domain and will defend your planets from attack.</value>
+		</inline-string>
+		<inline-string id="indFactory">
+			<value>Generic assembly line</value>
+		</inline-string>
+		<inline-string id="indFactoryDescription">
+			<value>Factories are the backbone of a thriving economy, providing goods and matériel to buy and sell on the free market. Your brand new Generic assembly lines will enhance your wealth and keep the population employed. Properly managed factories produce many economic benefits for an empire.</value>
+		</inline-string>
+		<inline-string id="reanimationCentre">
+			<value>Reanimation centre</value>
+		</inline-string>
+		<inline-string id="reanimationCentreDescription">
+			<value>People are frail but cheap and robots are hard wearing but expensive. Renew the life of your workers and you can take advantage of a cheap zombie resource, giving you a third option for keeping your empire thriving.</value>
+		</inline-string>
+		<inline-string id="superTurret">
+			<value>Biological turret</value>
+		</inline-string>
+		<inline-string id="superTurretDescription">
+			<value>The perfect union of man and machine, Biological Turrets are a blending of the technological and the biological to provide the ultimate defence for your planets. More powerful and accurate than Turrets, in ground based defence they are unmatched. Keep your people away from them, however, as they tend to hunger for human flesh.</value>
+		</inline-string>
+
+	</language>
+	
+	
+	<language name="Français" id="fr">
+
+		<inline-string id="milFactory">
+			<value>Fabrique de pièces de vaisseaux</value>
+		</inline-string>
+		<inline-string id="milFactoryDescription">
+			<value>Une fabrique de pièces de vaisseaux est une usine de production de masse pour la création des composants utilisés dans vos vaisseaux spatiaux. Elles sont essentielles à tout empire spatial ; bien gérées, elles produisent de nouveaux vaisseaux rapidement et efficacement.</value>
+		</inline-string>
+		<inline-string id="turret">
+			<value>Tourelle défensive</value>
+		</inline-string>
+		<inline-string id="turretDescription">
+			<value>Explorer sans être sur ses gardes est une statégie de lunatique. Les tourelles défensives sont la dernière ligne de défense d'une planète, énormes armes capables de détruire des véhicules en orbite depuis le sol. Elles peuvent être placées n'importe où, sur n'importe quel bâtiment ou espace dégagé de votre domaine, et défendront vos planètes contre les attaques.</value>
+		</inline-string>
+		<inline-string id="indFactory">
+			<value>Ligne de production générique</value>
+		</inline-string>
+		<inline-string id="indFactoryDescription">
+			<value>Les usines sont l'épine dorsale d'une économie florissante, fournissant des biens et pièces détachées qui peuvent être vendues sur le marché. Vos Lignes de production génériques flambant neuves vont augmenter votre richesse et conserver votre population dans l'emploi. Des usines bien gérées fournissent de nombreux bénéfices économiques à un empire.</value>
+		</inline-string>
+		<inline-string id="reanimationCentre">
+			<value>Centre de réanimation</value>
+		</inline-string>
+		<inline-string id="reanimationCentreDescription">
+			<value>Les humains sont frêles mais peu coûteux et les robots sont endurants mais très chers. Renouvelez la vie de vos travailleurs et vous pourrez tirer partie d'une ressource de zombies bon marché, vous permettant d'explorer une troisième voie pour conserver un empire florissant.</value>
+		</inline-string>
+		<inline-string id="superTurret">
+			<value>Tourelle biologique</value>
+		</inline-string>
+		<inline-string id="superTurretDescription">
+			<value>L'union parfaite de l'homme et de la machine, les tourelles biologiques sont un mélange du technologique et du biologique pour fournir la défense ultime à vos planètes. Plus puissantes et précises que les tourelles, au niveau défense au sol, elles ne peuvent être surclassées. Mais gardez votre population à distance, car elles ont tendance à avoir faim de chair humaine!</value>
+		</inline-string>
+
+	</language>
+
+</lw-text-data>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/data/text/mail.xml b/legacyworlds-server/legacyworlds-server-main/data/text/mail.xml
new file mode 100644
index 0000000..ecd50f4
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text ../i18n-text.xsd">
+
+	<language id="en" name="English">
+		<from-file id="registrationMail" source="mail/registrationMail-en.txt" />
+		<from-file id="passwordRecoveryMail" source="mail/passwordRecoveryMail-en.txt" />
+		<from-file id="reactivationMail" source="mail/reactivationMail-en.txt" />
+		<from-file id="addressChangeMail" source="mail/addressChangeMail-en.txt" />
+		<from-file id="adminRecapMail" source="mail/adminRecapMail.txt" />
+		<from-file id="messageMail" source="mail/messageMail-en.txt" />
+		<from-file id="recapMail" source="mail/recapMail-en.txt" />
+		<from-file id="quitMail" source="mail/quitMail-en.txt" />
+		<from-file id="bannedMail" source="mail/bannedMail-en.txt" />
+		<from-file id="banLiftedMail" source="mail/banLiftedMail-en.txt" />
+		<from-file id="adminErrorMail" source="mail/adminErrorMail.txt" />
+		<from-file id="inactivityWarningMail" source="mail/inactivityWarningMail-en.txt" />
+		<from-file id="inactivityQuitMail" source="mail/inactivityQuitMail-en.txt" />
+	</language>
+
+	<language id="fr" name="Français">
+		<from-file id="registrationMail" source="mail/registrationMail-fr.txt" />
+		<from-file id="passwordRecoveryMail" source="mail/passwordRecoveryMail-fr.txt" />
+		<from-file id="reactivationMail" source="mail/reactivationMail-fr.txt" />
+		<from-file id="addressChangeMail" source="mail/addressChangeMail-fr.txt" />
+		<from-file id="adminRecapMail" source="mail/adminRecapMail.txt" />
+		<from-file id="messageMail" source="mail/messageMail-fr.txt" />
+		<from-file id="recapMail" source="mail/recapMail-fr.txt" />
+		<from-file id="quitMail" source="mail/quitMail-fr.txt" />
+		<from-file id="bannedMail" source="mail/bannedMail-fr.txt" />
+		<from-file id="banLiftedMail" source="mail/banLiftedMail-fr.txt" />
+		<from-file id="adminErrorMail" source="mail/adminErrorMail.txt" />
+		<from-file id="inactivityWarningMail" source="mail/inactivityWarningMail-fr.txt" />
+		<from-file id="inactivityQuitMail" source="mail/inactivityQuitMail-fr.txt" />
+	</language>
+
+
+</lw-text-data>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/addressChangeMail-en.txt
similarity index 82%
rename from legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/addressChangeMail-en.txt
index 9dfc314..e424137 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/addressChangeMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Address change
+Legacy Worlds (B6M2) - Address change
 Hello,
 
 You are receiving this message because an user of Legacy Worlds (you, presumably) requested to change his or her address to ${address}.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/addressChangeMail-fr.txt
similarity index 83%
rename from legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/addressChangeMail-fr.txt
index 569a8e9..04d7c49 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/addressChangeMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/addressChangeMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Changement d'adresse
+Legacy Worlds (B6M2) - Changement d'adresse
 Bonjour,
 
 Vous avez reçu ce message car un utilisateur de Legacy Worlds (vous, probablement) a demandé à ce que son adresse soit changée pour ${address}.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/adminErrorMail.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/adminErrorMail.txt
similarity index 76%
rename from legacyworlds-server/legacyworlds-server-main/data/adminErrorMail.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/adminErrorMail.txt
index f577aa2..b2e0240 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/adminErrorMail.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/adminErrorMail.txt
@@ -1,4 +1,4 @@
-[LWB6-ADMIN] Server errors
+[LWB6M2-ADMIN] Server errors
 Errors have been found in the server's system log. Intervention might be required.
 
 ${contents}
diff --git a/legacyworlds-server/legacyworlds-server-main/data/adminRecapMail.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/adminRecapMail.txt
similarity index 73%
rename from legacyworlds-server/legacyworlds-server-main/data/adminRecapMail.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/adminRecapMail.txt
index 3d90b25..95a9335 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/adminRecapMail.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/adminRecapMail.txt
@@ -1,4 +1,4 @@
-[LWB6-ADMIN] Recap
+[LWB6M2-ADMIN] Recap
 Here's what happened in the past 12 hours...
 
 ${contents}
diff --git a/legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/banLiftedMail-en.txt
similarity index 79%
rename from legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/banLiftedMail-en.txt
index 39ceb1d..edf84f9 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/banLiftedMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Ban lifted
+Legacy Worlds (B6M2) - Ban lifted
 Hello,
 
 The ban on your account has been lifted.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/banLiftedMail-fr.txt
similarity index 77%
rename from legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/banLiftedMail-fr.txt
index 95b003f..7f43675 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/banLiftedMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/banLiftedMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Bannissement levé
+Legacy Worlds (B6M2) - Bannissement levé
 Bonjour,
 
 Le bannissement de votre compte a été levé.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/bannedMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/bannedMail-en.txt
similarity index 88%
rename from legacyworlds-server/legacyworlds-server-main/data/bannedMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/bannedMail-en.txt
index c546f78..2b344b8 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/bannedMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/bannedMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) account banned
+Legacy Worlds (B6M2) account banned
 Hello,
 
 Your Legacy Worlds account has been banned by the game's administrators for the following reason:
diff --git a/legacyworlds-server/legacyworlds-server-main/data/bannedMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/bannedMail-fr.txt
similarity index 90%
rename from legacyworlds-server/legacyworlds-server-main/data/bannedMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/bannedMail-fr.txt
index a2fb65e..0576f6a 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/bannedMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/bannedMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Compte banni
+Legacy Worlds (B6M2) - Compte banni
 Bonjour,
 
 Votre compte Legacy Worlds a été banni par les administrateurs du jeu pour la raison suivante :
diff --git a/legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityQuitMail-en.txt
similarity index 88%
rename from legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityQuitMail-en.txt
index d628f44..7b382c9 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityQuitMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Inactive account closed
+Legacy Worlds (B6M2) - Inactive account closed
 Hello,
 
 This email is being sent to inform you that your account on Legacy Worlds has been disabled. It had been inactive for 28 days.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityQuitMail-fr.txt
similarity index 88%
rename from legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityQuitMail-fr.txt
index 7441c8b..94c0496 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/inactivityQuitMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityQuitMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Fermeture de compte inactif
+Legacy Worlds (B6M2) - Fermeture de compte inactif
 Bonjour,
 
 Ce message vous a été envoyé pour vous prévenir que votre compte sur Legacy Worlds a été désactivé. Il a été inactif pendant 28 jours.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityWarningMail-en.txt
similarity index 89%
rename from legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityWarningMail-en.txt
index add97f3..b0f02fc 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityWarningMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Inactivity
+Legacy Worlds (B6M2) - Inactivity
 Hello,
 
 This email is being sent to warn you that your account on Legacy Worlds has been inactive for the past three weeks. Unless you connect in the coming week, it will be closed and your empire will be lost.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityWarningMail-fr.txt
similarity index 91%
rename from legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityWarningMail-fr.txt
index d2e8f27..088cb4c 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/inactivityWarningMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/inactivityWarningMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Inactivité
+Legacy Worlds (B6M2) - Inactivité
 Bonjour,
 
 Ce message vous a été envoyé pour vous prévenir que votre compte sur Legacy Worlds a été inactif pendant les trois dernières semaines. À moins que vous vous connectiez durant la semaine procahine, il sera fermé et votre empire sera perdu.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/messageMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/messageMail-en.txt
similarity index 82%
rename from legacyworlds-server/legacyworlds-server-main/data/messageMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/messageMail-en.txt
index 299b5c3..7550d66 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/messageMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/messageMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - New messages
+Legacy Worlds (B6M2) - New messages
 Hello ${empire},
 
 You have just received new messages in Legacy Worlds.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/messageMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/messageMail-fr.txt
similarity index 77%
rename from legacyworlds-server/legacyworlds-server-main/data/messageMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/messageMail-fr.txt
index d31af22..a7e81d7 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/messageMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/messageMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Nouveaux messages
+Legacy Worlds (B6M2) - Nouveaux messages
 Bonjour ${empire},
 
 Vous venez de recevoir de nouveaux messages sur Legacy Worlds!
diff --git a/legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/passwordRecoveryMail-en.txt
similarity index 92%
rename from legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/passwordRecoveryMail-en.txt
index d7cf79b..c3508e2 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/passwordRecoveryMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) password recovery
+Legacy Worlds (B6M2) password recovery
 Hello,
 
 You are receiving this message because an user of Legacy Worlds (you, presumably) requested to recover his password.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/passwordRecoveryMail-fr.txt
similarity index 91%
rename from legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/passwordRecoveryMail-fr.txt
index 94bc15e..a9bc10c 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/passwordRecoveryMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/passwordRecoveryMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Récupération de mot de passe
+Legacy Worlds (B6M2) - Récupération de mot de passe
 Bonjour,
 
 Vous avez reçu ce message car un utilisateur de Legacy Worlds (vous, probablement) a demandé à récupérer son mot de passe oublié.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/quitMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/quitMail-en.txt
similarity index 91%
rename from legacyworlds-server/legacyworlds-server-main/data/quitMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/quitMail-en.txt
index 8f379e7..032eb95 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/quitMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/quitMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Account closed
+Legacy Worlds (B6M2) - Account closed
 Hello,
 
 This email is being sent to confirm that your account has been closed as you requested.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/quitMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/quitMail-fr.txt
similarity index 85%
rename from legacyworlds-server/legacyworlds-server-main/data/quitMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/quitMail-fr.txt
index 2788e38..c293f47 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/quitMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/quitMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Compte désactivé
+Legacy Worlds (B6M2) - Compte désactivé
 Bonjour,
 
 Cet e-mail vous est envoyé pour confirmer que votre compte a été fermé à votre demande.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/reactivationMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/reactivationMail-en.txt
similarity index 85%
rename from legacyworlds-server/legacyworlds-server-main/data/reactivationMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/reactivationMail-en.txt
index c0176e0..cc79827 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/reactivationMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/reactivationMail-en.txt
@@ -1,8 +1,8 @@
-Legacy Worlds (B6M1) - Account reactivation
+Legacy Worlds (B6M2) - Account reactivation
 Hello,
 
 Thank you for revisiting at Legacy Worlds!
-Please note that you reactivated your account for "Beta 6 Milestone 1", a highly experimental, in-progress rewrite of the game. As this game does not include forums at this stage, you might want to register to Legacy Worlds Beta 5 to keep up with the game's news.
+Please note that you reactivated your account for "Beta 6 Milestone 2", a highly experimental, in-progress rewrite of the game. As this game does not include forums at this stage, you might want to register to Legacy Worlds Beta 5 to keep up with the game's news.
 
 Before we reactivate your account, there's just one more step to complete your request.
 You have to connect to the site using your e-mail address and password, then validate your account reactivation using the confirmation code below.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/reactivationMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/reactivationMail-fr.txt
similarity index 87%
rename from legacyworlds-server/legacyworlds-server-main/data/reactivationMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/reactivationMail-fr.txt
index 3a97e3c..62f3800 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/reactivationMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/reactivationMail-fr.txt
@@ -1,8 +1,8 @@
-Legacy Worlds (B6M1) - Réactivation de votre compte
+Legacy Worlds (B6M2) - Réactivation de votre compte
 Bonjour,
 
 Merci de revenir sur Legacy Worlds!
-Veuillez remarquer que vous venez de réactiver un compte sur le jeu "Beta 6 Milestone 1", une récriture en cours et par conséquent hautement expérimentale du jeu. Comme cette version ne dispose pas de forums à l'heure actuelle, nous vous suggérons de vous enregistrer également à Legacy Worlds Beta 5 afin de vous tenir au courant des dernières nouvelles.
+Veuillez remarquer que vous venez de réactiver un compte sur le jeu "Beta 6 Milestone 2", une récriture en cours et par conséquent hautement expérimentale du jeu. Comme cette version ne dispose pas de forums à l'heure actuelle, nous vous suggérons de vous enregistrer également à Legacy Worlds Beta 5 afin de vous tenir au courant des dernières nouvelles.
 
 Avant que nous réactivions votre compte, il vous reste une dernière étape à accomplir.
 Vous allez devoir vous connecter au site en utilisant votre adresse électronique et votre mot de passe, puis valider la réactivation de votre compte en saisissant le code de confirmation ci-dessous.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/recapMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/recapMail-en.txt
similarity index 77%
rename from legacyworlds-server/legacyworlds-server-main/data/recapMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/recapMail-en.txt
index c72810f..3b5f756 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/recapMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/recapMail-en.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Today's messages
+Legacy Worlds (B6M2) - Today's messages
 Hello ${empire},
 
 Here is a reminder of the messages you received today.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/recapMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/recapMail-fr.txt
similarity index 72%
rename from legacyworlds-server/legacyworlds-server-main/data/recapMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/recapMail-fr.txt
index 641f2d7..517d5a0 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/recapMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/recapMail-fr.txt
@@ -1,4 +1,4 @@
-Legacy Worlds (B6M1) - Messages du jour
+Legacy Worlds (B6M2) - Messages du jour
 Bonjour ${empire},
 
 Voici un récapitulatif des messages que vous avez reçu aujourd'hui.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/registrationMail-en.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/registrationMail-en.txt
similarity index 86%
rename from legacyworlds-server/legacyworlds-server-main/data/registrationMail-en.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/registrationMail-en.txt
index e91d215..4193dee 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/registrationMail-en.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/registrationMail-en.txt
@@ -1,8 +1,8 @@
-Legacy Worlds (B6M1) - Your account
+Legacy Worlds (B6M2) - Your account
 Hello,
 
 Thank you for registering at Legacy Worlds!
-Please note that you registered to "Beta 6 Milestone 1", a highly experimental, in-progress rewrite of the game. As this game does not include forums at this stage, you might want to register to Legacy Worlds Beta 5 to keep up with the game's news.
+Please note that you registered to "Beta 6 Milestone 2", a highly experimental, in-progress rewrite of the game. As this game does not include forums at this stage, you might want to register to Legacy Worlds Beta 5 to keep up with the game's news.
 
 Before we activate your account, there's just one more step to complete your registration.
 You have to connect to the site using the e-mail address and password you chose, then validate your account using the confirmation code below.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/registrationMail-fr.txt b/legacyworlds-server/legacyworlds-server-main/data/text/mail/registrationMail-fr.txt
similarity index 88%
rename from legacyworlds-server/legacyworlds-server-main/data/registrationMail-fr.txt
rename to legacyworlds-server/legacyworlds-server-main/data/text/mail/registrationMail-fr.txt
index a6bc7bf..4cd8f80 100644
--- a/legacyworlds-server/legacyworlds-server-main/data/registrationMail-fr.txt
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/mail/registrationMail-fr.txt
@@ -1,8 +1,8 @@
-Legacy Worlds (B6M1) - Votre compte
+Legacy Worlds (B6M2) - Votre compte
 Bonjour,
 
 Merci de vous être enregistré(e) sur Legacy Worlds!
-Veuillez remarquer que vous venez de créer un compte sur le jeu "Beta 6 Milestone 1", une récriture en cours et par conséquent hautement expérimentale du jeu. Comme cette version ne dispose pas de forums à l'heure actuelle, nous vous suggérons de vous enregistrer également à Legacy Worlds Beta 5 afin de vous tenir au courant des dernières nouvelles.
+Veuillez remarquer que vous venez de créer un compte sur le jeu "Beta 6 Milestone 2", une récriture en cours et par conséquent hautement expérimentale du jeu. Comme cette version ne dispose pas de forums à l'heure actuelle, nous vous suggérons de vous enregistrer également à Legacy Worlds Beta 5 afin de vous tenir au courant des dernières nouvelles.
 
 Avant que nous activions votre compte, il vous reste une dernière étape à accomplir.
 Vous allez devoir vous connecter au site en utilisant l'adresse électronique et le mot de passe que vous avez choisi, puis valider votre compte en saisissant le code de confirmation ci-dessous.
diff --git a/legacyworlds-server/legacyworlds-server-main/data/text/preferences.xml b/legacyworlds-server/legacyworlds-server-main/data/text/preferences.xml
new file mode 100644
index 0000000..ad6e43e
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/preferences.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text ../i18n-text.xsd">
+
+	<language id="en" name="English">
+
+		<inline-string id="pgDisplay">
+			<value>Display preferences</value>
+		</inline-string>
+		<inline-string id="pUseRLTime">
+			<value>Real-life time</value>
+		</inline-string>
+		<inline-string id="pUseRLTimeDescription">
+			<value>Selecting this option will cause all durations to be displayed using real-life minutes.</value>
+		</inline-string>
+		<inline-string id="pgMap">
+			<value>Map defaults</value>
+		</inline-string>
+		<inline-string id="pMapX">
+			<value>Map centre (X)</value>
+		</inline-string>
+		<inline-string id="pMapXDescription">
+			<value>The abscissa of the default map centre.</value>
+		</inline-string>
+		<inline-string id="pMapY">
+			<value>Map centre (Y)</value>
+		</inline-string>
+		<inline-string id="pMapYDescription">
+			<value>The ordinates of the default map centre.</value>
+		</inline-string>
+		<inline-string id="pMapSize">
+			<value>Map size</value>
+		</inline-string>
+		<inline-string id="pMapSizeDescription">
+			<value>The default size of the map.</value>
+		</inline-string>
+		<inline-string id="pgMail">
+			<value>E-mail settings</value>
+		</inline-string>
+		<inline-string id="pMailOnPM">
+			<value>Private messages</value>
+		</inline-string>
+		<inline-string id="pMailOnPMDescription">
+			<value>Select the type of e-mail notifications you will get for private messages sent by other empires.</value>
+		</inline-string>
+		<inline-string id="pMailOnAlliance">
+			<value>Alliance messages</value>
+		</inline-string>
+		<inline-string id="pMailOnAllianceDescription">
+			<value>Select the type of e-mail notifications you will get for alliance-wise messages.</value>
+		</inline-string>
+		<inline-string id="pMailOnIM">
+			<value>Internal messages</value>
+		</inline-string>
+		<inline-string id="pMailOnIMDescription">
+			<value>Select the type of e-mail notifications you will get for internal game messages.</value>
+		</inline-string>
+		<inline-string id="pMailOnAdmin">
+			<value>Messages from administrators</value>
+		</inline-string>
+		<inline-string id="pMailOnAdminDescription">
+			<value>Select the type of e-mail notifications you will get for messages sent by the game's administrators.</value>
+		</inline-string>
+
+	</language>
+	
+	
+	<language name="Français" id="fr">
+
+		<inline-string id="pgDisplay">
+			<value>Préférences d'affichage</value>
+		</inline-string>
+		<inline-string id="pUseRLTime">
+			<value>Temps réel</value>
+		</inline-string>
+		<inline-string id="pUseRLTimeDescription">
+			<value>Les durées seront affichées en utilisant de "vraies" mesures si cette option est sélectionnée.</value>
+		</inline-string>
+		<inline-string id="pgMap">
+			<value>Carte</value>
+		</inline-string>
+		<inline-string id="pMapX">
+			<value>Centre de la carte (X)</value>
+		</inline-string>
+		<inline-string id="pMapXDescription">
+			<value>L'abscisse par défaut du centre de la carte.</value>
+		</inline-string>
+		<inline-string id="pMapY">
+			<value>Centre de la carte (Y)</value>
+		</inline-string>
+		<inline-string id="pMapYDescription">
+			<value>L'ordonnée par défaut du centre de la carte.</value>
+		</inline-string>
+		<inline-string id="pMapSize">
+			<value>Taille de la carte</value>
+		</inline-string>
+		<inline-string id="pMapSizeDescription">
+			<value>La taille par défaut de la carte.</value>
+		</inline-string>
+		<inline-string id="pgMail">
+			<value>Envoi de courrier électronique</value>
+		</inline-string>
+		<inline-string id="pMailOnPM">
+			<value>Messages privés</value>
+		</inline-string>
+		<inline-string id="pMailOnPMDescription">
+			<value>Sélectionnez le type de notifications par courier électronique que vous recevrez lorsque d'autres empires vous envoient des messages privés.</value>
+		</inline-string>
+		<inline-string id="pMailOnAlliance">
+			<value>Messages d'alliance</value>
+		</inline-string>
+		<inline-string id="pMailOnAllianceDescription">
+			<value>Sélectionnez le type de notifications par courier électronique que vous recevrez lorsque vous recevez un message d'alliance.</value>
+		</inline-string>
+		<inline-string id="pMailOnIM">
+			<value>Messages internes</value>
+		</inline-string>
+		<inline-string id="pMailOnIMDescription">
+			<value>Sélectionnez le type de notifications par courier électronique que vous recevrez lorsque vous recevez un message interne du jeu.</value>
+		</inline-string>
+		<inline-string id="pMailOnAdmin">
+			<value>Messages des administrateurs</value>
+		</inline-string>
+		<inline-string id="pMailOnAdminDescription">
+			<value>Sélectionnez le type de notifications par courier électronique que vous recevrez lorsque vous recevez un message des administrateurs du jeu.</value>
+		</inline-string>
+
+	</language>
+
+</lw-text-data>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/data/text/technologies.xml b/legacyworlds-server/legacyworlds-server-main/data/text/technologies.xml
new file mode 100644
index 0000000..6285c28
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/data/text/technologies.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lw-text-data xmlns="http://www.deepclone.com/lw/b6/m1/i18n-text" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.deepclone.com/lw/b6/m1/i18n-text ../i18n-text.xsd">
+
+	<language id="en" name="English">
+
+		<inline-string id="civTech">
+			<value>Civilian technologies</value>
+		</inline-string>
+		<inline-string id="civTechDescription">
+			<value>They're not just your slaves, they're your scientists, poets and workers. Make the poets work first, then use these technologies to increase their productivity! Useless poets.</value>
+		</inline-string>
+
+		<inline-string id="indFactTech">
+			<value>Universal assemblers</value>
+		</inline-string>
+		<inline-string id="indFactTechDescription">
+			<value>You know how it is when you have a thingy-bob that you need to build but you just don't have the right tool. Well, fear no more, Universal Assemblers will solve all your problems! Build anything and everything with these clever machines, they are 1337.</value>
+		</inline-string>
+		<inline-string id="reanimationTech">
+			<value>Corpse reanimation</value>
+		</inline-string>
+		<inline-string id="reanimationTechDescription">
+			<value>Tired of workers dying too early? Want a little less perspiration in your corporation? Zombies will work, won't complain and best of all, they can be fed on almost anything! Feed them your enemies! But mostly, use them to increase factory productivity by a rather nice amount.</value>
+		</inline-string>
+		<inline-string id="superTurretTech">
+			<value>Biological generators</value>
+		</inline-string>
+		<inline-string id="superTurretTechDescription">
+			<value>For every turret a military commander wants, there is a bigger, stronger turret he wants more. Now you too can have such a turret! It will defend your planet with ease while you venture out into the galaxy, bending it to your will. Available in all good hardware stores.</value>
+		</inline-string>
+
+		<inline-string id="milTech">
+			<value>Military technologies</value>
+		</inline-string>
+		<inline-string id="milTechDescription">
+			<value>It is only one who is thoroughly acquainted with the evils of war that can thoroughly understand the profitable way of carrying it on. - Sun Tzu</value>
+		</inline-string>
+
+		<inline-string id="cruisersTech">
+			<value>Orbital construction</value>
+		</inline-string>
+		<inline-string id="cruisersTechDescription">
+			<value>Ships built on the ground must endure the stress of atmospheric flight before they are even able to dominate the vast emptiness of space. Build them in space and they will be sleeker, more powerful and now free of the need for windows. Use your brand new Cruisers to dominate the known universe.</value>
+		</inline-string>
+		<inline-string id="bCruisersTech">
+			<value>Structural reinforcement</value>
+		</inline-string>
+		<inline-string id="bCruisersTechDescription">
+			<value>The power of your Cruisers can be augmented by an improved design! Take advantage of a more structurally sound space vehicle that can bring empires to their knees with its speed and technological grace.</value>
+		</inline-string>
+		<inline-string id="dreadnoughtsTech">
+			<value>Automated space docks</value>
+		</inline-string>
+		<inline-string id="dreadnoughtsTechDescription">
+			<value>Technology has advanced. The ultimate weapon is now available. Claim the awesome power of the Dreadnought and crush your enemies. Ever wanted a tank in space? Well, now you have it. All their base are belong to you.</value>
+		</inline-string>
+
+	</language>
+	
+	
+	<language name="Français" id="fr">
+
+		<inline-string id="civTech">
+			<value>Technologies civiles</value>
+		</inline-string>
+		<inline-string id="civTechDescription">
+			<value>Ce ne sont pas uniquement vos esclaves, ce sont vos chercheurs, poètes et ouvriers. Faites travailler d'abord les poètes, puis utilisez ces technologies pour augmenter leur productivité! Poètes inutiles...</value>
+		</inline-string>
+
+		<inline-string id="indFactTech">
+			<value>Assembleurs universels</value>
+		</inline-string>
+		<inline-string id="indFactTechDescription">
+			<value>Vous savez ce que c'est d'avoir un truc à construire, alors que malheureusement vous ne diposez pas du bon outil. Eh bien, plus de peur : les Assembleurs Universels vont résoudre tous vos problèmes! Construisez tout et n'importe quoi avec ces machines intelligentes, elles sont 1337.</value>
+		</inline-string>
+		<inline-string id="reanimationTech">
+			<value>Réanimation de cadavres</value>
+		</inline-string>
+		<inline-string id="reanimationTechDescription">
+			<value>Fatigué de ces ouvriers qui passent l'arme à gauche trop tôt? Vous voulez un peu plus de transpiration dans vos ateliers ? Les zombies vont travailler, ne vont pas se plaindre, et encore mieux ils peuvent être nourris avec n'importe quoi - y compris avec vos énemis! Mais surtout, utilisez les pour augmenter de manière significative la productivité de vos usines.</value>
+		</inline-string>
+		<inline-string id="superTurretTech">
+			<value>Générateurs biologiques</value>
+		</inline-string>
+		<inline-string id="superTurretTechDescription">
+			<value>Pour chaque tourelle qu'un commandant militaire réclame, il en est une qu'il désire encore plus. Maintenant, vous aussi pouvez avoir de telles tourelles! Elles défendront vos planètes avec aisance, pendant que vous vous aventurerez dans la galaxie, la pliant à votre volonté. Disponible chez tous les bons quincaillers.</value>
+		</inline-string>
+
+		<inline-string id="milTech">
+			<value>Technologies militaires</value>
+		</inline-string>
+		<inline-string id="milTechDescription">
+			<value>Ceux qui ne comprennent pas les dommages que la guerre peut causer n'en comprendront jamais les avantages. - Sun Tzu</value>
+		</inline-string>
+
+		<inline-string id="cruisersTech">
+			<value>Construction orbitale</value>
+		</inline-string>
+		<inline-string id="cruisersTechDescription">
+			<value>Les vaisseaux construits à la surface doivent subir le stress du vol atmosphérique avant même d'être lancés à assaut du grand vide interstellaire. Construisez-les dans l'espace et ils seront plus gracieux, plus puissants, et ne nécessiteront plus de fenêtres. Utilisez vos tout nouveaux croiseurs pour dominer l'univers connu.</value>
+		</inline-string>
+		<inline-string id="bCruisersTech">
+			<value>Consolidation structurelle</value>
+		</inline-string>
+		<inline-string id="bCruisersTechDescription">
+			<value>La puissance de vos croiseurs peut être augmentée par une conception améliorée! Profitez d'un véhicule à la structure mieux adaptée à l'espace qui peut mettre des empires à genoux grâce à sa vitesse et sa finesse technologique.</value>
+		</inline-string>
+		<inline-string id="dreadnoughtsTech">
+			<value>Docks orbitaux automatisés</value>
+		</inline-string>
+		<inline-string id="dreadnoughtsTechDescription">
+			<value>La technologie a évolué. L'arme ultime est maintenant disponible. Revendiquez la puissance écrasante du cuirassé et pulvérisez vos opposants. Déjà rêvé d'un tank de l'espace ? Eh bien, maintenant, vous l'avez. All their base are belong to you.</value>
+		</inline-string>
+
+	</language>
+
+</lw-text-data>
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/hibernate.xml b/legacyworlds-server/legacyworlds-server-main/hibernate.xml
deleted file mode 100644
index 03001ba..0000000
--- a/legacyworlds-server/legacyworlds-server-main/hibernate.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-	<!-- Legacy Worlds Beta 6 - Default data source configuration -->
-	<!--
-		This file is provided as an example on how to configure the game
-		server's data sources. It configures a memory-based HSQL database,
-		initialising its schemas and structure.
-	-->
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="
-                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-	<bean id="hibernateProperties"
-		class="org.springframework.beans.factory.config.PropertiesFactoryBean">
-		<property name="properties">
-			<props>
-				<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
-				<prop key="hibernate.jdbc.batch_size">0</prop> 
-				<prop key="hibernate.show_sql">true</prop>
-				<prop key="hibernate.format_sql">true</prop><!-- -->
-			</props>
-		</property>
-	</bean>
-
-</beans>
diff --git a/legacyworlds-server/legacyworlds-server-main/pom.xml b/legacyworlds-server/legacyworlds-server-main/pom.xml
index 6774468..fd89151 100644
--- a/legacyworlds-server/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-main/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-main</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds server</name>
 	<description>Server main classes and JAR builder.</description>
 
@@ -29,6 +29,11 @@
 			<groupId>com.deepclone.lw</groupId>
 			<version>${project.version}</version>
 		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-beans-events</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+			<version>${project.version}</version>
+		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-i18n</artifactId>
 			<groupId>com.deepclone.lw</groupId>
@@ -54,6 +59,16 @@
 			<groupId>com.deepclone.lw</groupId>
 			<version>${project.version}</version>
 		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-beans-techs</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<artifactId>legacyworlds-server-beans-updates</artifactId>
+			<groupId>com.deepclone.lw</groupId>
+			<version>${project.version}</version>
+		</dependency>
 		<dependency>
 			<artifactId>legacyworlds-server-beans-user</artifactId>
 			<groupId>com.deepclone.lw</groupId>
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
index ab27ed1..863cd28 100644
--- a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportBuildables.java
@@ -52,7 +52,7 @@ public class ImportBuildables
 
 		public CostData cost;
 
-		public TechData tech;
+		public String tech;
 	}
 
 	@SuppressWarnings( "serial" )
@@ -70,18 +70,6 @@ public class ImportBuildables
 		public int work;
 	}
 
-	@SuppressWarnings( "serial" )
-	@XStreamAlias( "tech" )
-	public static class TechData
-			implements Serializable
-	{
-		@XStreamAsAttribute
-		public String name;
-
-		@XStreamAsAttribute
-		public int level;
-	}
-
 	@SuppressWarnings( "serial" )
 	@XStreamAlias( "building" )
 	public static class BuildingData
@@ -196,7 +184,6 @@ public class ImportBuildables
 		this.uocBuildingDep.addParameter( "output_type" , "building_output_type" );
 		this.uocBuildingDep.addParameter( "output" , Types.INTEGER );
 		this.uocBuildingDep.addParameter( "dep_name" , Types.VARCHAR );
-		this.uocBuildingDep.addParameter( "dep_level" , Types.INTEGER );
 
 		this.uocShipNoDep = new StoredProc( dataSource , "tech" , "uoc_ship" );
 		this.uocShipNoDep.addParameter( "name" , Types.VARCHAR );
@@ -216,7 +203,6 @@ public class ImportBuildables
 		this.uocShipDep.addParameter( "power" , Types.INTEGER );
 		this.uocShipDep.addParameter( "flight_time" , Types.INTEGER );
 		this.uocShipDep.addParameter( "dep_name" , Types.VARCHAR );
-		this.uocShipDep.addParameter( "dep_level" , Types.INTEGER );
 	}
 
 
@@ -241,7 +227,7 @@ public class ImportBuildables
 					ship.cost.upkeep , ship.power , ship.time );
 		} else {
 			this.uocShipDep.execute( ship.name , ship.description , ship.cost.build , ship.cost.work ,
-					ship.cost.upkeep , ship.power , ship.time , ship.tech.name , ship.tech.level );
+					ship.cost.upkeep , ship.power , ship.time , ship.tech );
 		}
 	}
 
@@ -256,7 +242,7 @@ public class ImportBuildables
 		} else {
 			this.uocBuildingDep.execute( building.name , building.description , building.cost.build ,
 					building.cost.work , building.cost.upkeep , building.workers , building.type.toString( ) ,
-					building.output , building.tech.name , building.tech.level );
+					building.output , building.tech );
 		}
 	}
 
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
index cd15b30..a6b944e 100644
--- a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportTechs.java
@@ -31,17 +31,26 @@ public class ImportTechs
 
 	private final Logger logger = Logger.getLogger( ImportTechs.class );
 
+	private static class ImportError
+			extends RuntimeException
+	{
+		private static final long serialVersionUID = 1L;
+	}
+
 	@XStreamAlias( "technologies" )
 	@SuppressWarnings( "serial" )
-	public static class Techs
+	public static class Technologies
 			implements Serializable
 	{
-		@XStreamImplicit( itemFieldName = "tech-line" )
-		public List< TechLine > lines;
+		@XStreamImplicit( itemFieldName = "category" )
+		public List< Category > categories;
+
+		@XStreamImplicit( itemFieldName = "technology" )
+		public List< Technology > technologies;
 	}
 
 	@SuppressWarnings( "serial" )
-	public static class TechLine
+	public static class Category
 			implements Serializable
 	{
 		@XStreamAsAttribute
@@ -49,18 +58,18 @@ public class ImportTechs
 
 		@XStreamAsAttribute
 		public String description;
-
-		@XStreamImplicit( itemFieldName = "level" )
-		public List< TechLevel > levels;
 	}
 
 	@SuppressWarnings( "serial" )
-	public static class TechLevel
+	public static class Technology
 			implements Serializable
 	{
 		@XStreamAsAttribute
 		public String name;
 
+		@XStreamAsAttribute
+		public String category;
+
 		@XStreamAsAttribute
 		public String description;
 
@@ -69,23 +78,57 @@ public class ImportTechs
 
 		@XStreamAsAttribute
 		public int cost;
+
+		@XStreamImplicit( itemFieldName = "depends" )
+		public List< Dependency > dependencies;
 	}
 
+	@SuppressWarnings( "serial" )
+	public static class Dependency
+			implements Serializable
+	{
+		@XStreamAlias( "on" )
+		@XStreamAsAttribute
+		public String dependsOn;
+	}
+
+	/** The file from which technology definitions will be loaded */
 	private File file;
+
+	/** The transaction template used to execute the import transaction */
 	private TransactionTemplate tTemplate;
-	private StoredProc uocLine;
-	private StoredProc uocLevel;
+
+	/** Wrapper for the stored procedure which updates or creates technology categories */
+	private StoredProc uocCategory;
+
+	/** Wrapper for the stored procedure which updates or creates technology definitions */
+	private StoredProc uocTechnology;
+
+	/** Wrapper for the stored procedure which adds a dependency to a technology */
+	private StoredProc addDependency;
 
 
+	/**
+	 * Initialise an XStream instance using the annotations on the {@link Technologies} class and
+	 * its various component classes.
+	 * 
+	 * @return the initialised XStream instance
+	 */
 	private XStream initXStream( )
 	{
 		XStream xstream = new XStream( );
-		xstream.processAnnotations( Techs.class );
+		xstream.processAnnotations( Technologies.class );
 		return xstream;
 	}
 
 
-	private Techs loadData( )
+	/**
+	 * Load technology definitions from an XML data file, deserialising it as a {@link Technologies}
+	 * instance through XStream.
+	 * 
+	 * @return the technology definition instance or <code>null</code> if an error occurs.
+	 */
+	private Technologies loadData( )
 	{
 		FileInputStream fis;
 		try {
@@ -96,7 +139,7 @@ public class ImportTechs
 
 		try {
 			XStream xstream = this.initXStream( );
-			return (Techs) xstream.fromXML( fis );
+			return (Technologies) xstream.fromXML( fis );
 		} catch ( Exception e ) {
 			e.printStackTrace( );
 			return null;
@@ -110,9 +153,15 @@ public class ImportTechs
 	}
 
 
+	/**
+	 * Create a basic Spring context containing the components that may be used to connect to the
+	 * database.
+	 * 
+	 * @return the initialised Spring context
+	 */
 	private ClassPathXmlApplicationContext createContext( )
 	{
-		// Load data source and Hibernate properties
+		// Load data source properties
 		String[] dataConfig = {
 			this.getDataSource( )
 		};
@@ -127,44 +176,159 @@ public class ImportTechs
 	}
 
 
+	/**
+	 * Create the {@link #tTemplate} transaction template from the transaction manager in the
+	 * context. In addition, initialise stored procedure wrappers {@link #uocCategory},
+	 * {@link #uocTechnology}.
+	 * 
+	 * @param ctx
+	 *            the Spring context
+	 */
 	private void getBeans( ApplicationContext ctx )
 	{
 		PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
 		this.tTemplate = new TransactionTemplate( tManager );
 
 		DataSource dataSource = ctx.getBean( DataSource.class );
-		this.uocLine = new StoredProc( dataSource , "tech" , "uoc_line" );
-		this.uocLine.addParameter( "tln" , Types.VARCHAR );
-		this.uocLine.addParameter( "tld" , Types.VARCHAR );
 
-		this.uocLevel = new StoredProc( dataSource , "tech" , "uoc_level" );
-		this.uocLevel.addParameter( "tech_line" , Types.VARCHAR );
-		this.uocLevel.addParameter( "level" , Types.INTEGER );
-		this.uocLevel.addParameter( "name" , Types.VARCHAR );
-		this.uocLevel.addParameter( "desc" , Types.VARCHAR );
-		this.uocLevel.addParameter( "points" , Types.INTEGER );
-		this.uocLevel.addParameter( "cost" , Types.INTEGER );
+		this.uocCategory = new StoredProc( dataSource , "tech" , "uoc_category" );
+		this.uocCategory.addOutput( "rv" , Types.INTEGER );
+		this.uocCategory.addParameter( "cat_name" , Types.VARCHAR );
+		this.uocCategory.addParameter( "cat_desc" , Types.VARCHAR );
+
+		this.uocTechnology = new StoredProc( dataSource , "tech" , "uoc_technology" );
+		this.uocTechnology.addOutput( "rv" , Types.INTEGER );
+		this.uocTechnology.addParameter( "nt_name" , Types.VARCHAR );
+		this.uocTechnology.addParameter( "nt_cat" , Types.VARCHAR );
+		this.uocTechnology.addParameter( "nt_desc" , Types.VARCHAR );
+		this.uocTechnology.addParameter( "nt_points" , Types.INTEGER );
+		this.uocTechnology.addParameter( "nt_cost" , Types.INTEGER );
+
+		this.addDependency = new StoredProc( dataSource , "tech" , "add_dependency" );
+		this.addDependency.addOutput( "rv" , Types.INTEGER );
+		this.addDependency.addParameter( "nd_name" , Types.VARCHAR );
+		this.addDependency.addParameter( "nd_dep" , Types.VARCHAR );
 	}
 
 
-	private void importTechnologies( Techs data )
+	/**
+	 * Import the technology data stored as a {@link Technologies} instance.
+	 * 
+	 * @param data
+	 *            the technology categories and technology definitions to import
+	 * 
+	 * @throws ImportError
+	 *             if the definitions are erroneous
+	 */
+	private void importTechnologies( Technologies data )
 	{
-		for ( TechLine line : data.lines ) {
-			this.uocLine.execute( line.name , line.description );
+		this.importCategories( data.categories );
+		for ( Technology tech : data.technologies ) {
+			this.importTechnology( tech );
+		}
+	}
 
-			int i = 1;
-			for ( TechLevel level : line.levels ) {
-				this.uocLevel.execute( line.name , i , level.name , level.description , level.points , level.cost );
-				i++;
+
+	/**
+	 * Import a single technology definition, along with its dependencies.
+	 * 
+	 * @param technology
+	 *            the technology definition to import
+	 * 
+	 * @throws ImportError
+	 *             if the definition is incorrect
+	 */
+	private void importTechnology( Technology technology )
+	{
+		this.logger.debug( "Importing technology " + technology.name );
+
+		int result = (Integer) this.uocTechnology.execute( technology.name , technology.category ,
+				technology.description , technology.points , technology.cost ).get( "rv" );
+		switch ( result ) {
+			case 0:
+				break;
+			case 1:
+				this.logger.error( "Technology " + technology.name + ": name string not found" );
+				throw new ImportError( );
+			case 2:
+				this.logger.error( "Technology " + technology.name + ": category '" + technology.category
+						+ "' not found" );
+				throw new ImportError( );
+			case 3:
+				this.logger.error( "Technology " + technology.name + ": description string '" + technology.description
+						+ "' not found" );
+				throw new ImportError( );
+			case 4:
+				this.logger.error( "Technology " + technology.name + ": invalid points and/or cost" );
+				throw new ImportError( );
+		}
+
+		if ( technology.dependencies == null ) {
+			return;
+		}
+
+		for ( Dependency dep : technology.dependencies ) {
+			result = (Integer) this.addDependency.execute( technology.name , dep.dependsOn ).get( "rv" );
+			switch ( result ) {
+				case 0:
+					break;
+				case 1:
+					this.logger.error( "Technology " + technology.name + ": not found while adding dependency" );
+					throw new ImportError( );
+				case 2:
+					this.logger.error( "Technology " + technology.name + ": dependency '" + dep.dependsOn
+							+ "' not found" );
+					throw new ImportError( );
+				case 3:
+					this.logger.error( "Technology " + technology.name + ": duplicate dependency '" + dep.dependsOn
+							+ "'" );
+					throw new ImportError( );
 			}
 		}
 	}
 
 
+	/**
+	 * Import the list of categories into the database using the <em>tech.uoc_category</em> stored
+	 * procedure.
+	 * 
+	 * @param categories
+	 *            the list of categories
+	 * 
+	 * @throws ImportError
+	 *             if the stored procedure returns a failure code
+	 */
+	private void importCategories( List< Category > categories )
+	{
+		for ( Category category : categories ) {
+			this.logger.debug( "Importing category " + category.name );
+
+			int result = (Integer) this.uocCategory.execute( category.name , category.description ).get( "rv" );
+			switch ( result ) {
+				case 0:
+					break;
+				case 1:
+					this.logger.error( "Category " + category.name + ": name string not found" );
+					throw new ImportError( );
+				case 2:
+					this.logger.error( "Category " + category.name + ": description string '" + category.description
+							+ "' not found" );
+					throw new ImportError( );
+			}
+		}
+	}
+
+
+	/**
+	 * Load the technologies and related data from the file specified on the command line, then
+	 * start a database transaction during which categories, technologies and dependencies will be
+	 * created. If anything should go wrong during the import, rollback the transaction.
+	 * 
+	 */
 	@Override
 	public void run( )
 	{
-		final Techs data = this.loadData( );
+		final Technologies data = this.loadData( );
 		if ( data == null ) {
 			System.err.println( "could not read data" );
 			return;
@@ -181,8 +345,10 @@ public class ImportTechs
 				try {
 					importTechnologies( data );
 					rv = true;
+				} catch ( ImportError e ) {
+					rv = false;
 				} catch ( RuntimeException e ) {
-					logger.error( e.getMessage( ) );
+					logger.error( "error during import" , e );
 					rv = false;
 				}
 				if ( !rv ) {
@@ -202,6 +368,15 @@ public class ImportTechs
 	}
 
 
+	/**
+	 * Make sure that the command-line parameters of this tool consist in a single file name, that
+	 * the file exists and is readable, setting {@link #file} accordingly.
+	 * 
+	 * @param options
+	 *            the array of command line parameters
+	 * 
+	 * @return <code>true</code> if the parameters are ok, <code>false</code> otherwise.
+	 */
 	@Override
 	public boolean setOptions( String... options )
 	{
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
index c3952ed..c395263 100644
--- a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportText.java
@@ -2,8 +2,7 @@ package com.deepclone.lw.cli;
 
 
 import java.io.*;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.Map;
 
 import javax.sql.DataSource;
 
@@ -19,10 +18,8 @@ import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallback;
 import org.springframework.transaction.support.TransactionTemplate;
 
-import com.thoughtworks.xstream.XStream;
-import com.thoughtworks.xstream.annotations.XStreamAlias;
-import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
-import com.thoughtworks.xstream.annotations.XStreamImplicit;
+import com.deepclone.lw.cli.i18n.LoadableText;
+import com.deepclone.lw.cli.i18n.Loader;
 
 
 
@@ -32,125 +29,12 @@ public class ImportText
 
 	private final Logger logger = Logger.getLogger( ImportText.class );
 
-	@SuppressWarnings( "serial" )
-	public abstract static class StringData
-			implements Serializable
-	{
-		@XStreamAsAttribute
-		public String id;
-
-
-		public abstract String getString( );
-	}
-
-	@SuppressWarnings( "serial" )
-	@XStreamAlias( "inline-string" )
-	public static class InlineString
-			extends StringData
-	{
-		public String value;
-
-
-		@Override
-		public String getString( )
-		{
-			return this.value;
-		}
-	}
-
-	@SuppressWarnings( "serial" )
-	@XStreamAlias( "from-file" )
-	public static class FileString
-			extends StringData
-	{
-		@XStreamAsAttribute
-		public String source;
-
-
-		@Override
-		public String getString( )
-		{
-			StringBuilder sBuilder = new StringBuilder( );
-			try {
-				BufferedReader in = new BufferedReader( new FileReader( source ) );
-				String str;
-				while ( ( str = in.readLine( ) ) != null ) {
-					sBuilder.append( str );
-					sBuilder.append( "\n" );
-				}
-				in.close( );
-			} catch ( IOException e ) {
-				throw new RuntimeException( "Could not read " + source );
-			}
-
-			return sBuilder.toString( );
-		}
-	}
-
-	@SuppressWarnings( "serial" )
-	public static class LanguageData
-			implements Serializable
-	{
-		@XStreamAsAttribute
-		public String id;
-
-		@XStreamAsAttribute
-		public String name;
-
-		@XStreamImplicit
-		public List< StringData > strings = new LinkedList< StringData >( );
-	}
-
-	@SuppressWarnings( "serial" )
-	@XStreamAlias( "lw-text-data" )
-	public static class TextData
-			implements Serializable
-	{
-		@XStreamImplicit( itemFieldName = "language" )
-		public List< LanguageData > languages = new LinkedList< LanguageData >( );
-	}
-
 	private File file;
 	private TransactionTemplate tTemplate;
 	private SimpleJdbcCall uocTranslation;
 	private SimpleJdbcCall uocLanguage;
 
 
-	private XStream initXStream( )
-	{
-		XStream xstream = new XStream( );
-		xstream.processAnnotations( TextData.class );
-		xstream.processAnnotations( InlineString.class );
-		xstream.processAnnotations( FileString.class );
-		return xstream;
-	}
-
-
-	private TextData loadData( )
-	{
-		FileInputStream fis;
-		try {
-			fis = new FileInputStream( this.file );
-		} catch ( FileNotFoundException e ) {
-			return null;
-		}
-
-		try {
-			XStream xstream = this.initXStream( );
-			return (TextData) xstream.fromXML( fis );
-		} catch ( Exception e ) {
-			e.printStackTrace( );
-			return null;
-		} finally {
-			try {
-				fis.close( );
-			} catch ( IOException e ) {
-				// EMPTY
-			}
-		}
-	}
-
-
 	private ClassPathXmlApplicationContext createContext( )
 	{
 		// Load data source and Hibernate properties
@@ -188,26 +72,23 @@ public class ImportText
 	}
 
 
-	private void importText( TextData data )
+	private void importText( LoadableText data )
 	{
-		for ( LanguageData ld : data.languages ) {
-			this.importLanguage( ld );
+		for ( String lId : data.getLanguages( ) ) {
+			this.importLanguage( data , lId );
 		}
 	}
 
 
-	private void importLanguage( LanguageData ld )
+	private void importLanguage( LoadableText data , String lId )
 	{
-		if ( ld.strings == null ) {
-			return;
-		}
-
 		// Try creating or updating the language
-		this.uocLanguage.execute( ld.id , ld.name );
+		this.uocLanguage.execute( lId , data.getLanguageName( lId ) );
 
 		// Import translations
-		for ( StringData sd : ld.strings ) {
-			this.uocTranslation.execute( ld.id , sd.id , sd.getString( ) );
+		for ( Map.Entry< String , String > string : data.getStrings( lId ) ) {
+			System.out.println( "Language " + lId + " string " + string.getKey( ) );
+			this.uocTranslation.execute( lId , string.getKey( ) , string.getValue( ) );
 		}
 	}
 
@@ -215,11 +96,8 @@ public class ImportText
 	@Override
 	public void run( )
 	{
-		final TextData data = this.loadData( );
-		if ( data == null ) {
-			System.err.println( "could not read data" );
-			return;
-		}
+		Loader textLoader = new Loader( this.file );
+		final LoadableText data = textLoader.load( );
 
 		AbstractApplicationContext ctx = this.createContext( );
 		this.createTemplates( ctx );
@@ -245,7 +123,7 @@ public class ImportText
 		} );
 
 		if ( rv ) {
-			this.logger.info( "Text import successful" );
+			System.out.println( "Text import successful" );
 		}
 
 		ToolBase.destroyContext( ctx );
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/FileString.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/FileString.java
new file mode 100644
index 0000000..d75eb08
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/FileString.java
@@ -0,0 +1,54 @@
+/**
+ * 
+ */
+package com.deepclone.lw.cli.i18n;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+@SuppressWarnings( "serial" )
+@XStreamAlias( "from-file" )
+public class FileString
+		extends StringData
+{
+	private File sourceFile;
+
+	@XStreamAsAttribute
+	public String source;
+
+
+	@Override
+	public String getString( )
+	{
+		StringBuilder sBuilder = new StringBuilder( );
+		System.out.println( "Loading text from " + this.sourceFile.getAbsolutePath( ) );
+		try {
+			BufferedReader in = new BufferedReader( new FileReader( this.sourceFile ) );
+			String str;
+			while ( ( str = in.readLine( ) ) != null ) {
+				sBuilder.append( str );
+				sBuilder.append( "\n" );
+			}
+			in.close( );
+		} catch ( IOException e ) {
+			throw new RuntimeException( "Could not read " + this.sourceFile.getAbsolutePath( ) );
+		}
+
+		return sBuilder.toString( );
+	}
+
+
+	@Override
+	public void setLoader( Loader loader )
+	{
+		this.sourceFile = new File( loader.getDirectory( ) , this.source );
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/InlineString.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/InlineString.java
new file mode 100644
index 0000000..4114589
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/InlineString.java
@@ -0,0 +1,28 @@
+/**
+ * 
+ */
+package com.deepclone.lw.cli.i18n;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+@SuppressWarnings( "serial" )
+@XStreamAlias( "inline-string" )
+public class InlineString
+		extends StringData
+{
+	public String value;
+
+
+	@Override
+	public String getString( )
+	{
+		return this.value;
+	}
+
+
+	@Override
+	public void setLoader( Loader loader )
+	{
+		// EMPTY
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/LanguageData.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/LanguageData.java
new file mode 100644
index 0000000..f199bbe
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/LanguageData.java
@@ -0,0 +1,36 @@
+/**
+ * 
+ */
+package com.deepclone.lw.cli.i18n;
+
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+@SuppressWarnings( "serial" )
+public class LanguageData
+		implements Serializable
+{
+	@XStreamAsAttribute
+	public String id;
+
+	@XStreamAsAttribute
+	public String name;
+
+	@XStreamImplicit
+	public List< StringData > strings = new LinkedList< StringData >( );
+
+
+	public void setLoader( Loader loader )
+	{
+		for ( StringData sd : this.strings ) {
+			sd.setLoader( loader );
+		}
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/LoadableText.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/LoadableText.java
new file mode 100644
index 0000000..96655d3
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/LoadableText.java
@@ -0,0 +1,73 @@
+package com.deepclone.lw.cli.i18n;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+
+
+public class LoadableText
+{
+
+	private final Map< String , String > languageNames;
+	private final Map< String , Map< String , String >> text;
+
+
+	public LoadableText( TextData data )
+	{
+		this.languageNames = new HashMap< String , String >( );
+		this.text = new HashMap< String , Map< String , String > >( );
+
+		if ( data.languages == null ) {
+			return;
+		}
+
+		for ( LanguageData language : data.languages ) {
+			this.languageNames.put( language.id , language.name );
+			Map< String , String > strings = new HashMap< String , String >( );
+			this.text.put( language.id , strings );
+
+			for ( StringData string : language.strings ) {
+				strings.put( string.id , string.getString( ) );
+			}
+		}
+	}
+
+
+	public void merge( LoadableText text )
+	{
+		for ( Map.Entry< String , String > entry : text.languageNames.entrySet( ) ) {
+			Map< String , String > lStrings = this.text.get( entry.getKey( ) );
+			if ( lStrings == null ) {
+				this.languageNames.put( entry.getKey( ) , entry.getValue( ) );
+				lStrings = new HashMap< String , String >( );
+				this.text.put( entry.getKey( ) , lStrings );
+			}
+
+			for ( Map.Entry< String , String > string : text.text.get( entry.getKey( ) ).entrySet( ) ) {
+				if ( lStrings.put( string.getKey( ) , string.getValue( ) ) != null ) {
+					throw new RuntimeException( "String '" + string.getKey( ) + "' defined more than once" );
+				}
+			}
+		}
+	}
+
+
+	public Set< String > getLanguages( )
+	{
+		return this.languageNames.keySet( );
+	}
+
+
+	public String getLanguageName( String lId )
+	{
+		return this.languageNames.get( lId );
+	}
+
+
+	public Set< Map.Entry< String , String >> getStrings( String lId )
+	{
+		return this.text.get( lId ).entrySet( );
+	}
+}
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/Loader.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/Loader.java
new file mode 100644
index 0000000..f00f41e
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/Loader.java
@@ -0,0 +1,99 @@
+package com.deepclone.lw.cli.i18n;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.thoughtworks.xstream.XStream;
+
+
+
+public class Loader
+{
+
+	private final Set< String > included;
+	private final XStream xstream;
+	private final File file;
+	private final String directory;
+
+
+	public Loader( File file )
+	{
+		this.included = new HashSet< String >( );
+		this.xstream = new XStream( );
+		this.xstream.processAnnotations( TextData.class );
+		this.xstream.processAnnotations( InlineString.class );
+		this.xstream.processAnnotations( FileString.class );
+		this.file = file;
+		this.directory = this.file.getAbsoluteFile( ).getParent( );
+	}
+
+
+	public Loader( Loader parent , String included )
+	{
+		this.included = parent.included;
+		this.xstream = parent.xstream;
+		if ( included.charAt( 0 ) == '/' ) {
+			this.file = new File( included );
+		} else {
+			this.file = new File( parent.directory , included );
+		}
+		this.directory = this.file.getAbsoluteFile( ).getParent( );
+	}
+
+
+	public LoadableText load( )
+	{
+		System.out.println( "Loading data file " + this.file.getAbsolutePath( ) );
+
+		TextData data = this.loadFile( );
+		data.setLoader( this );
+
+		LoadableText result = new LoadableText( data );
+		if ( data.includes != null ) {
+			for ( String file : data.includes ) {
+				Loader child = new Loader( this , file );
+				if ( this.included.contains( child.file.getAbsolutePath( ) ) ) {
+					continue;
+				}
+				this.included.add( child.file.getAbsolutePath( ) );
+				result.merge( child.load( ) );
+			}
+		}
+
+		return result;
+	}
+
+
+	private TextData loadFile( )
+	{
+		FileInputStream fis;
+		try {
+			fis = new FileInputStream( this.file );
+		} catch ( FileNotFoundException e ) {
+			throw new RuntimeException( e );
+		}
+
+		try {
+			return (TextData) this.xstream.fromXML( fis );
+		} catch ( Exception e ) {
+			throw new RuntimeException( e );
+		} finally {
+			try {
+				fis.close( );
+			} catch ( IOException e ) {
+				// EMPTY
+			}
+		}
+	}
+
+
+	public String getDirectory( )
+	{
+		return this.directory;
+	}
+}
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/StringData.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/StringData.java
new file mode 100644
index 0000000..0dac4a5
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/StringData.java
@@ -0,0 +1,25 @@
+/**
+ * 
+ */
+package com.deepclone.lw.cli.i18n;
+
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+
+
+@SuppressWarnings( "serial" )
+public abstract class StringData
+		implements Serializable
+{
+	@XStreamAsAttribute
+	public String id;
+
+
+	public abstract String getString( );
+
+
+	public abstract void setLoader( Loader loader );
+}
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/TextData.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/TextData.java
new file mode 100644
index 0000000..3a93efc
--- /dev/null
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/i18n/TextData.java
@@ -0,0 +1,34 @@
+/**
+ * 
+ */
+package com.deepclone.lw.cli.i18n;
+
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+
+
+@SuppressWarnings( "serial" )
+@XStreamAlias( "lw-text-data" )
+public class TextData
+		implements Serializable
+{
+	@XStreamImplicit( itemFieldName = "include" )
+	public List< String > includes = new LinkedList< String >( );
+
+	@XStreamImplicit( itemFieldName = "language" )
+	public List< LanguageData > languages = new LinkedList< LanguageData >( );
+
+
+	public void setLoader( Loader loader )
+	{
+		for ( LanguageData l : this.languages ) {
+			l.setLoader( loader );
+		}
+	}
+}
\ No newline at end of file
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/Server.java b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/Server.java
index f6a41e0..7f91446 100644
--- a/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/Server.java
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/java/com/deepclone/lw/srv/Server.java
@@ -38,9 +38,10 @@ public class Server
 		builder.setScope( BeanDefinition.SCOPE_SINGLETON );
 		builder.addPropertyValue( "serviceName" , name );
 		builder.addPropertyValue( "service" , bean );
-		builder.addPropertyValue( "ServiceInterface" , iface.getCanonicalName( ) );
+		builder.addPropertyValue( "serviceInterface" , iface.getCanonicalName( ) );
 		builder.addPropertyValue( "registryPort" , String.valueOf( this.getRmiPort( ) ) );
 		builder.addPropertyValue( "servicePort" , String.valueOf( sPort ) );
+		builder.addPropertyValue( "registryHost" , "localhost" );
 		ctx.registerBeanDefinition( name , builder.getBeanDefinition( ) );
 	}
 
@@ -48,17 +49,31 @@ public class Server
 	private AbstractApplicationContext makeRMIContext( ApplicationContext parent )
 	{
 		GenericApplicationContext context = new GenericApplicationContext( parent );
-		this.addRMIService( context , "termSrv" , parent.getBean( "terminator" ) , ServerTerminator.class , this.getTerminationPort( ) );
-		this.addRMIService( context , "sessionSrv" , parent.getBean( "sessionManager" ) , SessionManager.class , this.getServicePort( ) );
+		this.addRMIRegistry( context );
+		this.addRMIService( context , "termSrv" , parent.getBean( "terminator" ) , ServerTerminator.class , this
+				.getTerminationPort( ) );
+		this.addRMIService( context , "sessionSrv" , parent.getBean( "sessionManager" ) , SessionManager.class , this
+				.getServicePort( ) );
 		context.refresh( );
 		return context;
 	}
 
 
+	private void addRMIRegistry( GenericApplicationContext context )
+	{
+		BeanDefinitionBuilder builder;
+		builder = BeanDefinitionBuilder.rootBeanDefinition( "org.springframework.remoting.rmi.RmiRegistryFactoryBean" );
+		builder.setScope( BeanDefinition.SCOPE_SINGLETON );
+		builder.addPropertyValue( "port" , String.valueOf( this.getRmiPort( ) ) );
+		builder.addPropertyValue( "alwaysCreate" , "true" );
+		context.registerBeanDefinition( "rmiRegistry" , builder.getBeanDefinition( ) );
+	}
+
+
 	private ApplicationContext makeDataConfigContext( )
 	{
 		String[] dSource = {
-				this.getDataSource( ) ,
+			this.getDataSource( ) ,
 		};
 		FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( dSource );
 		ctx.refresh( );
diff --git a/legacyworlds-server/legacyworlds-server-main/src/main/resources/lw-server.xml b/legacyworlds-server/legacyworlds-server-main/src/main/resources/lw-server.xml
index 40e41af..96b67dc 100644
--- a/legacyworlds-server/legacyworlds-server-main/src/main/resources/lw-server.xml
+++ b/legacyworlds-server/legacyworlds-server-main/src/main/resources/lw-server.xml
@@ -22,6 +22,8 @@
 	<import resource="configuration/naming-beans.xml" />
 	<import resource="configuration/simple-beans.xml" />
 	<import resource="configuration/system-beans.xml" />
+	<import resource="configuration/techs-beans.xml" />
+	<import resource="configuration/updates-beans.xml" />
 	<import resource="configuration/user-beans.xml" />
 
 </beans>
diff --git a/legacyworlds-server/legacyworlds-server-tests/pom.xml b/legacyworlds-server/legacyworlds-server-tests/pom.xml
index ccf4b6b..02965d2 100644
--- a/legacyworlds-server/legacyworlds-server-tests/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-tests/pom.xml
@@ -4,13 +4,13 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-tests</artifactId>
 	<name>Legacy Worlds server tests</name>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<description>This package regroups all tests for server capabilities.</description>
 
 	<dependencies>
diff --git a/legacyworlds-server/legacyworlds-server-utils/pom.xml b/legacyworlds-server/legacyworlds-server-utils/pom.xml
index 492a2e1..989be1b 100644
--- a/legacyworlds-server/legacyworlds-server-utils/pom.xml
+++ b/legacyworlds-server/legacyworlds-server-utils/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-server</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server-utils</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds server utility classes</name>
 	<description>This package contains utility classes used by various parts of the server-side code.</description>
 
diff --git a/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/EmailAddress.java b/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/EmailAddress.java
index 5f1a169..526ea78 100644
--- a/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/EmailAddress.java
+++ b/legacyworlds-server/legacyworlds-server-utils/src/main/java/com/deepclone/lw/utils/EmailAddress.java
@@ -29,7 +29,7 @@ public class EmailAddress
 	private static final String quotedPair = "(\\\\" + asciiText + ")";
 
 	// RFC 2822 3.2.4 Atom:
-	private static final String atext = "[a-zA-Z0-9\\!\\#\\$\\%\\&amp;\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]";
+	private static final String atext = "[a-zA-Z0-9\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]";
 	private static final String dotAtomText = atext + "+" + "(" + "\\." + atext + "+)*";
 	private static final String dotAtom = fwsp + "(" + dotAtomText + ")" + fwsp;
 
diff --git a/legacyworlds-server/pom.xml b/legacyworlds-server/pom.xml
index db8aa26..c00caab 100644
--- a/legacyworlds-server/pom.xml
+++ b/legacyworlds-server/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-server</artifactId>
 	<name>Legacy Worlds server</name>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<packaging>pom</packaging>
 	<description>This metapackage is the root of the game server's components' code.</description>
 
diff --git a/legacyworlds-session/pom.xml b/legacyworlds-session/pom.xml
index 3ea53f7..941a81d 100644
--- a/legacyworlds-session/pom.xml
+++ b/legacyworlds-session/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-session</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds sessions</name>
 	<description>This module contains the definition of sessions used in client-server communications and all related classes and exceptions.</description>
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/CreateAuthChallengeCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/CreateAuthChallengeCommand.java
index 3a6bbef..9cc5a34 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/CreateAuthChallengeCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/CreateAuthChallengeCommand.java
@@ -1,11 +1,12 @@
 package com.deepclone.lw.cmd;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 public class CreateAuthChallengeCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/CreateAuthChallengeResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/CreateAuthChallengeResponse.java
index 1e5ad58..d1d9692 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/CreateAuthChallengeResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/CreateAuthChallengeResponse.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -9,7 +10,7 @@ public class CreateAuthChallengeResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String challenge;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/MaintenanceResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/MaintenanceResponse.java
index efe9995..abd5a50 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/MaintenanceResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/MaintenanceResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd;
 import java.sql.Timestamp;
 import java.util.Date;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -12,7 +13,7 @@ public class MaintenanceResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Timestamp start;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminOverviewCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminOverviewCommand.java
index fa340fe..f997300 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminOverviewCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminOverviewCommand.java
@@ -1,11 +1,12 @@
 package com.deepclone.lw.cmd.admin;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 public class AdminOverviewCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminOverviewResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminOverviewResponse.java
index b292818..c7937c9 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminOverviewResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminOverviewResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.admin;
 
 import com.deepclone.lw.cmd.admin.adata.AdminOverview;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class AdminOverviewResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private final AdminOverview overview;
 
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminResponse.java
index 7be1d55..ec411f8 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/AdminResponse.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.admin;
 
 
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -10,7 +11,7 @@ public class AdminResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Administrator admin;
 	private final boolean privilegeOk;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/NoOperationCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/NoOperationCommand.java
index 4765054..bc19cea 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/NoOperationCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/NoOperationCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.admin;
 
 
 import com.deepclone.lw.cmd.admin.adata.Privileges;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class NoOperationCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Privileges requirePrivilege;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/SetPasswordCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/SetPasswordCommand.java
index 49eeb2f..baa5056 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/SetPasswordCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/SetPasswordCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SetPasswordCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String sha1Auth;
 	private final String md5Auth;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/SetPasswordResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/SetPasswordResponse.java
index a4a429f..e1bcb86 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/SetPasswordResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/SetPasswordResponse.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.admin;
 
 
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -9,7 +10,7 @@ public class SetPasswordResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	public static enum PasswordChangeStatus {
 		OK ,
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/AdminOverview.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/AdminOverview.java
index 4eb221b..dabaf57 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/AdminOverview.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/AdminOverview.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.adata;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class AdminOverview
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private long newMessages;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/Administrator.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/Administrator.java
index 19c3dbf..91be442 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/Administrator.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/Administrator.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.adata;
 
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class Administrator
 		extends AdministratorBasics
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String address;
 	private boolean passwordChange;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/AdministratorBasics.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/AdministratorBasics.java
index 2f7342b..cb04d79 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/AdministratorBasics.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/AdministratorBasics.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.admin.adata;
 import java.io.Serializable;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class AdministratorBasics
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/Privileges.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/Privileges.java
index 824fb65..412c49f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/Privileges.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/adata/Privileges.java
@@ -20,6 +20,7 @@ public enum Privileges {
 	BUGM( 0x00000200 , "Receiving automated error e-mail" ) ,
 	BUGT( 0x00000400 , "Managing bugs reported through the bug tracker" ) ,
 	MNTM( 0x00000800 , "Activating/disabling maintenance mode" ) ,
+	GDAT( 0x00001000 , "Modifying game data" ) ,
 	SUPER( 0x80000000 , "Superuser (all privileges + admin management)" );
 
 	private final int bits;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ArchivedBanRequest.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ArchivedBanRequest.java
index d04e0ce..32d6336 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ArchivedBanRequest.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ArchivedBanRequest.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.bans;
 
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class ArchivedBanRequest
 		extends BanRequest
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private Timestamp update;
 	private boolean expired;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BanRequest.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BanRequest.java
index dee4b52..1a7f3d6 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BanRequest.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BanRequest.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.admin.bans;
 import java.io.Serializable;
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BanRequest
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private int requestedById;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BansSummaryCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BansSummaryCommand.java
index 6b54955..0bfdf9b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BansSummaryCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BansSummaryCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bans;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class BansSummaryCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BansSummaryResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BansSummaryResponse.java
index 9cfe672..1d21b10 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BansSummaryResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/BansSummaryResponse.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class BansSummaryResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< SummaryEntry > entries;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ConfirmBanCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ConfirmBanCommand.java
index 3ff608c..e19fca2 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ConfirmBanCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ConfirmBanCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bans;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ConfirmBanCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/LiftBanCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/LiftBanCommand.java
index 24f2067..98d6b96 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/LiftBanCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/LiftBanCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bans;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class LiftBanCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ListBansCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ListBansCommand.java
index 1ba7c91..60f8463 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ListBansCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ListBansCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bans;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ListBansCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BanType type;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ListBansResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ListBansResponse.java
index c956cec..205398a 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ListBansResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ListBansResponse.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class ListBansResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BanType type;
 	private final List< BanRequest > bans;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RejectBanCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RejectBanCommand.java
index 1e5e56b..0fc9092 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RejectBanCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RejectBanCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bans;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class RejectBanCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 	private final String reason;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RejectBanResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RejectBanResponse.java
index caa0a1e..38cf2d5 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RejectBanResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RejectBanResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.admin.bans;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class RejectBanResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private final boolean error;
 	private final int id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RequestBanCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RequestBanCommand.java
index 8f6e882..cee952c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RequestBanCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RequestBanCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bans;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class RequestBanCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String user;
 	private final boolean empire;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RequestBanResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RequestBanResponse.java
index 8637681..31365eb 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RequestBanResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/RequestBanResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.admin.bans;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class RequestBanResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	public static enum Error {
 		NOT_FOUND ,
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/SummaryEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/SummaryEntry.java
index 0fcfa02..ce4c74b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/SummaryEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/SummaryEntry.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.bans;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class SummaryEntry
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BanType type;
 	private final long count;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ValidatedBanRequest.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ValidatedBanRequest.java
index dfa6945..12586b7 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ValidatedBanRequest.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bans/ValidatedBanRequest.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.bans;
 
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class ValidatedBanRequest
 		extends BanRequest
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private Timestamp update;
 	private boolean redeemable;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/BugsSummaryCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/BugsSummaryCommand.java
index d5846a0..efbecb9 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/BugsSummaryCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/BugsSummaryCommand.java
@@ -1,5 +1,6 @@
 package com.deepclone.lw.cmd.admin.bt;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -7,6 +8,6 @@ public class BugsSummaryCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/BugsSummaryResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/BugsSummaryResponse.java
index c399f41..05315ba 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/BugsSummaryResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/BugsSummaryResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.admin.bt;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class BugsSummaryResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long pending;
 	private final long open;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/GetSnapshotCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/GetSnapshotCommand.java
index 9aa671a..df41a9a 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/GetSnapshotCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/GetSnapshotCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bt;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GetSnapshotCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long bugId;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/GetSnapshotResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/GetSnapshotResponse.java
index 439c768..d7dae36 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/GetSnapshotResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/GetSnapshotResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.admin.bt;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class GetSnapshotResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String snapshot;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ListBugsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ListBugsResponse.java
index 4975ab8..5e18e62 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ListBugsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ListBugsResponse.java
@@ -7,6 +7,7 @@ import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.bt.data.BugReport;
 import com.deepclone.lw.cmd.bt.data.BugStatus;
+import com.deepclone.lw.session.API;
 
 
 
@@ -14,7 +15,7 @@ public class ListBugsResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BugStatus status;
 	private final boolean ownOnly;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/MergeReportsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/MergeReportsCommand.java
index f95de2f..0997f53 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/MergeReportsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/MergeReportsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bt;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class MergeReportsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id1;
 	private final long id2;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/MergeReportsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/MergeReportsResponse.java
index aedcc55..accb14f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/MergeReportsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/MergeReportsResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.bt.data.BugEvent;
 import com.deepclone.lw.cmd.bt.data.BugReport;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class MergeReportsResponse
 		extends ViewBugResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final MergeError mergeError;
 	private final long mergeId;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ModerateCommentCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ModerateCommentCommand.java
index f648c86..2c3b6b3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ModerateCommentCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ModerateCommentCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bt;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ModerateCommentCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 	private final boolean validation;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/PostCommentResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/PostCommentResponse.java
index 13a97f0..e1375ba 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/PostCommentResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/PostCommentResponse.java
@@ -7,6 +7,7 @@ import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.bt.data.BugEvent;
 import com.deepclone.lw.cmd.bt.data.BugReport;
+import com.deepclone.lw.session.API;
 
 
 
@@ -14,7 +15,7 @@ public class PostCommentResponse
 		extends ViewBugResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean posted;
 	private final ObjectNameError commentError;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportBugResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportBugResponse.java
index 3231103..4f1fa99 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportBugResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportBugResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.admin.bt;
 import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class ReportBugResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long bugId;
 	private final ObjectNameError titleError;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportStatusCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportStatusCommand.java
index 2f7ee85..9d8f155 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportStatusCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportStatusCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.admin.bt;
 
 
 import com.deepclone.lw.cmd.bt.data.BugStatus;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class ReportStatusCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 	private final BugStatus status;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportVisibilityCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportVisibilityCommand.java
index 04d82c1..ae97fe3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportVisibilityCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ReportVisibilityCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.bt;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ReportVisibilityCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ValidateReportCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ValidateReportCommand.java
index 704c018..60c65ea 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ValidateReportCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ValidateReportCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.admin.bt;
 
 
 import com.deepclone.lw.cmd.bt.data.BugStatus;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class ValidateReportCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 	private final BugStatus status;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ViewBugResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ViewBugResponse.java
index eccb9d5..60ae71e 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ViewBugResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/bt/ViewBugResponse.java
@@ -7,6 +7,7 @@ import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.bt.data.BugEvent;
 import com.deepclone.lw.cmd.bt.data.BugReport;
+import com.deepclone.lw.session.API;
 
 
 
@@ -14,7 +15,7 @@ public class ViewBugResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BugReport report;
 	private final List< BugEvent > events;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/Category.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/Category.java
index 774a70a..89d82c5 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/Category.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/Category.java
@@ -5,13 +5,15 @@ import java.io.Serializable;
 import java.util.Collections;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class Category
 		implements Serializable , Comparable< Category >
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String name;
 	private final List< Definition > definitions;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/Definition.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/Definition.java
index 7115438..f5fb206 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/Definition.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/Definition.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.constants;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class Definition
 		implements Serializable , Comparable< Definition >
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String name;
 	private String description;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/GetConstantsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/GetConstantsCommand.java
index 93920a5..6fa7d41 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/GetConstantsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/GetConstantsCommand.java
@@ -1,11 +1,12 @@
 package com.deepclone.lw.cmd.admin.constants;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 public class GetConstantsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/GetConstantsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/GetConstantsResponse.java
index a31d96f..ca03aca 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/GetConstantsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/GetConstantsResponse.java
@@ -6,13 +6,14 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
 public class GetConstantsResponse
 		extends AdminResponse
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private List< Category > categories;
 
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/SetConstantCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/SetConstantCommand.java
index d2efe93..e506cf3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/SetConstantCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/SetConstantCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.constants;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SetConstantCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String name;
 	private final double value;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/SetConstantResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/SetConstantResponse.java
index cec5d9c..d5522c8 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/SetConstantResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/constants/SetConstantResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.admin.constants;
 import java.util.List;
 
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class SetConstantResponse
 		extends GetConstantsResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean error;
 	private final String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ChangeLanguageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ChangeLanguageCommand.java
index 9882fd5..0dea93c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ChangeLanguageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ChangeLanguageCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.i18n;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ChangeLanguageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String id;
 	private final String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ChangeLanguageResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ChangeLanguageResponse.java
index fd23d8e..57545ca 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ChangeLanguageResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ChangeLanguageResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.admin.i18n;
 import java.util.List;
 
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class ChangeLanguageResponse
 		extends GetLanguageResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean nameError;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/GetLanguageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/GetLanguageCommand.java
index 9ed73bb..4a66401 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/GetLanguageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/GetLanguageCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.i18n;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GetLanguageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String language;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/GetLanguageResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/GetLanguageResponse.java
index 68cbe5a..6f46e63 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/GetLanguageResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/GetLanguageResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class GetLanguageResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Language language;
 	private final List< I18NString > strings;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/I18NString.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/I18NString.java
index 06ada37..72691e9 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/I18NString.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/I18NString.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.i18n;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class I18NString
 		implements Serializable , Comparable< I18NString >
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String id;
 	private final String text;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/Language.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/Language.java
index 6258920..ec8c299 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/Language.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/Language.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.i18n;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class Language
 		implements Serializable , Comparable< Language >
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String id;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/SetStringCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/SetStringCommand.java
index 8cff779..77e7bb4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/SetStringCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/SetStringCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.i18n;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SetStringCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String language;
 	private final String id;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/SetStringResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/SetStringResponse.java
index 5b3af0f..6242475 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/SetStringResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/SetStringResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.admin.i18n;
 import java.util.List;
 
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class SetStringResponse
 		extends GetLanguageResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String edited;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ViewLanguagesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ViewLanguagesCommand.java
index 5a22e75..0f6f313 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ViewLanguagesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ViewLanguagesCommand.java
@@ -1,5 +1,6 @@
 package com.deepclone.lw.cmd.admin.i18n;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -7,6 +8,6 @@ public class ViewLanguagesCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ViewLanguagesResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ViewLanguagesResponse.java
index 96e66cf..9c5f2b1 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ViewLanguagesResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/i18n/ViewLanguagesResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class ViewLanguagesResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< Language > languages;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ExceptionEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ExceptionEntry.java
index 55d4aa3..31aeddb 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ExceptionEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ExceptionEntry.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.admin.logs;
 import java.io.Serializable;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class ExceptionEntry
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String className;
 	private final String message;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/GetEntryCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/GetEntryCommand.java
index c07f7c9..00361bd 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/GetEntryCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/GetEntryCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.logs;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GetEntryCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/GetEntryResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/GetEntryResponse.java
index 8d20250..7ed89d9 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/GetEntryResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/GetEntryResponse.java
@@ -5,13 +5,14 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
 public class GetEntryResponse
 		extends AdminResponse
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final LogEntry entry;
 	private final List< ExceptionEntry > exceptions;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/LogEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/LogEntry.java
index f203bb6..d5c567f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/LogEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/LogEntry.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.admin.logs;
 import java.io.Serializable;
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class LogEntry
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private Long id;
 	private Timestamp timestamp;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/TraceEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/TraceEntry.java
index 885c6f4..3226647 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/TraceEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/TraceEntry.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.logs;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class TraceEntry
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String location;
 	private final String fileName;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ViewLogCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ViewLogCommand.java
index d3f3ad7..a67b15f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ViewLogCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ViewLogCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.logs;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ViewLogCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final LogType type;
 	private final long firstEntry;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ViewLogResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ViewLogResponse.java
index 3964c7e..f9a1ab0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ViewLogResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/logs/ViewLogResponse.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class ViewLogResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long count;
 	private final List< LogEntry > entries;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/EnableMaintenanceCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/EnableMaintenanceCommand.java
index 2848b92..6929073 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/EnableMaintenanceCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/EnableMaintenanceCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.mntm;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class EnableMaintenanceCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String reason;
 	private final int duration;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/EndMaintenanceCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/EndMaintenanceCommand.java
index 11d5a82..da99e6e 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/EndMaintenanceCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/EndMaintenanceCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.mntm;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class EndMaintenanceCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/ExtendMaintenanceCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/ExtendMaintenanceCommand.java
index 4a40d58..3f30964 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/ExtendMaintenanceCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/ExtendMaintenanceCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.mntm;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ExtendMaintenanceCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int duration;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceChangeResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceChangeResponse.java
index c5ea32c..6a9e4e7 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceChangeResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceChangeResponse.java
@@ -5,6 +5,7 @@ import java.util.Date;
 
 import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class MaintenanceChangeResponse
 		extends MaintenanceStatusResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String newReason;
 	private final ObjectNameError reasonError;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceStatusCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceStatusCommand.java
index c0fd9b0..b4d08b9 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceStatusCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceStatusCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.mntm;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class MaintenanceStatusCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceStatusResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceStatusResponse.java
index 4f98983..9b441b6 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceStatusResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/mntm/MaintenanceStatusResponse.java
@@ -5,6 +5,7 @@ import java.util.Date;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class MaintenanceStatusResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String reason;
 	private final Date started;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ComposeMessageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ComposeMessageCommand.java
index 5eb5314..db5078c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ComposeMessageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ComposeMessageCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.admin.msg;
 
 
 import com.deepclone.lw.cmd.msgdata.MessageType;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class ComposeMessageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Boolean inbox;
 	private final Long replyTo;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ComposeMessageResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ComposeMessageResponse.java
index 7b01bc4..c95a7e5 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ComposeMessageResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ComposeMessageResponse.java
@@ -5,6 +5,7 @@ import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.msgdata.Message;
 import com.deepclone.lw.cmd.msgdata.MessageType;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class ComposeMessageResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private MessageType messageType;
 	private String target;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/GetMessagesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/GetMessagesCommand.java
index f6019f1..b1175b1 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/GetMessagesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/GetMessagesCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.msg;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GetMessagesCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean inbox;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/GetMessagesResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/GetMessagesResponse.java
index bd0c6da..ee774d3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/GetMessagesResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/GetMessagesResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.msgdata.MessageListEntry;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class GetMessagesResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< MessageListEntry > messages;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/MessageBoxCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/MessageBoxCommand.java
index c7431f2..5b2dfd2 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/MessageBoxCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/MessageBoxCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.msg;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class MessageBoxCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final MessageBoxAction action;
 	private final boolean inbox;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/PrepareMessageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/PrepareMessageCommand.java
index 0da0140..244779c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/PrepareMessageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/PrepareMessageCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.admin.msg;
 
 
 import com.deepclone.lw.cmd.msgdata.MessageType;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class PrepareMessageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final MessageType type;
 	private final Long id;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ReadMessageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ReadMessageCommand.java
index e4d178f..57db7bf 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ReadMessageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ReadMessageCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.msg;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ReadMessageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean inbox;
 	private final long id;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ReadMessageResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ReadMessageResponse.java
index c310b93..096124d 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ReadMessageResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/ReadMessageResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.admin.msg;
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.msgdata.Message;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class ReadMessageResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean inbox;
 	private final Message message;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/SendSpamCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/SendSpamCommand.java
index b282bcc..9acc488 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/SendSpamCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/msg/SendSpamCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.msg;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SendSpamCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String title;
 	private final String body;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/GetNamesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/GetNamesCommand.java
index 1502f47..bcd08ea 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/GetNamesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/GetNamesCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.naming;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GetNamesCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final NameType type;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/GetNamesResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/GetNamesResponse.java
index 735a487..54a68ba 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/GetNamesResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/GetNamesResponse.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class GetNamesResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final NameType type;
 	private final List< Name > names;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/Name.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/Name.java
index c3f67e8..3c71848 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/Name.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/Name.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.naming;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class Name
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 	private final String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesActionCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesActionCommand.java
index 0b778e1..e808868 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesActionCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesActionCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.naming;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class NamesActionCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final NameType type;
 	private final NameAction action;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesSummaryCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesSummaryCommand.java
index 96ea84a..325c47e 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesSummaryCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesSummaryCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.naming;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class NamesSummaryCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesSummaryResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesSummaryResponse.java
index adbb523..746f7d6 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesSummaryResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/naming/NamesSummaryResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class NamesSummaryResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	@SuppressWarnings( "serial" )
 	public static class Entry
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/GetPrefDefaultsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/GetPrefDefaultsCommand.java
index 0946c22..25b876b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/GetPrefDefaultsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/GetPrefDefaultsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.prefs;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class GetPrefDefaultsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/PrefDefaultsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/PrefDefaultsResponse.java
index fed8047..e36f9ec 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/PrefDefaultsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/PrefDefaultsResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.player.gdata.account.PrefCategory;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class PrefDefaultsResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< PrefCategory > preferences;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/SetPrefDefaultCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/SetPrefDefaultCommand.java
index 35f3945..64a778e 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/SetPrefDefaultCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/prefs/SetPrefDefaultCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.prefs;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SetPrefDefaultCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String preference;
 	private final String value;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/AddAdministratorCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/AddAdministratorCommand.java
index ed1eeb8..77febe4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/AddAdministratorCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/AddAdministratorCommand.java
@@ -6,6 +6,7 @@ import java.util.HashSet;
 import java.util.Set;
 
 import com.deepclone.lw.cmd.admin.adata.Privileges;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -14,7 +15,7 @@ public class AddAdministratorCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String address;
 	private final String appearAs;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/AddAdministratorResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/AddAdministratorResponse.java
index a31e6d4..f1a9067 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/AddAdministratorResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/AddAdministratorResponse.java
@@ -7,13 +7,14 @@ import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
 import com.deepclone.lw.cmd.admin.adata.PrivEntry;
+import com.deepclone.lw.session.API;
 
 
 
 public class AddAdministratorResponse
 		extends AdminResponse
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	public static enum AddressError {
 		EMPTY ,
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ListAdministratorsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ListAdministratorsCommand.java
index 8133b1c..e6930e1 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ListAdministratorsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ListAdministratorsCommand.java
@@ -1,11 +1,12 @@
 package com.deepclone.lw.cmd.admin.su;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 public class ListAdministratorsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ListAdministratorsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ListAdministratorsResponse.java
index 9a49ab2..39208b4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ListAdministratorsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ListAdministratorsResponse.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class ListAdministratorsResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< Administrator > administrators;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ResetAdminPasswordCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ResetAdminPasswordCommand.java
index 78c5bb0..6023fc3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ResetAdminPasswordCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ResetAdminPasswordCommand.java
@@ -1,10 +1,12 @@
 package com.deepclone.lw.cmd.admin.su;
 
+import com.deepclone.lw.session.API;
+
 
 public class ResetAdminPasswordCommand
 		extends ViewAdministratorCommand
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 
 	public ResetAdminPasswordCommand( int identifier )
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/SetPrivilegesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/SetPrivilegesCommand.java
index ea902e3..8fc8ad7 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/SetPrivilegesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/SetPrivilegesCommand.java
@@ -6,6 +6,7 @@ import java.util.HashSet;
 import java.util.Set;
 
 import com.deepclone.lw.cmd.admin.adata.Privileges;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class SetPrivilegesCommand
 		extends ViewAdministratorCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Set< Privileges > privileges;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ViewAdministratorCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ViewAdministratorCommand.java
index 8b6e119..c3aa977 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ViewAdministratorCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ViewAdministratorCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.su;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ViewAdministratorCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int identifier;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ViewAdministratorResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ViewAdministratorResponse.java
index a903ccc..176d54a 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ViewAdministratorResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/su/ViewAdministratorResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.admin.su;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class ViewAdministratorResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Administrator view;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/GetTechnologyCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/GetTechnologyCommand.java
new file mode 100644
index 0000000..2695f5f
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/GetTechnologyCommand.java
@@ -0,0 +1,29 @@
+package com.deepclone.lw.cmd.admin.techs;
+
+
+import com.deepclone.lw.session.API;
+import com.deepclone.lw.session.Command;
+
+
+
+public class GetTechnologyCommand
+		extends Command
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final String technology;
+
+
+	public GetTechnologyCommand( String technology )
+	{
+		this.technology = technology;
+	}
+
+
+	public String getTechnology( )
+	{
+		return technology;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/GetTechnologyResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/GetTechnologyResponse.java
new file mode 100644
index 0000000..ec14a8c
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/GetTechnologyResponse.java
@@ -0,0 +1,62 @@
+package com.deepclone.lw.cmd.admin.techs;
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cmd.admin.AdminResponse;
+import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
+
+
+
+public class GetTechnologyResponse
+		extends AdminResponse
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	public final TechEntry technology;
+
+	public final List< String > categories;
+
+	public final List< String > strings;
+
+
+	public GetTechnologyResponse( Administrator admin , boolean privError )
+	{
+		super( admin , privError );
+		this.technology = null;
+		this.categories = null;
+		this.strings = null;
+	}
+
+
+	public GetTechnologyResponse( Administrator admin , TechEntry technology , List< String > categories ,
+			List< String > strings )
+	{
+		super( admin , false );
+		this.technology = technology;
+		this.categories = categories;
+		this.strings = strings;
+	}
+
+
+	public TechEntry getTechnology( )
+	{
+		return this.technology;
+	}
+
+
+	public List< String > getCategories( )
+	{
+		return new LinkedList< String >( this.categories );
+	}
+
+
+	public List< String > getStrings( )
+	{
+		return new LinkedList< String >( this.strings );
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/ListCategoriesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/ListCategoriesCommand.java
new file mode 100644
index 0000000..736b187
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/ListCategoriesCommand.java
@@ -0,0 +1,15 @@
+package com.deepclone.lw.cmd.admin.techs;
+
+
+import com.deepclone.lw.session.API;
+import com.deepclone.lw.session.Command;
+
+
+
+public class ListCategoriesCommand
+		extends Command
+{
+
+	private static final long serialVersionUID = API.Version;
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/ListCategoriesResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/ListCategoriesResponse.java
new file mode 100644
index 0000000..e5446a3
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/ListCategoriesResponse.java
@@ -0,0 +1,40 @@
+package com.deepclone.lw.cmd.admin.techs;
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cmd.admin.AdminResponse;
+import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
+
+
+
+public class ListCategoriesResponse
+		extends AdminResponse
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final List< TechCategory > categories;
+
+
+	public ListCategoriesResponse( Administrator admin )
+	{
+		super( admin , false );
+		this.categories = null;
+	}
+
+
+	public ListCategoriesResponse( Administrator admin , List< TechCategory > categories )
+	{
+		super( admin , true );
+		this.categories = categories;
+	}
+
+
+	public List< TechCategory > getCategories( )
+	{
+		return new LinkedList< TechCategory >( this.categories );
+	}
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/TechCategory.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/TechCategory.java
new file mode 100644
index 0000000..b85721c
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/TechCategory.java
@@ -0,0 +1,50 @@
+package com.deepclone.lw.cmd.admin.techs;
+
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.session.API;
+
+
+
+public class TechCategory
+		implements Serializable
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final String name;
+
+	private final String description;
+
+	private final List< String > techs;
+
+
+	public TechCategory( String name , String description , List< String > techs )
+	{
+		this.name = name;
+		this.description = description;
+		this.techs = new LinkedList< String >( techs );
+	}
+
+
+	public String getName( )
+	{
+		return name;
+	}
+
+
+	public String getDescription( )
+	{
+		return description;
+	}
+
+
+	public List< String > getTechs( )
+	{
+		return techs;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/TechEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/TechEntry.java
new file mode 100644
index 0000000..cc4a2d2
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/techs/TechEntry.java
@@ -0,0 +1,62 @@
+package com.deepclone.lw.cmd.admin.techs;
+
+
+import java.io.Serializable;
+
+import com.deepclone.lw.session.API;
+
+
+
+public class TechEntry
+		implements Serializable
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final String category;
+	private final String name;
+	private final String description;
+	private final int points;
+	private final int cost;
+
+
+	public TechEntry( String category , String name , String description , int points , int cost )
+	{
+		this.category = category;
+		this.name = name;
+		this.description = description;
+		this.points = points;
+		this.cost = cost;
+	}
+
+
+	public String getCategory( )
+	{
+		return category;
+	}
+
+
+	public String getName( )
+	{
+		return name;
+	}
+
+
+	public String getDescription( )
+	{
+		return description;
+	}
+
+
+	public int getPoints( )
+	{
+		return points;
+	}
+
+
+	public int getCost( )
+	{
+		return cost;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/SetTaskStatusCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/SetTaskStatusCommand.java
index 82645b8..dca5056 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/SetTaskStatusCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/SetTaskStatusCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.tick;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SetTaskStatusCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int task;
 	private final TickerTaskStatus newStatus;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerStatusCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerStatusCommand.java
index e6dda40..42f7694 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerStatusCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerStatusCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.tick;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class TickerStatusCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerStatusResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerStatusResponse.java
index 9de84db..ad401cd 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerStatusResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerStatusResponse.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class TickerStatusResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean paused;
 	private final List< TickerTaskInfo > tasks;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerTaskInfo.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerTaskInfo.java
index 5f189de..88780dd 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerTaskInfo.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/TickerTaskInfo.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.admin.tick;
 import java.io.Serializable;
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class TickerTaskInfo
 		implements Serializable , Comparable< TickerTaskInfo >
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/ToggleTickerCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/ToggleTickerCommand.java
index 797781f..95b9f94 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/ToggleTickerCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/tick/ToggleTickerCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.tick;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class ToggleTickerCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountBanEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountBanEntry.java
index 6f124e0..5212f12 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountBanEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountBanEntry.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.users;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class AccountBanEntry
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String requestedByName;
 	private int requestedById;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountListEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountListEntry.java
index f102958..ec88fc0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountListEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountListEntry.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.admin.users;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class AccountListEntry
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private String address;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountSessionEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountSessionEntry.java
index ff8594d..2a432a6 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountSessionEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountSessionEntry.java
@@ -3,12 +3,14 @@ package com.deepclone.lw.cmd.admin.users;
 
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class AccountSessionEntry
 		extends AccountListEntry
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private List< UserSession > sessions;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountViewEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountViewEntry.java
index 44ff78d..8d4b36e 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountViewEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/AccountViewEntry.java
@@ -5,13 +5,15 @@ import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class AccountViewEntry
 		extends AccountListEntry
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private Integer empireId;
 	private int gameCredits;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/GiveCreditsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/GiveCreditsCommand.java
index 5ab9330..5a72297 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/GiveCreditsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/GiveCreditsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.users;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GiveCreditsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 	private final int credits;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListAccountsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListAccountsCommand.java
index 5b52707..58b84ed 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListAccountsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListAccountsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.users;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ListAccountsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final AccountStatus status;
 	private final boolean online;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListAccountsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListAccountsResponse.java
index 389bd1e..d93d8dc 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListAccountsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListAccountsResponse.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class ListAccountsResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final AccountStatus status;
 	private final boolean online;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListSessionsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListSessionsCommand.java
index 0dd8336..de06eb1 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListSessionsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListSessionsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.users;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ListSessionsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListSessionsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListSessionsResponse.java
index a9ae818..87bc72c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListSessionsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ListSessionsResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.admin.users;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class ListSessionsResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final AccountSessionEntry account;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/UserSession.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/UserSession.java
index b66cb2b..11a0265 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/UserSession.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/UserSession.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.admin.users;
 import java.io.Serializable;
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class UserSession
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long id;
 	private int credentialsId;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ViewAccountCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ViewAccountCommand.java
index aa05383..584f465 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ViewAccountCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ViewAccountCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.admin.users;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ViewAccountCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ViewAccountResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ViewAccountResponse.java
index add90f9..efa7255 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ViewAccountResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/admin/users/ViewAccountResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.admin.users;
 
 import com.deepclone.lw.cmd.admin.AdminResponse;
 import com.deepclone.lw.cmd.admin.adata.Administrator;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class ViewAccountResponse
 		extends AdminResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final AccountViewEntry account;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ListBugsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ListBugsCommand.java
index 18763af..ae33a05 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ListBugsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ListBugsCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.bt;
 
 
 import com.deepclone.lw.cmd.bt.data.BugStatus;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class ListBugsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BugStatus status;
 	private final boolean ownOnly;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/PostCommentCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/PostCommentCommand.java
index d032610..82ee37f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/PostCommentCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/PostCommentCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.bt;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class PostCommentCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 	private final String comment;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ReportBugCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ReportBugCommand.java
index 92425fe..bd57fd1 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ReportBugCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ReportBugCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.bt;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ReportBugCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String title;
 	private final String description;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ViewBugCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ViewBugCommand.java
index 1ef74ba..ed25758 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ViewBugCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/ViewBugCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.bt;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ViewBugCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugEvent.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugEvent.java
index 027b9d7..f53cf16 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugEvent.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugEvent.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.bt.data;
 import java.io.Serializable;
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BugEvent
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long id;
 	private BugEventType type;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugReport.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugReport.java
index 2767173..f4be6c6 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugReport.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugReport.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.bt.data;
 import java.io.Serializable;
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BugReport
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long reportId;
 	private String title;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugSubmitter.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugSubmitter.java
index 3e6e5b8..729ef38 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugSubmitter.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/bt/data/BugSubmitter.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.bt.data;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BugSubmitter
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private boolean isAdmin;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ConfirmPasswordRecoveryCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ConfirmPasswordRecoveryCommand.java
index fe21b7d..56b45af 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ConfirmPasswordRecoveryCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ConfirmPasswordRecoveryCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.ext;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ConfirmPasswordRecoveryCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String mailAddress;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ConfirmPasswordRecoveryResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ConfirmPasswordRecoveryResponse.java
index 4342cdd..0fdb4b3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ConfirmPasswordRecoveryResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ConfirmPasswordRecoveryResponse.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.ext;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -9,7 +10,7 @@ public class ConfirmPasswordRecoveryResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	public static enum PasswordRecoveryStatus {
 		OK ,
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/CreateAccountCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/CreateAccountCommand.java
index a4d0d32..60ff472 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/CreateAccountCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/CreateAccountCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.ext;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public final class CreateAccountCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private final String mail;
 	private final String mailConfirm;
 	private final String password;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/CreateAccountResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/CreateAccountResponse.java
index d7fff8b..f997c37 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/CreateAccountResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/CreateAccountResponse.java
@@ -5,6 +5,7 @@ import java.util.Map;
 
 import com.deepclone.lw.cmd.MailError;
 import com.deepclone.lw.cmd.PasswordError;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -13,7 +14,7 @@ public final class CreateAccountResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean created;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ListLanguagesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ListLanguagesCommand.java
index d04a39e..5fc3027 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ListLanguagesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ListLanguagesCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.ext;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class ListLanguagesCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ListLanguagesResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ListLanguagesResponse.java
index 5f046c3..4815584 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ListLanguagesResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/ListLanguagesResponse.java
@@ -7,6 +7,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -15,12 +16,12 @@ public class ListLanguagesResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	public static class Language
 			implements Serializable
 	{
-		private static final long serialVersionUID = 1L;
+		private static final long serialVersionUID = API.Version;
 
 		private final String id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/RequestPasswordRecoveryCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/RequestPasswordRecoveryCommand.java
index dea1b75..5d78bef 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/RequestPasswordRecoveryCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/RequestPasswordRecoveryCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.ext;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class RequestPasswordRecoveryCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String mailAddress;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/RequestPasswordRecoveryResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/RequestPasswordRecoveryResponse.java
index cdf6407..bbe7753 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/RequestPasswordRecoveryResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/ext/RequestPasswordRecoveryResponse.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.ext;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -9,7 +10,7 @@ public class RequestPasswordRecoveryResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	public static enum PasswordRecoveryRequestStatus {
 		OK ,
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/msgdata/Message.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/msgdata/Message.java
index d3e8547..d11c1db 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/msgdata/Message.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/msgdata/Message.java
@@ -5,6 +5,7 @@ import java.io.Serializable;
 import java.sql.Timestamp;
 
 import com.deepclone.lw.cmd.player.gdata.GameTime;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class Message
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long id;
 	private Long previous;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/msgdata/MessageListEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/msgdata/MessageListEntry.java
index aee6cac..12809b2 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/msgdata/MessageListEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/msgdata/MessageListEntry.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.msgdata;
 import java.io.Serializable;
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class MessageListEntry
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long id;
 	private MessageType type;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/EmpireResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/EmpireResponse.java
index 7b6eea1..3fa9bcb 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/EmpireResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/EmpireResponse.java
@@ -1,14 +1,13 @@
 package com.deepclone.lw.cmd.player;
 
 
-import java.util.Collections;
 import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.battles.BattleListEntry;
 import com.deepclone.lw.cmd.player.gdata.empire.OverviewData;
-import com.deepclone.lw.cmd.player.gdata.empire.ResearchLineData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -16,19 +15,16 @@ public class EmpireResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private final OverviewData overview;
-	private final List< ResearchLineData > research;
 	private final List< BattleListEntry > battles;
 
 
-	public EmpireResponse( GamePageData page , OverviewData overview , List< ResearchLineData > research ,
-			List< BattleListEntry > battles )
+	public EmpireResponse( GamePageData page , OverviewData overview , List< BattleListEntry > battles )
 	{
 		super( page );
 		this.overview = overview;
 		this.battles = battles;
-		this.research = Collections.unmodifiableList( research );
 	}
 
 
@@ -38,12 +34,6 @@ public class EmpireResponse
 	}
 
 
-	public List< ResearchLineData > getResearch( )
-	{
-		return research;
-	}
-
-
 	public List< BattleListEntry > getBattles( )
 	{
 		return battles;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/GetNewPlanetCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/GetNewPlanetCommand.java
index 0901c47..f8984af 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/GetNewPlanetCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/GetNewPlanetCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GetNewPlanetCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String name;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/GetNewPlanetResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/GetNewPlanetResponse.java
index 772d507..269e75a 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/GetNewPlanetResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/GetNewPlanetResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player;
 import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class GetNewPlanetResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String name;
 	private final ObjectNameError error;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ImplementTechCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ImplementTechCommand.java
deleted file mode 100644
index 9a26ed7..0000000
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ImplementTechCommand.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.deepclone.lw.cmd.player;
-
-
-import com.deepclone.lw.session.Command;
-
-
-
-public class ImplementTechCommand
-		extends Command
-{
-
-	private static final long serialVersionUID = 1L;
-
-	private final int tech;
-
-
-	public ImplementTechCommand( int tech )
-	{
-		this.tech = tech;
-	}
-
-
-	public int getTech( )
-	{
-		return tech;
-	}
-
-}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ListPlanetsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ListPlanetsCommand.java
index bf8b5e8..28ad77c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ListPlanetsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ListPlanetsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class ListPlanetsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ListPlanetsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ListPlanetsResponse.java
index 0e93aac..47a01b3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ListPlanetsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ListPlanetsResponse.java
@@ -7,6 +7,7 @@ import java.util.List;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.PlanetListData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -14,7 +15,7 @@ public class ListPlanetsResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< PlanetListData > planets;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/OverviewCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/OverviewCommand.java
index 030a27b..c61817b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/OverviewCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/OverviewCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class OverviewCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ViewMapCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ViewMapCommand.java
index 2eb03bb..4ef2af4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ViewMapCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ViewMapCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player;
 
 
 import com.deepclone.lw.cmd.player.gdata.MapSize;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class ViewMapCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean defaults;
 	private final Integer x;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ViewMapResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ViewMapResponse.java
index 703b122..cca0e46 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ViewMapResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/ViewMapResponse.java
@@ -8,6 +8,7 @@ import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.MapSize;
 import com.deepclone.lw.cmd.player.gdata.map.MapSystemData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -15,7 +16,7 @@ public class ViewMapResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int x;
 	private final int y;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountReactivationCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountReactivationCommand.java
index 2634d43..7d9967c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountReactivationCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountReactivationCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class AccountReactivationCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountReactivationResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountReactivationResponse.java
index 9eddc44..a7cd609 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountReactivationResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountReactivationResponse.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -9,7 +10,7 @@ public class AccountReactivationResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String address;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountValidationCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountValidationCommand.java
index 9854256..6b37962 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountValidationCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountValidationCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class AccountValidationCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean initialisation;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountValidationResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountValidationResponse.java
index 8bd908f..cfa6bc3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountValidationResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/AccountValidationResponse.java
@@ -5,6 +5,7 @@ import java.util.Collections;
 import java.util.List;
 
 import com.deepclone.lw.cmd.ObjectNameError;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -13,7 +14,7 @@ public class AccountValidationResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean validated;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/BanDetailsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/BanDetailsCommand.java
index dca68e4..3667bae 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/BanDetailsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/BanDetailsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class BanDetailsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/BanDetailsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/BanDetailsResponse.java
index 20e70c3..76b4d1c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/BanDetailsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/BanDetailsResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.account;
 
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -11,7 +12,7 @@ public class BanDetailsResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Timestamp banTime;
 	private final String banReason;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/CancelQuitCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/CancelQuitCommand.java
index 062f377..d4ba67f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/CancelQuitCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/CancelQuitCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class CancelQuitCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetAccountCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetAccountCommand.java
index e51d582..63b4c7c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetAccountCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetAccountCommand.java
@@ -1,11 +1,12 @@
 package com.deepclone.lw.cmd.player.account;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 public class GetAccountCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetAccountResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetAccountResponse.java
index 3deb26c..913f598 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetAccountResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetAccountResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.account;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.account.AccountData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class GetAccountResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final AccountData account;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetLanguageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetLanguageCommand.java
index b60f61c..f86484f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetLanguageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetLanguageCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class GetLanguageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetLanguageResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetLanguageResponse.java
index c18ad3d..d6d2bc4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetLanguageResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/GetLanguageResponse.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -9,7 +10,7 @@ public class GetLanguageResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String language;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/QuitGameCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/QuitGameCommand.java
index c0a4776..a3bfd28 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/QuitGameCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/QuitGameCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class QuitGameCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String reason;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetAddressCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetAddressCommand.java
index 3362047..8ec0ddb 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetAddressCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetAddressCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SetAddressCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String sha1Auth;
 	private final String md5Auth;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetAddressResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetAddressResponse.java
index b281611..1f780e0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetAddressResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetAddressResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.account;
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.account.AccountData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class SetAddressResponse
 		extends GetAccountResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	public static enum AddressChangeStatus {
 		OK ,
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetLanguageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetLanguageCommand.java
index 75b7984..c38d16e 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetLanguageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetLanguageCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SetLanguageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String language;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPasswordCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPasswordCommand.java
index ef777be..55f2aaf 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPasswordCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPasswordCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class SetPasswordCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String sha1Auth;
 	private final String md5Auth;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPasswordResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPasswordResponse.java
index 3d6e71d..93fe53a 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPasswordResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPasswordResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.account;
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.account.AccountData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class SetPasswordResponse
 		extends GetAccountResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	public static enum PasswordChangeStatus {
 		OK ,
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPreferencesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPreferencesCommand.java
index d1c4057..9d9b293 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPreferencesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/SetPreferencesCommand.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.account;
 
 import java.util.Map;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -11,7 +12,7 @@ public class SetPreferencesCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Map< String , String > values;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ToggleVacationCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ToggleVacationCommand.java
index da6d81a..4e910ee 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ToggleVacationCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ToggleVacationCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class ToggleVacationCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ValidateSetAddressCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ValidateSetAddressCommand.java
index 69ad994..feb53cd 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ValidateSetAddressCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ValidateSetAddressCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.account;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ValidateSetAddressCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean cancel;
 	private final String code;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ValidateSetAddressResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ValidateSetAddressResponse.java
index 5f1f15a..52b8cb0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ValidateSetAddressResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/account/ValidateSetAddressResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.account;
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.account.AccountData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class ValidateSetAddressResponse
 		extends GetAccountResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean codeError;
 	private final String code;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/AllianceStatusCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/AllianceStatusCommand.java
index ceeb01c..a8d7220 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/AllianceStatusCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/AllianceStatusCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class AllianceStatusCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/AllianceStatusResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/AllianceStatusResponse.java
index f2eea70..63fb7c2 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/AllianceStatusResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/AllianceStatusResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.alliances;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.alliance.AllianceData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class AllianceStatusResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final AllianceData alliance;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CancelJoinCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CancelJoinCommand.java
index 54af717..5610612 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CancelJoinCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CancelJoinCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class CancelJoinCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CreateAllianceCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CreateAllianceCommand.java
index 5e250e7..8ca082f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CreateAllianceCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CreateAllianceCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -8,7 +9,7 @@ import com.deepclone.lw.session.Command;
 public class CreateAllianceCommand
 		extends Command
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String tag;
 	private final String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CreateAllianceResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CreateAllianceResponse.java
index c484453..133e48f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CreateAllianceResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/CreateAllianceResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.alliances;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.alliance.AllianceCreationStatus;
 import com.deepclone.lw.cmd.player.gdata.alliance.AllianceData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class CreateAllianceResponse
 		extends AllianceStatusResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final AllianceCreationStatus creation;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/JoinAllianceCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/JoinAllianceCommand.java
index e0fe9cd..5a6c4bf 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/JoinAllianceCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/JoinAllianceCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -8,7 +9,7 @@ import com.deepclone.lw.session.Command;
 public class JoinAllianceCommand
 		extends Command
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String tag;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/JoinAllianceResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/JoinAllianceResponse.java
index d544543..9bf4f40 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/JoinAllianceResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/JoinAllianceResponse.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.alliances;
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.alliance.AllianceData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class JoinAllianceResponse
 		extends AllianceStatusResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String joinFailure;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/KickMembersCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/KickMembersCommand.java
index fddf2a3..7c430db 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/KickMembersCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/KickMembersCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class KickMembersCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int[] members;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/LeaveAllianceCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/LeaveAllianceCommand.java
index 4cf33da..bb518ae 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/LeaveAllianceCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/LeaveAllianceCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class LeaveAllianceCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ManageRequestsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ManageRequestsCommand.java
index 826e1a1..7f78179 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ManageRequestsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ManageRequestsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ManageRequestsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int[] members;
 	private final boolean accept;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/TransferLeadershipCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/TransferLeadershipCommand.java
index 558d360..c0671df 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/TransferLeadershipCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/TransferLeadershipCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class TransferLeadershipCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int toMember;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ViewAllianceCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ViewAllianceCommand.java
index aa201bf..cea77b0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ViewAllianceCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ViewAllianceCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.alliances;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -8,7 +9,7 @@ import com.deepclone.lw.session.Command;
 public class ViewAllianceCommand
 		extends Command
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String tag;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ViewAllianceResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ViewAllianceResponse.java
index 2f3e083..29da170 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ViewAllianceResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/alliances/ViewAllianceResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.alliances;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.alliance.AllianceData;
 import com.deepclone.lw.cmd.player.gdata.alliance.PublicAllianceInformation;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class ViewAllianceResponse
 		extends AllianceStatusResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String requested;
 	private final PublicAllianceInformation info;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/GetBattleCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/GetBattleCommand.java
index 555c2f4..c65ef39 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/GetBattleCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/GetBattleCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.battles;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GetBattleCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long battle;
 	private final Long tick;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/GetBattleResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/GetBattleResponse.java
index bf46be5..ce78d6b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/GetBattleResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/GetBattleResponse.java
@@ -4,13 +4,14 @@ package com.deepclone.lw.cmd.player.battles;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.battles.BattleView;
+import com.deepclone.lw.session.API;
 
 
 
 public class GetBattleResponse
 		extends GameResponseBase
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private final BattleView battle;
 
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/ListBattlesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/ListBattlesCommand.java
index 6b8041a..9ee8ee7 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/ListBattlesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/ListBattlesCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.battles;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ListBattlesCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int page;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/ListBattlesResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/ListBattlesResponse.java
index a4856c7..2681b84 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/ListBattlesResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/battles/ListBattlesResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.battles.BattleListEntry;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class ListBattlesResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private final List< BattleListEntry > list;
 	private final int currentPage;
 	private final int pages;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ListBugsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ListBugsResponse.java
index 6481ab0..21e84ee 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ListBugsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ListBugsResponse.java
@@ -7,6 +7,7 @@ import com.deepclone.lw.cmd.bt.data.BugReport;
 import com.deepclone.lw.cmd.bt.data.BugStatus;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.session.API;
 
 
 
@@ -14,7 +15,7 @@ public class ListBugsResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BugStatus status;
 	private final boolean ownOnly;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/PostCommentResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/PostCommentResponse.java
index 2c7c353..97af985 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/PostCommentResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/PostCommentResponse.java
@@ -7,6 +7,7 @@ import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.bt.data.BugEvent;
 import com.deepclone.lw.cmd.bt.data.BugReport;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -14,7 +15,7 @@ public class PostCommentResponse
 		extends ViewBugResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean posted;
 	private final ObjectNameError commentError;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ReportBugResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ReportBugResponse.java
index 6fb47c9..c5657cb 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ReportBugResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ReportBugResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.bt;
 import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class ReportBugResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long bugId;
 	private final ObjectNameError titleError;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ViewBugResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ViewBugResponse.java
index 0f487c5..a7d18f3 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ViewBugResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/bt/ViewBugResponse.java
@@ -7,6 +7,7 @@ import com.deepclone.lw.cmd.bt.data.BugEvent;
 import com.deepclone.lw.cmd.bt.data.BugReport;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.session.API;
 
 
 
@@ -14,7 +15,7 @@ public class ViewBugResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BugReport report;
 	private final List< BugEvent > events;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/AddEnemyCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/AddEnemyCommand.java
index f4342b2..5ee13fd 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/AddEnemyCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/AddEnemyCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.elist;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class AddEnemyCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean alliance;
 	private final String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/AddEnemyResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/AddEnemyResponse.java
index 0e3e7e2..99f369f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/AddEnemyResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/AddEnemyResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 import com.deepclone.lw.cmd.ObjectNameError;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class AddEnemyResponse
 		extends EnemyListResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final ObjectNameError error;
 	private final boolean alliance;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/EnemyListCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/EnemyListCommand.java
index d72bf7d..c8e85a1 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/EnemyListCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/EnemyListCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.elist;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class EnemyListCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/EnemyListResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/EnemyListResponse.java
index 85ebd0d..8abccc6 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/EnemyListResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/EnemyListResponse.java
@@ -6,13 +6,14 @@ import java.util.List;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
 public class EnemyListResponse
 		extends GameResponseBase
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< NameIdPair > empires;
 	private final List< NameIdPair > alliances;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/RemoveEnemiesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/RemoveEnemiesCommand.java
index 9a3ffbe..617ea27 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/RemoveEnemiesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/elist/RemoveEnemiesCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.elist;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class RemoveEnemiesCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean alliance;
 	private final int[] identifiers;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/DisbandFleetsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/DisbandFleetsCommand.java
index 4ac3589..a61e613 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/DisbandFleetsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/DisbandFleetsCommand.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.fleets;
 
+import com.deepclone.lw.session.API;
+
 
 public class DisbandFleetsCommand
 		extends MultiFleetsCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean confirm;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/DisbandFleetsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/DisbandFleetsResponse.java
index 9eace3b..ec51c4b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/DisbandFleetsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/DisbandFleetsResponse.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.fleets;
 
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -9,7 +10,7 @@ public class DisbandFleetsResponse
 		extends MultiFleetsResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 
 	public DisbandFleetsResponse( GamePageData page )
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MergeFleetsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MergeFleetsCommand.java
index f5029ce..24ba678 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MergeFleetsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MergeFleetsCommand.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.fleets;
 
+import com.deepclone.lw.session.API;
+
 
 public class MergeFleetsCommand
 		extends MultiFleetsCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 
 	public MergeFleetsCommand( long[] fleets )
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MergeFleetsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MergeFleetsResponse.java
index f0e92a5..642dde5 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MergeFleetsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MergeFleetsResponse.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.fleets;
 
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -9,7 +10,7 @@ public class MergeFleetsResponse
 		extends MultiFleetsResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 
 	public MergeFleetsResponse( GamePageData page )
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MoveFleetsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MoveFleetsCommand.java
index 1efee46..8a8c811 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MoveFleetsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MoveFleetsCommand.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.fleets;
 
+import com.deepclone.lw.session.API;
+
 
 public class MoveFleetsCommand
 		extends MultiFleetsCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	
 	private final String destination;
 	private final Boolean mode;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MoveFleetsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MoveFleetsResponse.java
index e524398..77f9631 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MoveFleetsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MoveFleetsResponse.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.fleets;
 
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -9,7 +10,7 @@ public class MoveFleetsResponse
 		extends MultiFleetsResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private boolean error;
 	private String destination;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MultiFleetsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MultiFleetsCommand.java
index 4ebc86c..ef11bda 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MultiFleetsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MultiFleetsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.fleets;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -8,7 +9,7 @@ import com.deepclone.lw.session.Command;
 public abstract class MultiFleetsCommand
 		extends Command
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long fleets[];
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MultiFleetsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MultiFleetsResponse.java
index 3728e18..a7bea2b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MultiFleetsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/MultiFleetsResponse.java
@@ -7,6 +7,7 @@ import java.util.List;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.fleets.ShortFleetView;
+import com.deepclone.lw.session.API;
 
 
 
@@ -14,7 +15,7 @@ public abstract class MultiFleetsResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< ShortFleetView > fleets = new LinkedList< ShortFleetView >( );
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/RenameFleetsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/RenameFleetsCommand.java
index 95393e6..1dd1331 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/RenameFleetsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/RenameFleetsCommand.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.fleets;
 
+import com.deepclone.lw.session.API;
+
 
 public class RenameFleetsCommand
 		extends MultiFleetsCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean rename;
 	private final String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/RenameFleetsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/RenameFleetsResponse.java
index e20a569..d5b792d 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/RenameFleetsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/RenameFleetsResponse.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.fleets;
 
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -9,7 +10,7 @@ public class RenameFleetsResponse
 		extends MultiFleetsResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private boolean error;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SetFleetsModeCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SetFleetsModeCommand.java
index 3f44ab3..4a253bd 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SetFleetsModeCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SetFleetsModeCommand.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.fleets;
 
+import com.deepclone.lw.session.API;
+
 
 public class SetFleetsModeCommand
 		extends MultiFleetsCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean attack;
 	private final boolean confirm;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SetFleetsModeResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SetFleetsModeResponse.java
index edf245c..bc515ef 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SetFleetsModeResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SetFleetsModeResponse.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.fleets;
 
 
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.session.API;
 
 
 
@@ -9,7 +10,7 @@ public class SetFleetsModeResponse
 		extends MultiFleetsResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private boolean attack;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SplitFleetCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SplitFleetCommand.java
index 686a3c2..dbdbacd 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SplitFleetCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SplitFleetCommand.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.fleets;
 
 import java.util.Map;
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -11,7 +12,7 @@ public class SplitFleetCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long fleet;
 	private final Map< Integer , Integer > ships;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SplitFleetResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SplitFleetResponse.java
index 6351066..c59f9d2 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SplitFleetResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/SplitFleetResponse.java
@@ -8,6 +8,7 @@ import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.fleets.ShortFleetView;
 import com.deepclone.lw.cmd.player.gdata.fleets.SplitShips;
+import com.deepclone.lw.session.API;
 
 
 
@@ -15,7 +16,7 @@ public class SplitFleetResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private ShortFleetView initialFleet;
 	private final List< SplitShips > ships = new LinkedList< SplitShips >( );
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/ViewFleetsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/ViewFleetsCommand.java
index 67f33d3..ab631d1 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/ViewFleetsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/ViewFleetsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.fleets;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class ViewFleetsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/ViewFleetsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/ViewFleetsResponse.java
index d1c6484..2b53ce4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/ViewFleetsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/fleets/ViewFleetsResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.fleets;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.fleets.FleetsView;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class ViewFleetsResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private final FleetsView fleets;
 
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GamePageData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GamePageData.java
index 82e1767..16fdf2c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GamePageData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GamePageData.java
@@ -6,13 +6,15 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class GamePageData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 	private final String empire;
 	private final Character special;
 	private final String alliance;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GameResponseBase.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GameResponseBase.java
index f535b9d..45de365 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GameResponseBase.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GameResponseBase.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.gdata;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.CommandResponse;
 
 
@@ -9,7 +10,7 @@ public abstract class GameResponseBase
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final GamePageData page;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GameTime.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GameTime.java
index ac2c9b1..9aa4302 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GameTime.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/GameTime.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class GameTime
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int years;
 	private final int weeks;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/NameIdPair.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/NameIdPair.java
index 4bea9e4..11010d0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/NameIdPair.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/NameIdPair.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class NameIdPair
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 	private final String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListData.java
index 27c4571..4a1bf05 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetListData.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PlanetListData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetReference.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetReference.java
index fb111b3..277ff4f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetReference.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/PlanetReference.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PlanetReference
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long identifier;
 	private final String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/ShortBattleView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/ShortBattleView.java
index 111f17e..92ad295 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/ShortBattleView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/ShortBattleView.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class ShortBattleView
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long id;
 	private long friendly;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/TimeCombo.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/TimeCombo.java
index 4f180d2..891da10 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/TimeCombo.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/TimeCombo.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class TimeCombo
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long ticks;
 	private GameTime gameTime;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/AccountData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/AccountData.java
index 8b94e01..8027ffa 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/AccountData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/AccountData.java
@@ -8,6 +8,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.ext.ListLanguagesResponse;
 import com.deepclone.lw.cmd.player.gdata.GameTime;
+import com.deepclone.lw.session.API;
 
 
 
@@ -15,7 +16,7 @@ public class AccountData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String address;
 	private int gameCredits;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/MailChangeData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/MailChangeData.java
index 65957a1..f930ae4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/MailChangeData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/MailChangeData.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.player.gdata.account;
 import java.io.Serializable;
 import java.sql.Timestamp;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class MailChangeData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Timestamp until;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefCategory.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefCategory.java
index 6641104..0c4591b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefCategory.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefCategory.java
@@ -5,13 +5,15 @@ import java.io.Serializable;
 import java.util.LinkedList;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PrefCategory
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String name;
 	private List< PrefValue > preferences = new LinkedList< PrefValue >( );
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefChoice.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefChoice.java
index fe45669..54d9b83 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefChoice.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefChoice.java
@@ -3,12 +3,14 @@ package com.deepclone.lw.cmd.player.gdata.account;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PrefChoice
 		implements Serializable
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String value;
 	private String display;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefValue.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefValue.java
index 1ed93ed..a8b598a 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefValue.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/account/PrefValue.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.player.gdata.account;
 import java.io.Serializable;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PrefValue
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String id;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceCreationStatus.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceCreationStatus.java
index 241b5a3..19b52a2 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceCreationStatus.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceCreationStatus.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.gdata.alliance;
 import java.io.Serializable;
 
 import com.deepclone.lw.cmd.ObjectNameError;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class AllianceCreationStatus
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String tag;
 	private final ObjectNameError tagError;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceData.java
index 08b670c..442e7ce 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceData.java
@@ -3,6 +3,8 @@ package com.deepclone.lw.cmd.player.gdata.alliance;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 
@@ -10,7 +12,7 @@ public class AllianceData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final PublicAllianceInformation main;
 	private final AllianceMemberData member;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceLeaderData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceLeaderData.java
index a5293df..fda58ba 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceLeaderData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceLeaderData.java
@@ -5,6 +5,7 @@ import java.io.Serializable;
 import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class AllianceLeaderData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< NameIdPair > requests;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceMemberData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceMemberData.java
index 48f35a3..ac147a8 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceMemberData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AllianceMemberData.java
@@ -5,6 +5,7 @@ import java.io.Serializable;
 import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class AllianceMemberData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< NameIdPair > members;
 	private final List< AlliancePlanetData > planets;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AlliancePlanetData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AlliancePlanetData.java
index a17aa8a..b0acb87 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AlliancePlanetData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/AlliancePlanetData.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.alliance;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class AlliancePlanetData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/PublicAllianceInformation.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/PublicAllianceInformation.java
index 76463c1..ebee786 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/PublicAllianceInformation.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/alliance/PublicAllianceInformation.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.alliance;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PublicAllianceInformation
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 	private final String tag;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleDescription.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleDescription.java
index 22eed1e..80b8217 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleDescription.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleDescription.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.gdata.battles;
 import java.io.Serializable;
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class BattleDescription
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long id;
 	private NameIdPair location;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleDisplay.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleDisplay.java
index e6d94d0..4e1ea82 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleDisplay.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleDisplay.java
@@ -6,6 +6,7 @@ import java.util.LinkedList;
 import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.TimeCombo;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class BattleDisplay
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private TimeCombo current;
 	private TimeCombo previous;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleEvent.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleEvent.java
index 6e99a05..340be4b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleEvent.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleEvent.java
@@ -5,13 +5,15 @@ import java.io.Serializable;
 import java.util.LinkedList;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BattleEvent
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private BattleEventType type;
 	private boolean planet;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleHistoryInterval.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleHistoryInterval.java
index 7c15e5e..c2f5be5 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleHistoryInterval.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleHistoryInterval.java
@@ -6,6 +6,7 @@ import java.util.LinkedList;
 import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.TimeCombo;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class BattleHistoryInterval
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private TimeCombo begin;
 	private boolean battleBegins;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleHistoryTick.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleHistoryTick.java
index 526de4f..12929fe 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleHistoryTick.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleHistoryTick.java
@@ -6,6 +6,7 @@ import java.util.LinkedList;
 import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.TimeCombo;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class BattleHistoryTick
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final TimeCombo time;
 	private final List< BattleEvent > events = new LinkedList< BattleEvent >( );
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleListEntry.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleListEntry.java
index aca40ad..fddda87 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleListEntry.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleListEntry.java
@@ -5,6 +5,7 @@ import java.io.Serializable;
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.TimeCombo;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class BattleListEntry
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long id;
 	private NameIdPair location;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattlePlanetBuildings.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattlePlanetBuildings.java
index d416dd1..2fc4cbe 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattlePlanetBuildings.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattlePlanetBuildings.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.gdata.battles;
 
 
 import com.deepclone.lw.cmd.player.gdata.PlanetRelationType;
+import com.deepclone.lw.session.API;
 
 
 
@@ -9,7 +10,7 @@ public class BattlePlanetBuildings
 		extends BattleShipsList
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private PlanetRelationType relation;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattlePlayerShips.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattlePlayerShips.java
index af042d8..66b2c38 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattlePlayerShips.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattlePlayerShips.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.gdata.battles;
 
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
@@ -9,7 +10,7 @@ public class BattlePlayerShips
 		extends BattleShipsList
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private NameIdPair player;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipType.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipType.java
index 3133440..65d5129 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipType.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipType.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.battles;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BattleShipType
 		implements Serializable , Comparable< BattleShipType >
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String name;
 	private long cAmount;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipsList.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipsList.java
index 389771b..922d4c4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipsList.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipsList.java
@@ -5,13 +5,15 @@ import java.io.Serializable;
 import java.util.LinkedList;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public abstract class BattleShipsList
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	protected List< BattleShipType > ships = new LinkedList< BattleShipType >( );
 	protected long cPower = 0;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipsView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipsView.java
index d38d726..bd7f6f2 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipsView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleShipsView.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.battles;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BattleShipsView
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final BattlePlayerShips own = new BattlePlayerShips( );
 	private final BattleSideShips friendly = new BattleSideShips( false );
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleSideShips.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleSideShips.java
index 6dca7ad..613069f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleSideShips.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleSideShips.java
@@ -5,13 +5,15 @@ import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BattleSideShips
 		extends BattleShipsList
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean hostile;
 	private final List< BattlePlayerShips > players = new LinkedList< BattlePlayerShips >( );
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleView.java
index c9fc728..31acef8 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/battles/BattleView.java
@@ -5,13 +5,15 @@ import java.io.Serializable;
 import java.util.LinkedList;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BattleView
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private BattleDescription description;
 	private BattleDisplay display;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/OverviewData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/OverviewData.java
index d82cc66..fabcfad 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/OverviewData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/OverviewData.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.empire;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class OverviewData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long planets;
 	private int newMessages;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
new file mode 100644
index 0000000..5af296b
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchData.java
@@ -0,0 +1,224 @@
+package com.deepclone.lw.cmd.player.gdata.empire;
+
+
+import java.io.Serializable;
+
+import com.deepclone.lw.session.API;
+
+
+
+public class ResearchData
+		implements Serializable , Comparable< ResearchData >
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final String identifier;
+
+	private int completion;
+
+	private String category;
+	private String name;
+	private String description;
+
+	private Integer priority;
+	private Integer cost;
+
+	private String[] dependsOn;
+
+
+	/**
+	 * Create a research data record for a technology that hasn't been identified yet.
+	 * 
+	 * @param numericId
+	 *            numeric identifier of the technology
+	 * @param category
+	 *            name of the category the technology belongs to
+	 * @param completion
+	 *            completion percentage
+	 * @param priority
+	 *            research priority
+	 */
+	public ResearchData( int numericId , String category , int completion , int priority )
+	{
+		this.identifier = "unknown-" + numericId;
+		this.completion = completion;
+		this.priority = priority;
+		this.category = category;
+		this.name = null;
+		this.description = null;
+		this.cost = null;
+		this.dependsOn = null;
+	}
+
+
+	/**
+	 * Create a research data record for an identified technology that is still being researched.
+	 * 
+	 * @param identifier
+	 *            technology identifier
+	 * @param category
+	 *            name of the category the technology belongs to
+	 * @param name
+	 *            name of the technology
+	 * @param description
+	 *            description of the technology
+	 * @param dependsOn
+	 *            identifiers of the technologies this technology depends on
+	 * @param completion
+	 *            completion percentage
+	 * @param priority
+	 *            research priority
+	 */
+	public ResearchData( String identifier , String category , String name , String description , String[] dependsOn ,
+			int completion , int priority )
+	{
+		this.identifier = identifier;
+		this.category = category;
+		this.name = name;
+		this.description = description;
+		this.dependsOn = dependsOn.clone( );
+		this.completion = completion;
+		this.priority = priority;
+		this.cost = null;
+	}
+
+
+	/**
+	 * Create a research data record for a technology that needs to be implemented.
+	 * 
+	 * @param identifier
+	 *            technology identifier
+	 * @param category
+	 *            name of the category the technology belongs to
+	 * @param name
+	 *            name of the technology
+	 * @param description
+	 *            description of the technology
+	 * @param dependsOn
+	 *            identifiers of the technologies this technology depends on
+	 * @param cost
+	 *            research priority
+	 */
+	public ResearchData( String identifier , String category , String name , String description , String[] dependsOn ,
+			int cost )
+	{
+		this.identifier = identifier;
+		this.category = category;
+		this.name = name;
+		this.description = description;
+		this.dependsOn = dependsOn.clone( );
+		this.completion = 100;
+		this.priority = null;
+		this.cost = cost;
+	}
+
+
+	@Override
+	public int compareTo( ResearchData o )
+	{
+		if ( this.cost != null && o.cost == null ) {
+			return -1;
+		} else if ( this.cost == null && o.cost != null ) {
+			return 1;
+		} else if ( this.name != null && o.name != null ) {
+			return this.name.compareTo( o.name );
+		} else if ( this.name != null ) {
+			return -1;
+		} else if ( o.name != null ) {
+			return 1;
+		}
+		return this.identifier.compareTo( o.identifier );
+	}
+
+
+	public int getCompletion( )
+	{
+		return completion;
+	}
+
+
+	public void setCompletion( int completion )
+	{
+		this.completion = completion;
+	}
+
+
+	public String getCategory( )
+	{
+		return category;
+	}
+
+
+	public void setCategory( String category )
+	{
+		this.category = category;
+	}
+
+
+	public String getName( )
+	{
+		return name;
+	}
+
+
+	public void setName( String name )
+	{
+		this.name = name;
+	}
+
+
+	public String getDescription( )
+	{
+		return description;
+	}
+
+
+	public void setDescription( String description )
+	{
+		this.description = description;
+	}
+
+
+	public Integer getPriority( )
+	{
+		return priority;
+	}
+
+
+	public void setPriority( Integer priority )
+	{
+		this.priority = priority;
+	}
+
+
+	public Integer getCost( )
+	{
+		return cost;
+	}
+
+
+	public void setCost( Integer cost )
+	{
+		this.cost = cost;
+	}
+
+
+	public String[] getDependsOn( )
+	{
+		return dependsOn;
+	}
+
+
+	public void setDependsOn( String[] dependsOn )
+	{
+		this.dependsOn = dependsOn;
+	}
+
+
+	public String getIdentifier( )
+	{
+		return identifier;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchLineData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchLineData.java
deleted file mode 100644
index a48f027..0000000
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/ResearchLineData.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.deepclone.lw.cmd.player.gdata.empire;
-
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-
-
-public class ResearchLineData
-		implements Serializable , Comparable< ResearchLineData >
-{
-
-	private static final long serialVersionUID = 1L;
-
-	private final long id;
-	private final String name;
-	private final String description;
-	private final List< TechnologyData > implemented;
-	private final TechnologyData current;
-
-
-	public ResearchLineData( long id , String name , String description , List< TechnologyData > implemented ,
-			TechnologyData current )
-	{
-		this.id = id;
-		this.name = name;
-		this.description = description;
-		this.implemented = Collections.unmodifiableList( implemented );
-		this.current = current;
-	}
-
-
-	@Override
-	public int compareTo( ResearchLineData other )
-	{
-		return this.name.compareTo( other.name );
-	}
-
-
-	public long getId( )
-	{
-		return id;
-	}
-
-
-	public String getName( )
-	{
-		return name;
-	}
-
-
-	public String getDescription( )
-	{
-		return description;
-	}
-
-
-	public List< TechnologyData > getImplemented( )
-	{
-		return implemented;
-	}
-
-
-	public TechnologyData getCurrent( )
-	{
-		return current;
-	}
-
-}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyCategoryData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyCategoryData.java
new file mode 100644
index 0000000..67d3dc1
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyCategoryData.java
@@ -0,0 +1,55 @@
+package com.deepclone.lw.cmd.player.gdata.empire;
+
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.session.API;
+
+
+
+public class TechnologyCategoryData
+		implements Serializable , Comparable< TechnologyCategoryData >
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final String name;
+	private final String description;
+	private final List< TechnologyData > technologies;
+
+
+	public TechnologyCategoryData( String name , String description , List< TechnologyData > technologies )
+	{
+		this.name = name;
+		this.description = description;
+		this.technologies = new LinkedList< TechnologyData >( technologies );
+	}
+
+
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	public String getDescription( )
+	{
+		return this.description;
+	}
+
+
+	public List< TechnologyData > getTechnologies( )
+	{
+		return this.technologies;
+	}
+
+
+	@Override
+	public int compareTo( TechnologyCategoryData o )
+	{
+		return this.name.compareTo( o.name );
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyData.java
index 7ea35f0..a598f72 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/empire/TechnologyData.java
@@ -2,69 +2,71 @@ package com.deepclone.lw.cmd.player.gdata.empire;
 
 
 import java.io.Serializable;
+import java.util.List;
+
+import com.deepclone.lw.session.API;
 
 
 
 public class TechnologyData
-		implements Serializable
+		implements Serializable , Comparable< TechnologyData >
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
+	private final String identifier;
 	private final String name;
 	private final String description;
-	private final Integer researched;
-	private final Long cost;
+
+	private final List< String > dependsOn;
+	private final List< String > dependencyOf;
 
 
-	public TechnologyData( String name , String description )
+	public TechnologyData( String identifier , String name , String description , List< String > dependsOn ,
+			List< String > dependencyOf )
 	{
+		this.identifier = identifier;
 		this.name = name;
 		this.description = description;
-		this.researched = null;
-		this.cost = null;
+		this.dependsOn = dependsOn;
+		this.dependencyOf = dependencyOf;
 	}
 
 
-	public TechnologyData( String name , String description , int researched )
+	public String getIdentifier( )
 	{
-		this.name = name;
-		this.description = description;
-		this.researched = researched;
-		this.cost = null;
-	}
-
-
-	public TechnologyData( String name , String description , int researched , long cost )
-	{
-		this.name = name;
-		this.description = description;
-		this.researched = researched;
-		this.cost = cost;
+		return this.identifier;
 	}
 
 
 	public String getName( )
 	{
-		return name;
+		return this.name;
 	}
 
 
 	public String getDescription( )
 	{
-		return description;
+		return this.description;
 	}
 
 
-	public Integer getResearched( )
+	public List< String > getDependsOn( )
 	{
-		return researched;
+		return this.dependsOn;
 	}
 
 
-	public Long getCost( )
+	public List< String > getDependencyOf( )
 	{
-		return cost;
+		return this.dependencyOf;
+	}
+
+
+	@Override
+	public int compareTo( TechnologyData o )
+	{
+		return this.name.compareTo( o.name );
 	}
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetLocation.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetLocation.java
index 464936b..b3e259b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetLocation.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetLocation.java
@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.ShortBattleView;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class FleetLocation
 		extends NameIdPair
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private boolean own;
 	private boolean attacking;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetOwner.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetOwner.java
index 2653142..d88bfed 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetOwner.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetOwner.java
@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.PlanetRelationType;
+import com.deepclone.lw.session.API;
 
 
 
@@ -15,7 +16,7 @@ public class FleetOwner
 		extends NameIdPair
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final PlanetRelationType relation;
 	private final List< StaticFleet > fleets = new LinkedList< StaticFleet >( );
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetShips.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetShips.java
index a29a8a6..074a691 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetShips.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetShips.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.fleets;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class FleetShips
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int type;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetsView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetsView.java
index dc16fe4..3fbeed6 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetsView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/FleetsView.java
@@ -5,13 +5,15 @@ import java.io.Serializable;
 import java.util.LinkedList;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class FleetsView
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< FleetLocation > locations = new LinkedList< FleetLocation >( );
 	private final List< MovingFleet > moving = new LinkedList< MovingFleet >( );
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/MovingFleet.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/MovingFleet.java
index e37a38f..3bc0311 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/MovingFleet.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/MovingFleet.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.gdata.fleets;
 
 import com.deepclone.lw.cmd.player.gdata.GameTime;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class MovingFleet
 		extends StaticFleet
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long timeLeft;
 	private boolean attacking;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/ShortFleetView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/ShortFleetView.java
index a19833a..4f273bd 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/ShortFleetView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/ShortFleetView.java
@@ -3,6 +3,7 @@ package com.deepclone.lw.cmd.player.gdata.fleets;
 
 import com.deepclone.lw.cmd.player.gdata.GameTime;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
@@ -10,7 +11,7 @@ public class ShortFleetView
 		extends NameIdPair
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int flightTime;
 	private GameTime gameFlightTime;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/SplitShips.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/SplitShips.java
index b464387..f36a872 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/SplitShips.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/SplitShips.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.gdata.fleets;
 
+import com.deepclone.lw.session.API;
+
 
 public class SplitShips
 		extends FleetShips
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private int selectedAmount = 0;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/StaticFleet.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/StaticFleet.java
index 32d9ac9..d07362d 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/StaticFleet.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/fleets/StaticFleet.java
@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.deepclone.lw.cmd.player.gdata.GameTime;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class StaticFleet
 		extends NameIdPair
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private FleetStatus status;
 	private long penalty;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/map/MapPlanetData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/map/MapPlanetData.java
index 8a04d19..783db11 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/map/MapPlanetData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/map/MapPlanetData.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.gdata.map;
 import java.io.Serializable;
 
 import com.deepclone.lw.cmd.player.gdata.PlanetRelationType;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class MapPlanetData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final long id;
 	private final int picture;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/map/MapSystemData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/map/MapSystemData.java
index db7a54a..c9c9f7d 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/map/MapSystemData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/map/MapSystemData.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.map;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class MapSystemData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final MapPlanetData planets[] = new MapPlanetData[ 5 ];
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableBuildingData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableBuildingData.java
index 34c5ce6..25ce6f7 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableBuildingData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableBuildingData.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.gdata.planets;
 
+import com.deepclone.lw.session.API;
+
 
 public class BuildableBuildingData
 		extends BuildableItemData
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int workers;
 	private String prodType;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableItemData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableItemData.java
index 5ae878e..32ab59d 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableItemData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableItemData.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.gdata.planets;
 import java.io.Serializable;
 
 import com.deepclone.lw.cmd.player.gdata.GameTime;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public abstract class BuildableItemData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int id;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableShipData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableShipData.java
index 99c6d5b..6277e0f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableShipData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildableShipData.java
@@ -1,13 +1,14 @@
 package com.deepclone.lw.cmd.player.gdata.planets;
 
 import com.deepclone.lw.cmd.player.gdata.GameTime;
+import com.deepclone.lw.session.API;
 
 
 public class BuildableShipData
 		extends BuildableItemData
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int power;
 	private int flightTime;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildingData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildingData.java
index 7647c24..c62df08 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildingData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/BuildingData.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.planets;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class BuildingData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private Integer id;
 	private String name;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/OwnPlanetStatusData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/OwnPlanetStatusData.java
index 6013826..34bc000 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/OwnPlanetStatusData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/OwnPlanetStatusData.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.gdata.planets;
 import java.io.Serializable;
 
 import com.deepclone.lw.cmd.player.gdata.GameTime;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class OwnPlanetStatusData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private boolean abandonPossible;
 	private boolean renamePossible;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetBasicView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetBasicView.java
index 31de86e..2e9adb4 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetBasicView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetBasicView.java
@@ -3,13 +3,15 @@ package com.deepclone.lw.cmd.player.gdata.planets;
 
 import java.io.Serializable;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PlanetBasicView
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String name;
 	private final int picture;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOrbitalView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOrbitalView.java
index 5992b9c..fc48a1d 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOrbitalView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOrbitalView.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.player.gdata.planets;
 import java.io.Serializable;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PlanetOrbitalView
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private long population;
 	private long defencePoints;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOwnView.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOwnView.java
index a567a8e..b61f708 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOwnView.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/PlanetOwnView.java
@@ -4,13 +4,15 @@ package com.deepclone.lw.cmd.player.gdata.planets;
 import java.io.Serializable;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class PlanetOwnView
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private int happiness;
 	private int hChange;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/QueueData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/QueueData.java
index 8aad730..90f4610 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/QueueData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/QueueData.java
@@ -5,13 +5,15 @@ import java.io.Serializable;
 import java.util.Collections;
 import java.util.List;
 
+import com.deepclone.lw.session.API;
+
 
 
 public class QueueData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean appendPossible;
 	private final List< QueueItemData > items;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/QueueItemData.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/QueueItemData.java
index a1000df..7de850f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/QueueItemData.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/gdata/planets/QueueItemData.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.gdata.planets;
 import java.io.Serializable;
 
 import com.deepclone.lw.cmd.player.gdata.GameTime;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class QueueItemData
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private String name;
 	private String description;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ComposeMessageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ComposeMessageCommand.java
index 8d7a441..d655d6b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ComposeMessageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ComposeMessageCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.msgs;
 
 
 import com.deepclone.lw.cmd.msgdata.MessageType;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class ComposeMessageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Boolean inbox;
 	private final Long replyTo;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ComposeMessageResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ComposeMessageResponse.java
index 45b95c0..f9b82b2 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ComposeMessageResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ComposeMessageResponse.java
@@ -5,6 +5,7 @@ import com.deepclone.lw.cmd.msgdata.Message;
 import com.deepclone.lw.cmd.msgdata.MessageType;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class ComposeMessageResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private MessageType messageType;
 	private String target;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/GetMessagesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/GetMessagesCommand.java
index 92d24ef..3accf00 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/GetMessagesCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/GetMessagesCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.msgs;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class GetMessagesCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean inbox;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/GetMessagesResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/GetMessagesResponse.java
index 09025bc..94b67a7 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/GetMessagesResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/GetMessagesResponse.java
@@ -6,6 +6,7 @@ import java.util.List;
 import com.deepclone.lw.cmd.msgdata.MessageListEntry;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class GetMessagesResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< MessageListEntry > messages;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ListTargetsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ListTargetsCommand.java
index 9809a62..5974e5b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ListTargetsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ListTargetsCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.msgs;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,6 +10,6 @@ public class ListTargetsCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ListTargetsResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ListTargetsResponse.java
index 28aa092..6c5fd7f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ListTargetsResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ListTargetsResponse.java
@@ -8,6 +8,7 @@ import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.NameIdPair;
 import com.deepclone.lw.cmd.player.gdata.alliance.PublicAllianceInformation;
+import com.deepclone.lw.session.API;
 
 
 
@@ -15,7 +16,7 @@ public class ListTargetsResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final List< NameIdPair > empires;
 	private final List< PublicAllianceInformation > alliances;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/MessageBoxCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/MessageBoxCommand.java
index 757cd99..8891e05 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/MessageBoxCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/MessageBoxCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.msgs;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class MessageBoxCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final MessageBoxAction action;
 	private final boolean inbox;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/PrepareMessageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/PrepareMessageCommand.java
index 3abe09e..c6ce810 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/PrepareMessageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/PrepareMessageCommand.java
@@ -2,6 +2,7 @@ package com.deepclone.lw.cmd.player.msgs;
 
 
 import com.deepclone.lw.cmd.msgdata.MessageType;
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -10,7 +11,7 @@ public class PrepareMessageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final MessageType type;
 	private final Long id;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ReadMessageCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ReadMessageCommand.java
index 1656175..e1bd6fc 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ReadMessageCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ReadMessageCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.msgs;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ReadMessageCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean inbox;
 	private final long id;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ReadMessageResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ReadMessageResponse.java
index 436de68..fe110a9 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ReadMessageResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/msgs/ReadMessageResponse.java
@@ -4,6 +4,7 @@ package com.deepclone.lw.cmd.player.msgs;
 import com.deepclone.lw.cmd.msgdata.Message;
 import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.session.API;
 
 
 
@@ -11,7 +12,7 @@ public class ReadMessageResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean inbox;
 	private final Message message;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/AbandonPlanetCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/AbandonPlanetCommand.java
index 34711e2..149fd04 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/AbandonPlanetCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/AbandonPlanetCommand.java
@@ -1,10 +1,12 @@
 package com.deepclone.lw.cmd.player.planets;
 
+import com.deepclone.lw.session.API;
+
 
 public class AbandonPlanetCommand
 		extends ViewPlanetCommand
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean cancel;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildShipsCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildShipsCommand.java
index 62d8521..12c159b 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildShipsCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildShipsCommand.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.planets;
 
+import com.deepclone.lw.session.API;
+
 
 public class BuildShipsCommand
 		extends ViewPlanetCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int sType;
 	private final int amount;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildingActionCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildingActionCommand.java
index 4d7ec2b..53e985e 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildingActionCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildingActionCommand.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.planets;
 
+import com.deepclone.lw.session.API;
+
 
 public class BuildingActionCommand
 		extends ViewPlanetCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int bType;
 	private final int amount;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildingActionResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildingActionResponse.java
index 86a4daf..c1a30b8 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildingActionResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/BuildingActionResponse.java
@@ -5,6 +5,7 @@ import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetBasicView;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetOrbitalView;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetOwnView;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class BuildingActionResponse
 		extends ViewPlanetResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean destructionFailed;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/FlushQueueCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/FlushQueueCommand.java
index d2e1b08..5a456e0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/FlushQueueCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/FlushQueueCommand.java
@@ -1,10 +1,12 @@
 package com.deepclone.lw.cmd.player.planets;
 
+import com.deepclone.lw.session.API;
+
 
 public class FlushQueueCommand
 		extends ViewPlanetCommand
 {
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final boolean military;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/RenamePlanetCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/RenamePlanetCommand.java
index 09a586d..d0153ce 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/RenamePlanetCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/RenamePlanetCommand.java
@@ -1,11 +1,13 @@
 package com.deepclone.lw.cmd.player.planets;
 
+import com.deepclone.lw.session.API;
+
 
 public class RenamePlanetCommand
 		extends ViewPlanetCommand
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String name;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/RenamePlanetResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/RenamePlanetResponse.java
index 9073eb6..52b65e0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/RenamePlanetResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/RenamePlanetResponse.java
@@ -5,6 +5,7 @@ import com.deepclone.lw.cmd.player.gdata.GamePageData;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetBasicView;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetOrbitalView;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetOwnView;
+import com.deepclone.lw.session.API;
 
 
 
@@ -12,7 +13,7 @@ public class RenamePlanetResponse
 		extends ViewPlanetResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final String renamingTo;
 	private final String renameError;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ViewPlanetCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ViewPlanetCommand.java
index b3d1137..c4c7a6f 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ViewPlanetCommand.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ViewPlanetCommand.java
@@ -1,6 +1,7 @@
 package com.deepclone.lw.cmd.player.planets;
 
 
+import com.deepclone.lw.session.API;
 import com.deepclone.lw.session.Command;
 
 
@@ -9,7 +10,7 @@ public class ViewPlanetCommand
 		extends Command
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final int id;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ViewPlanetResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ViewPlanetResponse.java
index 6761652..03c4810 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ViewPlanetResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/planets/ViewPlanetResponse.java
@@ -6,6 +6,7 @@ import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetBasicView;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetOrbitalView;
 import com.deepclone.lw.cmd.player.gdata.planets.PlanetOwnView;
+import com.deepclone.lw.session.API;
 
 
 
@@ -13,7 +14,7 @@ public class ViewPlanetResponse
 		extends GameResponseBase
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private final Integer id;
 	private final boolean ownershipError;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ImplementTechCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ImplementTechCommand.java
new file mode 100644
index 0000000..d5187ac
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ImplementTechCommand.java
@@ -0,0 +1,29 @@
+package com.deepclone.lw.cmd.player.research;
+
+
+import com.deepclone.lw.session.API;
+import com.deepclone.lw.session.Command;
+
+
+
+public class ImplementTechCommand
+		extends Command
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final String tech;
+
+
+	public ImplementTechCommand( String tech )
+	{
+		this.tech = tech;
+	}
+
+
+	public String getTech( )
+	{
+		return tech;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ResearchOperationResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ResearchOperationResponse.java
new file mode 100644
index 0000000..9be7842
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ResearchOperationResponse.java
@@ -0,0 +1,87 @@
+package com.deepclone.lw.cmd.player.research;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+import com.deepclone.lw.cmd.player.gdata.empire.TechnologyCategoryData;
+import com.deepclone.lw.session.API;
+
+
+
+/**
+ * Response to a research operation ({@link SetResearchPrioritiesCommand} or
+ * {@link ImplementTechCommand}). When the operation was successful, the response does not carry any
+ * actual data; when an error occurs, however, it includes the standard research view.
+ * 
+ * @author tseeker
+ */
+public class ResearchOperationResponse
+		extends ViewResearchResponse
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	/**
+	 * Possible results of the operation
+	 */
+	public static enum Result {
+		/** The operation succeeded */
+		OK ,
+
+		/** Not enough resources (when the command was {@link ImplementTechCommand}) */
+		ERR_RESOURCES ,
+
+		/**
+		 * Technology already implemented (when the command was {@link ImplementTechCommand}) or
+		 * list of research topics modified (when the command was
+		 * {@link SetResearchPrioritiesCommand}).
+		 */
+		ERR_STATE_CHANGED ,
+
+		/** Invalid priority values (total is not 100, negative values, etc...) */
+		ERR_INVALID
+	}
+
+	/** Result of the operation */
+	private final Result result;
+
+
+	/** Create a response indicating a successful operation */
+	public ResearchOperationResponse( )
+	{
+		super( null , new ArrayList< ResearchData >( ) , new ArrayList< TechnologyCategoryData >( ) );
+		this.result = Result.OK;
+	}
+
+
+	/**
+	 * Create a response indicating an error during the operation.
+	 * 
+	 * @param page
+	 *            the basic page information
+	 * @param research
+	 *            the list of research topics
+	 * @param implemented
+	 *            the list of implemented technologies
+	 * @param result
+	 *            the error code.
+	 */
+	public ResearchOperationResponse( GamePageData page , List< ResearchData > research ,
+			List< TechnologyCategoryData > implemented , Result result )
+	{
+		super( page , research , implemented );
+		assert ( result != Result.OK );
+		this.result = result;
+	}
+
+
+	/** @return the result of the operation */
+	public Result getResult( )
+	{
+		return result;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/SetResearchPrioritiesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/SetResearchPrioritiesCommand.java
new file mode 100644
index 0000000..44134a7
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/SetResearchPrioritiesCommand.java
@@ -0,0 +1,32 @@
+package com.deepclone.lw.cmd.player.research;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.deepclone.lw.session.API;
+import com.deepclone.lw.session.Command;
+
+
+
+public class SetResearchPrioritiesCommand
+		extends Command
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final Map< String , Integer > priorities;
+
+
+	public SetResearchPrioritiesCommand( Map< String , Integer > priorities )
+	{
+		this.priorities = new HashMap< String , Integer >( priorities );
+	}
+
+
+	public Map< String , Integer > getPriorities( )
+	{
+		return priorities;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ViewResearchCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ViewResearchCommand.java
new file mode 100644
index 0000000..66c8cf6
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ViewResearchCommand.java
@@ -0,0 +1,15 @@
+package com.deepclone.lw.cmd.player.research;
+
+
+import com.deepclone.lw.session.API;
+import com.deepclone.lw.session.Command;
+
+
+
+public class ViewResearchCommand
+		extends Command
+{
+
+	private static final long serialVersionUID = API.Version;
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ViewResearchResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ViewResearchResponse.java
new file mode 100644
index 0000000..c11df9d
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/research/ViewResearchResponse.java
@@ -0,0 +1,45 @@
+package com.deepclone.lw.cmd.player.research;
+
+
+import java.util.Collections;
+import java.util.List;
+
+import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+import com.deepclone.lw.cmd.player.gdata.empire.TechnologyCategoryData;
+import com.deepclone.lw.session.API;
+
+
+
+public class ViewResearchResponse
+		extends GameResponseBase
+{
+
+	private static final long serialVersionUID = API.Version;
+
+	private final List< ResearchData > research;
+	private final List< TechnologyCategoryData > implementedTechnologies;
+
+
+	public ViewResearchResponse( GamePageData page , List< ResearchData > research ,
+			List< TechnologyCategoryData > implemented )
+	{
+		super( page );
+		this.research = Collections.unmodifiableList( research );
+		this.implementedTechnologies = Collections.unmodifiableList( implemented );
+	}
+
+
+	public List< ResearchData > getResearch( )
+	{
+		return this.research;
+	}
+
+
+	public List< TechnologyCategoryData > getImplementedTechnologies( )
+	{
+		return this.implementedTechnologies;
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/API.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/API.java
new file mode 100644
index 0000000..84e513e
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/API.java
@@ -0,0 +1,15 @@
+package com.deepclone.lw.session;
+
+
+public final class API
+{
+	
+	public static final long Version = 0x005099002;
+
+	/* Prevent construction */
+	private API( )
+	{
+		// EMPTY
+	}
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/Command.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/Command.java
index 5733eae..419f17d 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/Command.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/Command.java
@@ -8,7 +8,7 @@ import java.io.Serializable;
 public abstract class Command
 		implements Serializable
 {
-
-	private static final long serialVersionUID = 1L;
+	
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/CommandResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/CommandResponse.java
index bef3cd8..f9d0eaf 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/CommandResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/CommandResponse.java
@@ -9,6 +9,6 @@ public abstract class CommandResponse
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/NullResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/NullResponse.java
index a7f44ab..19dfc94 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/NullResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/NullResponse.java
@@ -5,6 +5,6 @@ public final class NullResponse
 		extends CommandResponse
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 }
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionCommandException.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionCommandException.java
index bc53083..671f59c 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionCommandException.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionCommandException.java
@@ -5,7 +5,7 @@ public class SessionCommandException
 		extends SessionException
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 
 	public SessionCommandException( String message )
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionException.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionException.java
index 8490fb9..708fdf9 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionException.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionException.java
@@ -5,7 +5,7 @@ public abstract class SessionException
 		extends Exception
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	private boolean keepSession;
 
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionIdentifierException.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionIdentifierException.java
index 4273f76..3218562 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionIdentifierException.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionIdentifierException.java
@@ -10,7 +10,7 @@ public final class SessionIdentifierException
 		extends SessionException
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 
 	public SessionIdentifierException( String identifier )
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionInternalException.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionInternalException.java
index 42474ee..f0b1614 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionInternalException.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionInternalException.java
@@ -10,7 +10,7 @@ public final class SessionInternalException
 		extends SessionException
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 
 	public SessionInternalException( boolean keep , Throwable cause )
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionReference.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionReference.java
index 4e7fb70..7605943 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionReference.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionReference.java
@@ -19,7 +19,7 @@ public final class SessionReference
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	/** String identifier of the session */
 	public final String identifier;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionResponse.java
index 6071671..2ebc3ac 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionResponse.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionResponse.java
@@ -14,7 +14,7 @@ public class SessionResponse
 		implements Serializable
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 	/** Updated session reference */
 	public final SessionReference session;
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionStateException.java b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionStateException.java
index eaa69e5..45ed6e0 100644
--- a/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionStateException.java
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/session/SessionStateException.java
@@ -10,7 +10,7 @@ public final class SessionStateException
 		extends SessionException
 {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = API.Version;
 
 
 	public SessionStateException( )
diff --git a/legacyworlds-utils/pom.xml b/legacyworlds-utils/pom.xml
index 195d0d6..cf05448 100644
--- a/legacyworlds-utils/pom.xml
+++ b/legacyworlds-utils/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-utils</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds common utilities</name>
 	<description>The classes in this package are used by all parts of the Legacy Worlds code.</description>
 
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/technologies.ftl b/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/technologies.ftl
new file mode 100644
index 0000000..dd03803
--- /dev/null
+++ b/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/types/technologies.ftl
@@ -0,0 +1,22 @@
+<@page title="Technologies">
+	<h1>Technologies</h1>
+	<#if data.categories?size == 0>
+		<p>No technologies have been defined.</p>
+	<#else>
+		<#list data.categories as category>
+			<ul><li><span style="font-size: larger">Category ${category.name?xhtml}</span>
+				(<a href="tech-category-${category.name?url}">edit</a>)
+				<#if category.techs?size == 0>
+					<ul><li>No technologies in this category.</li></ul>
+				<#else>
+					<ul>
+						<#list category.techs as tech>
+							<li>${tech?xhtml}</li>
+						</#list>
+					</ul>
+				</#if>
+			</li></ul>
+			<p>&nbsp;</p>
+		</#list>
+	</#if>
+</@page>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/version.ftl b/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/version.ftl
index c9406cf..e4abdb7 100644
--- a/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/version.ftl
+++ b/legacyworlds-web/legacyworlds-web-admin/WebContent/WEB-INF/fm/version.ftl
@@ -1,2 +1,2 @@
-<#macro version>Milestone 1</#macro>
-<#macro full_version>Beta 6 milestone 1 (5.99.1)</#macro>
\ No newline at end of file
+<#macro version>Milestone 2</#macro>
+<#macro full_version>Beta 6 milestone 2 (5.99.2)</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-admin/pom.xml b/legacyworlds-web/legacyworlds-web-admin/pom.xml
index 5d1f361..824b028 100644
--- a/legacyworlds-web/legacyworlds-web-admin/pom.xml
+++ b/legacyworlds-web/legacyworlds-web-admin/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-web</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 	
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-web-admin</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<packaging>war</packaging>
 	<name>Legacy Worlds administration site</name>
 
diff --git a/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/TechnologyPages.java b/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/TechnologyPages.java
new file mode 100644
index 0000000..ff32e08
--- /dev/null
+++ b/legacyworlds-web/legacyworlds-web-admin/src/main/java/com/deepclone/lw/web/admin/TechnologyPages.java
@@ -0,0 +1,37 @@
+package com.deepclone.lw.web.admin;
+
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import com.deepclone.lw.cmd.admin.techs.ListCategoriesResponse;
+import com.deepclone.lw.session.SessionException;
+import com.deepclone.lw.web.beans.intercept.SessionRequirement;
+import com.deepclone.lw.web.beans.session.SessionMaintenanceException;
+import com.deepclone.lw.web.beans.session.SessionServerException;
+import com.deepclone.lw.web.beans.view.PageControllerBase;
+import com.deepclone.lw.web.csess.AdminSession;
+
+
+
+@Controller
+@SessionRequirement( value = true , subType = "main" , redirectTo = "admin-session" )
+public class TechnologyPages
+		extends PageControllerBase
+{
+
+	@RequestMapping( "/techs" )
+	public String viewStatus( HttpServletRequest request , Model model )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		ListCategoriesResponse response = this.getSession( AdminSession.class , request ).getTechList( );
+		if ( !response.isPrivilegeOk( ) ) {
+			return this.redirect( "main" );
+		}
+		return this.render( model , "internal" , "en" , "technologies" , response );
+	}
+
+}
diff --git a/legacyworlds-web/legacyworlds-web-beans/pom.xml b/legacyworlds-web/legacyworlds-web-beans/pom.xml
index 5560487..faef876 100644
--- a/legacyworlds-web/legacyworlds-web-beans/pom.xml
+++ b/legacyworlds-web/legacyworlds-web-beans/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-web</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-web-beans</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<name>Legacy Worlds common web beans</name>
 	<description>Module for common web-related beans. </description>
 	<packaging>jar</packaging>
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/AdminSession.java b/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/AdminSession.java
index 2f89e3e..cd5522b 100644
--- a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/AdminSession.java
+++ b/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/AdminSession.java
@@ -17,6 +17,8 @@ import com.deepclone.lw.cmd.admin.msg.*;
 import com.deepclone.lw.cmd.admin.naming.*;
 import com.deepclone.lw.cmd.admin.prefs.*;
 import com.deepclone.lw.cmd.admin.su.*;
+import com.deepclone.lw.cmd.admin.techs.ListCategoriesCommand;
+import com.deepclone.lw.cmd.admin.techs.ListCategoriesResponse;
 import com.deepclone.lw.cmd.admin.tick.*;
 import com.deepclone.lw.cmd.admin.users.*;
 import com.deepclone.lw.cmd.bt.*;
@@ -539,4 +541,13 @@ public class AdminSession
 		this.execute( new EndMaintenanceCommand( ) );
 	}
 
+
+	/* Technology graph */
+
+	public ListCategoriesResponse getTechList( )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		return (ListCategoriesResponse) this.execute( new ListCategoriesCommand( ) );
+	}
+
 }
diff --git a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java b/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
index 1cc1607..70559b6 100644
--- a/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
+++ b/legacyworlds-web/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
@@ -17,6 +17,11 @@ import com.deepclone.lw.cmd.player.elist.*;
 import com.deepclone.lw.cmd.player.fleets.*;
 import com.deepclone.lw.cmd.player.gdata.*;
 import com.deepclone.lw.cmd.player.planets.*;
+import com.deepclone.lw.cmd.player.research.ImplementTechCommand;
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse;
+import com.deepclone.lw.cmd.player.research.SetResearchPrioritiesCommand;
+import com.deepclone.lw.cmd.player.research.ViewResearchCommand;
+import com.deepclone.lw.cmd.player.research.ViewResearchResponse;
 import com.deepclone.lw.cmd.player.msgs.*;
 import com.deepclone.lw.session.Command;
 import com.deepclone.lw.session.SessionException;
@@ -69,7 +74,7 @@ public class PlayerSession
 	}
 
 
-	/* Empire commands */
+	/* Overview */
 
 	public EmpireResponse getOverview( )
 			throws SessionException , SessionServerException , SessionMaintenanceException
@@ -78,10 +83,26 @@ public class PlayerSession
 	}
 
 
-	public EmpireResponse implementTechnology( int technology )
+	/* Technologies */
+
+	public ViewResearchResponse viewResearch( )
 			throws SessionException , SessionServerException , SessionMaintenanceException
 	{
-		return (EmpireResponse) this.execute( new ImplementTechCommand( technology ) );
+		return (ViewResearchResponse) this.execute( new ViewResearchCommand( ) );
+	}
+
+
+	public ResearchOperationResponse implementTechnology( String technology )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		return (ResearchOperationResponse) this.execute( new ImplementTechCommand( technology ) );
+	}
+
+
+	public ResearchOperationResponse setResearchPriorities( Map< String , Integer > priorities )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		return (ResearchOperationResponse) this.execute( new SetResearchPrioritiesCommand( priorities ) );
 	}
 
 
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/home.ftl b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/home.ftl
index a9dedfb..cc4a22b 100644
--- a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/home.ftl
+++ b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/static/home.ftl
@@ -1,4 +1,4 @@
-<@page title="Legacy Worlds Beta 6 - Milestone 1">
+<@page title="Legacy Worlds Beta 6 - Milestone 2">
 	<p>
 		Welcome to the first milestone release for Legacy Worlds' Beta 6.
 	</p>
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/overview.ftl b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/overview.ftl
index af04b79..b923794 100644
--- a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/overview.ftl
+++ b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/overview.ftl
@@ -2,7 +2,6 @@
 <@page title="Empire">
 
 	<#assign ov = data.overview >
-	<#assign rs = data.research >
 
 	<@tabs>
 
@@ -61,56 +60,6 @@
 		
 			</@right_column>
 		</@tab>
-		
-		<@tab id="research" title="Research">
-			<#if rs?size == 0>
-				<p>Our scientists are still settling in.</p>
-			</#if>
-			<#list rs as research>
-				<div>
-					<h3>${research.name?xhtml}</h3>
-					<p>${research.description?xhtml}</p>
-
-					<@left_column>
-						<#if research.implemented?size == 0>
-							<p>No usable technologies.</p>
-						<#else>
-							<@dt_main>
-								<#list research.implemented as tech>
-									<@dt_status>
-										${tech.name?xhtml}
-										<div class="auto-hide">${tech.description?xhtml}</div>
-									</@dt_status>
-								</#list>
-							</@dt_main>
-						</#if>
-					</@left_column>
-					
-					<#if research.current?has_content>
-						<@right_column>
-							<@dt_main>
-								<@dt_status>
-									Current research: <strong>${research.current.name?xhtml}</strong>
-									<p>
-										${research.current.description?xhtml}
-									</p>
-								</@dt_status>
-								<@dt_entry title="Progress">${research.current.researched}%</@dt_entry>
-								<#if research.current.cost?has_content>
-									<@dt_entry title="Cost">${research.current.cost?string(",##0")} <@abbr_bgc/></@dt_entry>
-									<#if data.page.cash gte research.current.cost && data.page.special! != 'v'>
-										<@dt_status><form action="implement-${research.id}.action#research" method="post">
-											<div><@ff_submit label="Implement technology" /></div>
-										</form></@dt_status>
-									</#if>
-								</#if>
-							</@dt_main>
-						</@right_column>
-					</#if>
-
-				</div>
-			</#list>
-		</@tab>
 
 	</@tabs>
 
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/technologies.ftl b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/technologies.ftl
new file mode 100644
index 0000000..954dfe8
--- /dev/null
+++ b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/en/types/technologies.ftl
@@ -0,0 +1,171 @@
+<#macro render_entry identifier title category description>
+	<div class="research-entry" id="research-${identifier?xhtml}">
+		<div class="title">${title?xhtml}</div>
+		<div class="category">Category: <b>${category?xhtml}</b></div>
+		<div class="description">${description?xhtml}</div>
+		<#nested>
+	</div>
+</#macro>
+<#macro render_implementable entry>
+	<@render_entry entry.identifier entry.name entry.category entry.description>
+		<div class="cost">Cost: <b>${entry.cost?string(",##0")}</b> <@abbr_bgc/></div>
+		<#if data.page.cash &gt;= entry.cost>
+			<form class="implement" name="implement-${entry.identifier?xhtml}" action="implement-tech.action" method="POST">
+				<input type="hidden" name="technology" value="${entry.identifier?xhtml}" />
+				<input type="submit" class="input" value="Implement technology" />
+			</form>
+		</#if> 
+	</@render_entry>
+</#macro>
+<#macro render_research entry title description display_priority_form>
+	<@render_entry entry.identifier title entry.category description>
+		<div class="progress">Progress: <span class="progress">${entry.completion}%</span></div>
+		<#if display_priority_form>
+			<div class="priority">
+				Priority:
+				<span class="priority">
+					<input type="text" maxlength="3" size="4" class="input priority-field" name="priority-${entry.identifier?xhtml}" id="priority-${entry.identifier?xhtml}" value="${entry.priority}" />%
+				</span> 
+			</div>
+		</#if>
+	</@render_entry>
+</#macro>
+<#macro render_unidentified entry display_priority_form>
+	<@render_research entry "Unidentified technology" "Our researchers are working on something new, but they have no clue where it will lead at this time." display_priority_form />
+</#macro>
+<#macro render_research_list entries>
+	<#local priority_form_started = false>
+	<#local priority_form_checked = false>
+	<#local display_priority_form = false>
+	<#local counter = 0>
+	<#-- List of in-progress research topics as well as implementable technologies -->
+	<#list entries as entry>
+		<#if entry.cost?has_content>
+			<#-- Technology that can be implemented -->
+			<#local counter = counter + 1>
+			<@render_implementable entry />
+		<#else>
+			<#if ! priority_form_checked>
+				<#-- Check whether we need to display the priority form -->
+				<#if entries?size - counter &gt; 1>
+					<#local display_priority_form = true>
+				</#if>
+				<#local priority_form_checked = true>
+			</#if>
+			<#-- Start of the research priority form, if necessary -->
+			<#if display_priority_form && ! priority_form_started>
+				<#local priority_form_started = true>
+				<form name="priority" action="set-research-priority.action" method="POST">
+			</#if>
+			<#if entry.name?has_content>
+				<#-- Identified technology being researched -->
+				<@render_research entry entry.name entry.description display_priority_form />
+			<#else>
+				<#-- Unidentified technology -->
+				<@render_unidentified entry display_priority_form />
+			</#if>
+		</#if>
+	</#list>
+	<#-- End of the research priority form, if necessary -->
+	<#if priority_form_started>
+			<div class="submit-priorities">
+				<input type="submit" class="input" value="Update research priorities" />
+			</div>
+		</form>
+	</#if>
+</#macro>
+<#macro find_tech_name id lists>
+	<#local found=false>
+	<#list lists as cat>
+		<#list cat.technologies as tech>
+			<#if tech.identifier == id><#local found=true>${tech.name?xhtml}<#break></#if>
+		</#list>
+		<#if found><#break></#if>
+	</#list>
+</#macro>
+<#macro render_tech_dependencies deps fullLists>
+	<ul>
+		<#list deps as dependency>
+			<li><a href="#tech-${dependency?xhtml}"><@find_tech_name dependency fullLists /></a></li>
+		</#list>
+	</ul>
+</#macro>
+<#macro render_technology tech fullList>
+	<div class="technology" id="tech-${tech.identifier?xhtml}">
+		<div class="title">${tech.name?xhtml}</div>
+		<div class="description">
+			<p>${tech.description?xhtml}</p>
+		</div>
+		
+		<#if tech.dependsOn?size != 0>
+			<div class="dependencies">
+				<b>Depends on:</b>
+				<@render_tech_dependencies tech.dependsOn fullList />
+			</div>
+		</#if>
+		
+		<#if tech.dependencyOf?size != 0>
+			<div class="rev-deps">
+				<b>Required by:</b>
+				<@render_tech_dependencies tech.dependencyOf fullList />
+			</div>
+		</#if>
+
+	</div>
+</#macro>
+<#macro render_tech_list categories>
+	<#-- List categories -->
+	<#list categories as category>
+		<div class="tech-category">
+			<h2>${category.name?xhtml}</h2>
+			<p>${category.description?xhtml}</p>
+			<#-- List technologies in the category -->
+			<#list category.technologies as technology>
+				<@render_technology technology categories />
+			</#list>
+		</div>
+	</#list>
+</#macro>
+<#macro render>
+<@page title="Technologies">
+
+	<#if data.research?size == 0 && data.implementedTechnologies?size == 0>
+		<p>Our scientists are still settling in, please come back in a little while...</p>
+		<#return>
+	</#if>
+	
+	<@tabs>
+	
+		<#if data.research?size != 0>
+			<@tab id="research" title="Research">
+				<#if data.result?exists>
+					<#switch data.result>
+						<#case "ERR_RESOURCES">
+							<@standalone_error>We do not possess the necessary resources anymore.</@standalone_error>
+							<#break>
+						<#case "ERR_STATE_CHANGED">
+							<@standalone_error>Something changed... Please try again.</@standalone_error>
+							<#break>
+						<#case "ERR_INVALID">
+							<@standalone_error>Invalid priorities.</@standalone_error>
+							<#break>
+						<#default>
+							<#-- Ignore other error codes -->
+							<#break>
+					</#switch>
+				</#if>
+				<#-- Render the list -->
+				<@render_research_list data.research />
+			</@tab>
+		</#if>
+
+		<#if data.implementedTechnologies?size != 0>
+			<@tab id="implemented" title="Implemented technologies">
+				<@render_tech_list data.implementedTechnologies />
+			</@tab>
+		</#if>
+
+	</@tabs>
+
+</@page>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/overview.ftl b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/overview.ftl
index 919f8e9..61bdda4 100644
--- a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/overview.ftl
+++ b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/overview.ftl
@@ -2,7 +2,6 @@
 <@page title="Empire">
 
 	<#assign ov = data.overview >
-	<#assign rs = data.research >
 
 	<@tabs>
 
@@ -61,56 +60,6 @@
 		
 			</@right_column>
 		</@tab>
-		
-		<@tab id="research" title="Recherche">
-			<#if rs?size == 0>
-				<p>Nos scientifiques sont encore en train de s'installer.</p>
-			</#if>
-			<#list rs as research>
-				<div>
-					<h3>${research.name?xhtml}</h3>
-					<p>${research.description?xhtml}</p>
-
-					<@left_column>
-						<#if research.implemented?size == 0>
-							<p>Aucune technologie utilisable.</p>
-						<#else>
-							<@dt_main>
-								<#list research.implemented as tech>
-									<@dt_status>
-										${tech.name?xhtml}
-										<div class="auto-hide">${tech.description?xhtml}</div>
-									</@dt_status>
-								</#list>
-							</@dt_main>
-						</#if>
-					</@left_column>
-					
-					<#if research.current?has_content>
-						<@right_column>
-							<@dt_main>
-								<@dt_status>
-									Recherche actuelle : <strong>${research.current.name?xhtml}</strong>
-									<p>
-										${research.current.description?xhtml}
-									</p>
-								</@dt_status>
-								<@dt_entry title="Progression">${research.current.researched}%</@dt_entry>
-								<#if research.current.cost?has_content>
-									<@dt_entry title="Coût">${research.current.cost?string(",##0")} <@abbr_bgc/></@dt_entry>
-									<#if data.page.cash gte research.current.cost && data.page.special! != 'v'>
-										<@dt_status><form action="implement-${research.id}.action#research" method="post">
-											<div><@ff_submit label="Appliquer la technologie" /></div>
-										</form></@dt_status>
-									</#if>
-								</#if>
-							</@dt_main>
-						</@right_column>
-					</#if>
-
-				</div>
-			</#list>
-		</@tab>
 
 	</@tabs>
 
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/technologies.ftl b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/technologies.ftl
new file mode 100644
index 0000000..5ae9fc6
--- /dev/null
+++ b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/fr/types/technologies.ftl
@@ -0,0 +1,171 @@
+<#macro render_entry identifier title category description>
+	<div class="research-entry" id="research-${identifier?xhtml}">
+		<div class="title">${title?xhtml}</div>
+		<div class="category">Catégorie: <b>${category?xhtml}</b></div>
+		<div class="description">${description?xhtml}</div>
+		<#nested>
+	</div>
+</#macro>
+<#macro render_implementable entry>
+	<@render_entry entry.identifier entry.name entry.category entry.description>
+		<div class="cost">Coût: <b>${entry.cost?string(",##0")}</b> <@abbr_bgc/></div>
+		<#if data.page.cash &gt;= entry.cost>
+			<form class="implement" name="implement-${entry.identifier?xhtml}" action="implement-tech.action" method="POST">
+				<input type="hidden" name="technology" value="${entry.identifier?xhtml}" />
+				<input type="submit" class="input" value="Appliquer la technologie" />
+			</form>
+		</#if> 
+	</@render_entry>
+</#macro>
+<#macro render_research entry title description display_priority_form>
+	<@render_entry entry.identifier title entry.category description>
+		<div class="progress">Progrès: <span class="progress">${entry.completion}%</span></div>
+		<#if display_priority_form>
+			<div class="priority">
+				Priorité:
+				<span class="priority">
+					<input type="text" maxlength="3" size="4" class="input priority-field" name="priority-${entry.identifier?xhtml}" id="priority-${entry.identifier?xhtml}" value="${entry.priority}" />%
+				</span> 
+			</div>
+		</#if>
+	</@render_entry>
+</#macro>
+<#macro render_unidentified entry display_priority_form>
+	<@render_research entry "Technologie non identifiée" "Nos chercheurs travaillent sur une nouvelle technologie, mais ne savent pas encore où leurs travaux les mèneront." display_priority_form />
+</#macro>
+<#macro render_research_list entries>
+	<#local priority_form_started = false>
+	<#local priority_form_checked = false>
+	<#local display_priority_form = false>
+	<#local counter = 0>
+	<#-- List of in-progress research topics as well as implementable technologies -->
+	<#list entries as entry>
+		<#if entry.cost?has_content>
+			<#-- Technology that can be implemented -->
+			<#local counter = counter + 1>
+			<@render_implementable entry />
+		<#else>
+			<#if ! priority_form_checked>
+				<#-- Check whether we need to display the priority form -->
+				<#if entries?size - counter &gt; 1>
+					<#local display_priority_form = true>
+				</#if>
+				<#local priority_form_checked = true>
+			</#if>
+			<#-- Start of the research priority form, if necessary -->
+			<#if display_priority_form && ! priority_form_started>
+				<#local priority_form_started = true>
+				<form name="priority" action="set-research-priority.action" method="POST">
+			</#if>
+			<#if entry.name?has_content>
+				<#-- Identified technology being researched -->
+				<@render_research entry entry.name entry.description display_priority_form />
+			<#else>
+				<#-- Unidentified technology -->
+				<@render_unidentified entry display_priority_form />
+			</#if>
+		</#if>
+	</#list>
+	<#-- End of the research priority form, if necessary -->
+	<#if priority_form_started>
+			<div class="submit-priorities">
+				<input type="submit" class="input" value="Modifier les priorités" />
+			</div>
+		</form>
+	</#if>
+</#macro>
+<#macro find_tech_name id lists>
+	<#local found=false>
+	<#list lists as cat>
+		<#list cat.technologies as tech>
+			<#if tech.identifier == id><#local found=true>${tech.name?xhtml}<#break></#if>
+		</#list>
+		<#if found><#break></#if>
+	</#list>
+</#macro>
+<#macro render_tech_dependencies deps fullLists>
+	<ul>
+		<#list deps as dependency>
+			<li><a href="#tech-${dependency?xhtml}"><@find_tech_name dependency fullLists /></a></li>
+		</#list>
+	</ul>
+</#macro>
+<#macro render_technology tech fullList>
+	<div class="technology" id="tech-${tech.identifier?xhtml}">
+		<div class="title">${tech.name?xhtml}</div>
+		<div class="description">
+			<p>${tech.description?xhtml}</p>
+		</div>
+		
+		<#if tech.dependsOn?size != 0>
+			<div class="dependencies">
+				<b>Dépend de:</b>
+				<@render_tech_dependencies tech.dependsOn fullList />
+			</div>
+		</#if>
+		
+		<#if tech.dependencyOf?size != 0>
+			<div class="rev-deps">
+				<b>Requise par:</b>
+				<@render_tech_dependencies tech.dependencyOf fullList />
+			</div>
+		</#if>
+
+	</div>
+</#macro>
+<#macro render_tech_list categories>
+	<#-- List categories -->
+	<#list categories as category>
+		<div class="tech-category">
+			<h2>${category.name?xhtml}</h2>
+			<p>${category.description?xhtml}</p>
+			<#-- List technologies in the category -->
+			<#list category.technologies as technology>
+				<@render_technology technology categories />
+			</#list>
+		</div>
+	</#list>
+</#macro>
+<#macro render>
+<@page title="Technologies">
+
+	<#if data.research?size == 0 && data.implementedTechnologies?size == 0>
+		<p>Nos chercheurs sont encore en train de s'installer, revenez dans un petit moment...</p>
+		<#return>
+	</#if>
+	
+	<@tabs>
+	
+		<#if data.research?size != 0>
+			<@tab id="research" title="Recherche">
+				<#if data.result?exists>
+					<#switch data.result>
+						<#case "ERR_RESOURCES">
+							<@standalone_error>Nous ne possédons plus les ressources nécessaires.</@standalone_error>
+							<#break>
+						<#case "ERR_STATE_CHANGED">
+							<@standalone_error>Quelque chose a changé... Veuillez réessayer.</@standalone_error>
+							<#break>
+						<#case "ERR_INVALID">
+							<@standalone_error>Priorités invalides.</@standalone_error>
+							<#break>
+						<#default>
+							<#-- Ignore other error codes -->
+							<#break>
+					</#switch>
+				</#if>
+				<#-- Render the list -->
+				<@render_research_list data.research />
+			</@tab>
+		</#if>
+
+		<#if data.implementedTechnologies?size != 0>
+			<@tab id="implemented" title="Technologies maîtrisées">
+				<@render_tech_list data.implementedTechnologies />
+			</@tab>
+		</#if>
+
+	</@tabs>
+
+</@page>
+</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/version.ftl b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/version.ftl
index c9406cf..e4abdb7 100644
--- a/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/version.ftl
+++ b/legacyworlds-web/legacyworlds-web-main/WebContent/WEB-INF/fm/version.ftl
@@ -1,2 +1,2 @@
-<#macro version>Milestone 1</#macro>
-<#macro full_version>Beta 6 milestone 1 (5.99.1)</#macro>
\ No newline at end of file
+<#macro version>Milestone 2</#macro>
+<#macro full_version>Beta 6 milestone 2 (5.99.2)</#macro>
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-main/WebContent/css/main.css b/legacyworlds-web/legacyworlds-web-main/WebContent/css/main.css
index 48ff3b4..40af4ab 100644
--- a/legacyworlds-web/legacyworlds-web-main/WebContent/css/main.css
+++ b/legacyworlds-web/legacyworlds-web-main/WebContent/css/main.css
@@ -839,4 +839,61 @@ tr.alliance-msg * {
 
 tr.empire-msg * {
 	color: #afafaf;
+}
+
+/* Research and technologies */
+div.research-entry {
+	padding: 0 0 15px 0;
+}
+
+div.research-entry div {
+	padding: 0 0 0 20px;
+}
+
+div.research-entry div.title {
+	color: white;
+	font-size: 110%;
+	font-weight: bold;
+	padding: 0;
+}
+
+div.research-entry div.description {
+	padding: 0 0 5px 20px;
+	font-style: italic;
+}
+
+div.research-entry div.progress {
+	padding: 0 0 5px 20px;
+}
+
+div.research-entry span.progress {
+	font-weight: bold;
+	color: white;
+}
+
+div.research-entry form.implement {
+	padding: 5px 0 0 40px;
+}
+
+div.tech-category {
+	padding: 0 0 15px 0;
+}
+
+div.technology {
+	padding: 15px 0 0 20px;
+}
+
+div.technology div {
+	padding: 0 0 0 20px;
+}
+
+div.technology div.title {
+	color: white;
+	font-size: 105%;
+	font-weight: bold;
+	padding: 0;
+}
+
+div.technology div.description {
+	padding: 0;
 }
\ No newline at end of file
diff --git a/legacyworlds-web/legacyworlds-web-main/pom.xml b/legacyworlds-web/legacyworlds-web-main/pom.xml
index 099a1af..577b5ee 100644
--- a/legacyworlds-web/legacyworlds-web-main/pom.xml
+++ b/legacyworlds-web/legacyworlds-web-main/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds-web</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-web-main</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<packaging>war</packaging>
 	<name>Legacy Worlds main site</name>
 
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java b/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
index ffbbf46..e8f4831 100644
--- a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
+++ b/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/OverviewPage.java
@@ -6,9 +6,7 @@ import javax.servlet.http.HttpServletRequest;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.ModelAttribute;
-import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.SessionAttributes;
 
 import com.deepclone.lw.session.SessionException;
@@ -35,21 +33,4 @@ public class OverviewPage
 		return this.render( model , "game" , language , "overview" , pSession.getOverview( ) );
 	}
 
-
-	@RequestMapping( value = "/implement-{tech}.action" , method = RequestMethod.POST )
-	public String implement( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model ,
-			@PathVariable String tech )
-			throws SessionException , SessionServerException , SessionMaintenanceException
-	{
-		int techId;
-		try {
-			techId = Integer.parseInt( tech );
-		} catch ( NumberFormatException e ) {
-			return this.redirect( "overview" );
-		}
-
-		PlayerSession pSession = this.getSession( PlayerSession.class , request );
-		return this.render( model , "game" , language , "overview" , pSession.implementTechnology( techId ) );
-	}
-
 }
diff --git a/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/TechnologyPages.java b/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/TechnologyPages.java
new file mode 100644
index 0000000..1042912
--- /dev/null
+++ b/legacyworlds-web/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/TechnologyPages.java
@@ -0,0 +1,143 @@
+package com.deepclone.lw.web.main.game;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.SessionAttributes;
+
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse;
+import com.deepclone.lw.cmd.player.research.ViewResearchResponse;
+import com.deepclone.lw.cmd.player.research.ResearchOperationResponse.Result;
+import com.deepclone.lw.session.SessionException;
+import com.deepclone.lw.web.beans.intercept.SessionRequirement;
+import com.deepclone.lw.web.beans.session.SessionMaintenanceException;
+import com.deepclone.lw.web.beans.session.SessionServerException;
+import com.deepclone.lw.web.beans.view.PageControllerBase;
+import com.deepclone.lw.web.csess.PlayerSession;
+
+
+
+@Controller
+@SessionRequirement( value = true , redirectTo = "player-session" , subType = "game" )
+@SessionAttributes( "language" )
+public class TechnologyPages
+		extends PageControllerBase
+{
+
+	@RequestMapping( "/technologies" )
+	public String viewTechnologies( HttpServletRequest request , @ModelAttribute( "language" ) String language ,
+			Model model )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		PlayerSession pSession = this.getSession( PlayerSession.class , request );
+		return this.renderPage( model , language , pSession.viewResearch( ) );
+	}
+
+
+	@RequestMapping( value = "/implement-tech.action" , method = RequestMethod.POST )
+	public String implement( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model ,
+			@RequestParam( "technology" ) String tech )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		if ( tech == null || tech.equals( "" ) ) {
+			return this.redirect( "technologies" );
+		}
+
+		PlayerSession pSession = this.getSession( PlayerSession.class , request );
+		ResearchOperationResponse response = pSession.implementTechnology( tech );
+		if ( response.getResult( ) == Result.OK ) {
+			return this.redirect( "technologies" );
+		}
+		return this.renderPage( model , language , response );
+	}
+
+
+	@RequestMapping( value = "/set-research-priority.action" , method = RequestMethod.POST )
+	public String setPriorities( HttpServletRequest request , @ModelAttribute( "language" ) String language ,
+			Model model )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		PlayerSession pSession = this.getSession( PlayerSession.class , request );
+		ResearchOperationResponse response;
+
+		Map< String , Integer > priorities = new HashMap< String , Integer >( );
+		if ( this.getPriorityValues( priorities , request ) ) {
+			response = pSession.setResearchPriorities( priorities );
+			if ( response.getResult( ) == Result.OK ) {
+				return this.redirect( "technologies" );
+			}
+		} else {
+			response = this.createInvalidResponse( pSession , priorities );
+		}
+
+		return this.renderPage( model , language , response );
+	}
+
+
+	private String renderPage( Model model , String language , ViewResearchResponse data )
+	{
+		return this.render( model , "game" , language , "technologies" , data );
+	}
+
+
+	private ResearchOperationResponse createInvalidResponse( PlayerSession pSession , Map< String , Integer > priorities )
+			throws SessionException , SessionServerException , SessionMaintenanceException
+	{
+		ViewResearchResponse view = pSession.viewResearch( );
+		for ( ResearchData research : view.getResearch( ) ) {
+			Integer nPrio = priorities.get( research.getIdentifier( ) );
+			if ( nPrio != null && research.getPriority( ) != null ) {
+				research.setPriority( nPrio );
+			}
+		}
+		return new ResearchOperationResponse( view.getPage( ) , view.getResearch( ) ,
+				view.getImplementedTechnologies( ) , Result.ERR_INVALID );
+	}
+
+
+	@SuppressWarnings( "unchecked" )
+	private boolean getPriorityValues( Map< String , Integer > priorities , HttpServletRequest request )
+	{
+		Map< String , String[] > parameters = request.getParameterMap( );
+		int total = 0;
+		boolean ok = true;
+
+		for ( String pName : parameters.keySet( ) ) {
+			if ( !pName.matches( "^priority-[A-Za-z0-9\\-]+" ) ) {
+				continue;
+			}
+
+			String tName = pName.substring( "priority-".length( ) );
+			String pValues[] = parameters.get( pName );
+			if ( pValues.length != 1 || pValues[ 0 ] == null ) {
+				ok = false;
+				continue;
+			}
+
+			int value;
+			try {
+				value = Integer.parseInt( pValues[ 0 ] );
+			} catch ( NumberFormatException e ) {
+				ok = false;
+				continue;
+			}
+
+			priorities.put( tName , value );
+			total += value;
+
+			ok = ok && ! ( value < 0 || value > 100 );
+		}
+
+		return ok && total == 100 && priorities.size( ) >= 2;
+	}
+}
diff --git a/legacyworlds-web/pom.xml b/legacyworlds-web/pom.xml
index 67a4c72..24fa5cb 100644
--- a/legacyworlds-web/pom.xml
+++ b/legacyworlds-web/pom.xml
@@ -4,12 +4,12 @@
 	<parent>
 		<artifactId>legacyworlds</artifactId>
 		<groupId>com.deepclone.lw</groupId>
-		<version>5.99.1</version>
+		<version>5.99.2</version>
 	</parent>
 
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds-web</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<packaging>pom</packaging>
 	<name>Legacy Worlds web sites</name>
 	<description>Root module for Legacy Worlds web sites</description>
diff --git a/pom.xml b/pom.xml
index 8f8a680..e1ad570 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>com.deepclone.lw</groupId>
 	<artifactId>legacyworlds</artifactId>
-	<version>5.99.1</version>
+	<version>5.99.2</version>
 	<packaging>pom</packaging>
 	<name>Legacy Worlds</name>
 	<description>Main Maven project for LW</description>
@@ -50,15 +50,15 @@
 
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-		<org.springframework.version>3.0.3.RELEASE</org.springframework.version>
+		<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
 		<log4j.version>1.2.16</log4j.version>
-		<org.slf4j.version>1.5.11</org.slf4j.version>
-		<commons.dbcp.version>1.2.2</commons.dbcp.version>
+		<org.slf4j.version>1.6.1</org.slf4j.version>
+		<commons.dbcp.version>1.4</commons.dbcp.version>
 		<commons.codec.version>1.4</commons.codec.version>
 		<cglib.version>2.2</cglib.version>
 		<javax.mail.version>1.4.1</javax.mail.version>
 		<com.thoughtworks.xstream.version>1.3.1</com.thoughtworks.xstream.version>
-		<junit.version>4.7</junit.version>
+		<junit.version>4.8.2</junit.version>
 		<org.freemarker.version>2.3.16</org.freemarker.version>
 	</properties>
 
diff --git a/runsrv.sh b/runsrv.sh
index d761187..226099d 100755
--- a/runsrv.sh
+++ b/runsrv.sh
@@ -1,4 +1,4 @@
 #!/bin/sh
 BASE="`dirname $0`"
 cd "$BASE/legacyworlds-server/legacyworlds-server-main"
-java -jar target/legacyworlds-server-main-5.99.1.jar
+java -Djava.rmi.server.hostname=localhost -jar target/legacyworlds-server-main-5.99.2.jar
diff --git a/runtool.sh b/runtool.sh
index 30ba8b1..037ac04 100755
--- a/runtool.sh
+++ b/runtool.sh
@@ -1,4 +1,4 @@
 #!/bin/sh
 BASE="`dirname $0`"
 cd "$BASE/legacyworlds-server/legacyworlds-server-main"
-java -jar target/legacyworlds-server-main-5.99.1.jar --run-tool $1 "$2"
+java -Djava.rmi.server.hostname=localhost -jar target/legacyworlds-server-main-5.99.2.jar --run-tool $1 "$2"