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 &, < and > in XML strings + * + * @param string + * the string to escape + * + * @return the escaped string + */ + private String xmlString( String string ) + { + return string.replace( "&" , "&" ).replace( "<" , "<" ).replace( ">" , ">" ); + } + + + /** + * Escape &, < and >, ' and " in XML strings + * + * @param string + * the string to escape + * + * @return the escaped string + */ + private String quoteString( String string ) + { + return "\"" + this.xmlString( string ).replace( "\"" , """ ).replace( "'" , "'" ) + "\""; + } + + + /** + * 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> </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> </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 zcmb5VXIN9;6E%85LPwf_1VRylbfgocqjc#VL4nYbE=^hhK}skIML~M+(mM*$ks?)^ z^p1dZMZp_?|M$Mn{dy1ilII-u?CdjpX3d(l|IYqh0Vvg#)sz7c2n49&{(!#=z(W9x zhxhM;yCAp^J`p}X1cFaONJv0LMnXnLN<vC{i=3MB7C99;DJdl#CDm;jT3T8%iaYdl zH1yOov^4()0fBM<1HmW8$0w$_MS6?o|M&H`6QCjj0}ucVVgvA~Kwv7+-yVPo0B|$= zH{Jhx;o(f-6A%&+<8EtF0=TbWFdhUS4@>~Y$D;t@;l`lCr@l)dq;Ol8kj=`2h(<UR z!=6j4SR<lm-8KG$4jxAQ5b3$fA*%0%&8t1WLw`@~k%3Kk5^g+3oS}c#{y*k$1O79I zyD372o6A3IAVLUkPX8tZ!lMFH-xY$~R$$Y$^1#O#%I*3=!(OwhI4+`hOz?LeAj3U~ zdx#2<1OAjNlXTX;X6~8NHKpRnt!Z}TNOJ?{M8c!!=Ym}}v)y@=?*QDRTB@A`r1+b? z6~<KE(ijyF`WPE!R@HYY7u6>cY>gBtguVJoqPn6J5QUZmD|vnzU3nT^CBR5&aW$38 zx;e|)p_8l5ohpj)M+@t%r9c#Mzg2<~psPd!DT^F65g_nCJ<G);u7>KP+=CL>YLWHH zj!725rcalrOoBYMkb!hy;1_U7Skr6$g<6#dpl5Fxdl^&1Zx`SwKnG<Yn$z7RQ7;$w z2@AmUX_Wa9RMv{8N>2~Z@alJp;mU#M^ORF6HX)C2_CQAS1r!1T9De!p{VzOTOEqTZ zL*j`$Ii>Nm$OWD9P+WQGrcdpc+3Q*WS7cIWXAWHqmf(xR;EB*Y?q<$97&<CBLIYB= z;2-@97{GPAVXv5CS{CAG`BRy$f0a4lp*=Egrfy<i;ythWv>?>(Ix2GIl@a6-qYmU% zG^f}l*D5A*1J4z4(;I_l1zesQe!&M;!jx4SXapq4u!LAHVodnJa2b^voJ$hCJL2<^ zXnK8b;1K&v`(!^UJj`pYISHNNbDVy7!=M=btK}`Nr=iH%k*Te^jz_RoR=SpCKF8|v zMv!5#_nEJZOzF;iS93yfRi$l2C2faWs-cZ_Yzm~*rIVUK{)9l*Za)Oox~yrcS+?_) z^4v}PQC7v9E9<7x=h=S&4CvSDA;B7neO>zZqN$$yJE`Z&2tKcb2HQF3uMEh3fBOKj zltt-&vGxe2;2z^Qlh!w>QwaNU@3}kk6h@lqYbhb&+fnO|HU<i*)-)8Qh3|74qOPRY zq+kC9s7*5O7<<cIf6RT7_cHECrpic?qU~;68`k{zsmt_VfavAxd_In8{({#QyX)8D zGon6@tmf;a!in#Rr2}irwqZ72Pupz@0!JU~^<5%(cqT>d#;wmN)nPT+{LN_K4z!Pe z_#i7_LfTI7Rf<&PYC~v0Vw)*}Db8fTtmfd`-FPEF<?dZA6-1xkb?;-AH*<28dv0^r z_qFMlF;{i#jBWnCqErgf)yfk$<`UEztQ^K!KXtU9>)?EvhTzkui;1s~r;ykswfpu& ziNjDC#8=6np#e2c(nupc!GNu5i*U#%NM{#o_S@fof#n8%>Dz-Y!Wy^hJ@W~S^}~q1 zD*-7k`CYKN=IE_h=UdVDy0Hb^qZG(+v=`2MCRFPD&PgFB1~yYe<F$|2m6b_fbJVre z`or9XLgNGMM*%7Z!j<NSe}Ox$Qw_Ea^<R?;#E+#<B|r5uq}s`LlAl`ux_SUPc!TUT z{!CngsbwKcWSIsZqs1QSaLu(i04#O$y=DFL;akl`SxFJ`+quj8kXtPq+p=<QXE=A~ z${}eCaCAv!XLF%0E_p5U6>k5FAeA_X*hn+_-ehJiS?TY8&K$#q7p5jDo&Mo!<hwv& zXqIG<gw6R~m*WrQ#6~c|fkT9t5-2F{dh|^I{$^%R;~RHGDq|F^0Mjf<Rv`&tR-iR$ zPL{VkXBp>%^*0~zqrvfWUDD$;_Y4ywm1f(jN8%PkLl6BA&P0D#{srzCPrNyOS`?(M z^L#FT!Dn0YwC&<l-eA}w_BK{aEqB3V6z>NCz7pX<3{d*qRZ4+$<#v*;5(q<==^=)` zjAnMpwN--;(A$j?NM-dmH5di4d=>!TApIBB7{ynR%w8FP0VmVjlb&rUHJe&W{Qm+I zKu)egfLjdydI#!N&0$XsV00t;lh8Vvnh3VuRaRySRm2t$K%q>%3S-DM6!*cJX>q)G z%3Wj2B2$qDkjy9u!6L88^_<(BTvf7Pj0Q5$#`MPhIkm)n<T>qKjk~O5_u(^-oSm7H zE4dttF+QKz-?-&<z2RFaT_iaK@FcgTE!X1DKuV%9rR-&m=MYpUl6@%^#g#9Og#8|u zSMM984^7PH+!0CmHKnU9T7GY&rozhL{^#wz;Ceh=C26XPS{HvK+AgU@Y6KLF(w6!7 z62z4k16s1jAdMucs;1Yg2j%uOIi5iT+o=cPt5A}N*N*)6v=NBT&P!Qkkx}lTwgoE= zkuKMtIc(`F<dhJD`DPBU(;zMFkOqFUQ{@@VBUYsprnF-GCd6I$0()G%wFIBSw$UI% z8g5_%o#4cUhn^}S>!c%*8BqfCJ8MMC?)k>c@kP-m%wlfplc{h&Pa8hQ^QgkJrmA;^ zj4bJ<U_w{k#Ej)L7cu9S1a{Q?`9dh_4s_IOG^n{y2oa@L(;=+>x*rn1F4uhH=-8oh zb}ciqyX~`hoE@lhOQ!8f)TOA)9;-*qbTvhIc-MN(R-(&nz|lZ|qudcKEe32>pCtB4 zwS2d!C^6(?!W;PZ=1D*jZp)#Bb~6oq4qWD-(n!Mj)C&KHy$nPc5_^G`#b2_X9{sLD z^2Vk*vr;OM)7x(Le3g{Vwj91(dfo9_VD9T)q?;6;2g+;K{#Q}#Bz}iZj|*Z}{#Q-^ zK{{^1GdY9`VrW&}L3}xw0cjSdb{z^YQGGr<DQ{GP6|7w7oRM=&l9LcZMvf?RC<R>L z87yF?9iqHKsscDqDl4_S2udnF=D+P0GgLpBbuxeDoxUViAEiDhI+VaFP#QIr(PrDl z(<1dy!WM4*1^@qK-hZi1iPpCLmiL245wsaAP$u0FA8X)x1Y{wie!!q?lKeP@dG1K# zUlO+6tzH;`S9*9eRma>*fne&Q&3zI!VXl|bI`qkgNj#|ds@Hyex+2%Is`FkKV~`&z zk|PS3$D}L;p((LYWnV#7cmF;D3iL_9F^aly-Ql=JeZ$5E=dO8a8;;_K)(gtxNf{JQ zcvjKHK~YsFEE6Sd(Xvy?hOs=A?U=W^nQ12K8m>AbiIRQf%AUl(l!Zp+-;3h5Z=lif z0H)4Dh00M}xVDC?J#ZULzul+JR90%Jr()>A7AEgbL?-iVD>VjGkTkV$Qg)#8k*hDb zcwZws8BP9cgw}gAr7zQ$zjBd!e(_T;Jko}1r@NWQpK!+kKh28jleIt7ke$vB4X>*- z_u#-n{l+?088K*tE@P)I5O)XTsVw^C$!Tw$O9Ur1xqU=mb35S0aId@?-F(Q#R59m6 zf`F`Zj=?B6@lb;<(n|J_y86bW2kn@b)fgBi%s^NoqqX&-lXv-p;0`=d*eiZY=f9U@ zP<A-RYH6QN@U8g6_v3@VKu`(Mzi677E<yslQYBve-OP?AL!!O@Oc+MH@THz;l|0t1 zEpMuk?7w1JLuq!~iT{}!{r>Z!BrnbMkt>PqApP@;%QckQd2sTsri#+8-}x<dLX;EH z8>7dg#8_A@L7yjq&I$|$1W-LSh1~MG*UMFmU&sCmh8KV(@LTbhCU^dN^+a!~HL2)D zC1PM}e&NcUIC+KaCb7ToNmEOOh_arV(#+1muIEFVjH37Uzmk_!Z9$>k%v#cha%tQk zQ}PR$mXbYz+)2?}PxrMYJiZOOb}=~Xeh~J+f=<KSgL7sI#Xgf3F}mLILZkd7FGSJL z$kMh9J?vIpDSO%sn*!1oc9@=(I7m4A7C#+B`jzQukf!hzhgGg7U+{Pm9rC8fTk+ZC zyMkx8D$fPm@!SI#Ce;N^3%ef#>8tNleN<!AuV=bj)iN+G>L+2Uz7*uFa3ykc1_%8G zR?^$|t7LC`Qz&#ZZ7vG#G4PGl-g17NY;HOnus-$d%PjkbdzExa<<;P;MHS?tU*&sk z@5s`L$<O!Rd0O7SUo0(94mT#g*}znD?VnjHsn=qQ*Akdw#8YC}ps;wnFl|`UAFygS zVY9T~dxsCZS+V;UL-;!uo)5G2#Qa~BCj3zq;rO8KnU(jOEoNr-BgnO)by;Kl>W$gN zchTQzD({1eo+$;g$GS6iMW<;g3_!M-IuG=1solbd)}o72*yT-UQNW3F&3<;|<uz&1 zZjuw-#FG)9u*a&(?h5_PQVds@Dm!jre*t`1K1Vazx(Te)q-hu9h}iV5=V`6-!14he zEn~(8K*ZgjzJDY7NBF13aV^nR#M*~(mxE7F<gZ+2z%^vEw%+dHeJViwvLJgaeLf#` z=2ay;K|;X4zcikwWYXp56-yRfE7QnQrp#UyYm}#Uc7yZ&*`4*}RVVVuKLWF3UwoaX zsRljs_PE&eUlc<&TsT8&z*#B~dloz=_xUHw8ccDMOJ-H#+HhHnD?zY%2H6I=KUms_ zW>4?}p1;*}f5Mx$3|*@d>ljRcD;>;>-NE6CHAWC>q7ACG7<(dGaXL;G@l`Y|Yx%J? ziX&wkvw@jcgQeOdu|1dYiSRI-k4UJF-#_1<t$guYt!g{v6HiY>3ny&7A>c6kr(%-H zuGD^;Y*{12W=6pL&>D+6)I@ydQDWmE(X|qAIWJii_)S@wcG273i2RL&e$d-*u49>- zi6iW7NH6MsrwFKQr@Ne!S?$`=6OCxgEb$nJxA}eRWEJE@?28Olt>ilFgw1kJTzM$& z#t@C*&7-)M?W2oA`hii8Z5o)(I^sATDCIGRLTqg8@-jEbqXd1qx~#pj-5Nl`<{F|N zD)6e~9+JL_o>vg8JTrlsoTn2!I(M!)cd0G%>#hE;gyCn>xWq_v+A75zFQ<yHqx^ki z#pY(4^Rl3na0~x~rL-L?hRd3int;HMc?yaCnZx&|zHfH%rS=Zt*>IcIm)X(93mhIP z+sQ-m46L5BrCjEH2M&;7bBOh&@=~s@hqQOKtyKt6F}(M6HE23`t;Bj}0(7q42=RHk z&P&AQzO@uzA@>KU3EYJWiQpcLBFe<`!)<EJ#8AYICdfNAS2lb9iY!O_wgRi)bT_;2 zYG~IAr3x!b0b(RW^MeTeHZ4=1Fj+^-qaTuh$l+`8$Esqs&x7E)P*VvA;Z~=tpxS!Z zh~DsM@7q*sPc=#Sl#yoPr<^lavZBFnPaj32;3#=^))NVqE!5kL#RU}y>Te{X*sGBU z<O#YIxfqy1;pwqd<B*5qWs`z3N)2_&unVWff7@SoYmwzCib#bg9?WlqMnaC!8@^2a zFV#W$?s3?%KU9$i^QpMVn?+2=z3v-lr(|g+4kv_eP!maC-?~LlS&)@SJ*=RR`5Eo$ z*GC>wQ;-85yvU15EGV@kAMZk}=ujz{k}XAh)v$P`{Q8O*!<=7@dwFpz_L-^*$U`DX z5cDu|DH6O^oMSk`9nITn?4+@dj_X21n@d&Zp}Q%TfFyLFU)p{l{jE!3v#A+kZU%l1 zXAMpnfXcOiFLQYF(xCD|pPMlexXc)E9XNr@?gsfZ-j*QNZHP?>4MbOOq$L_LD7j*3 zpo4GWALLb{j<qn?@M2R=EFaz+%Hw*Zqa9&LLTCwq8}yYx0UYl)sCsJOP4W^(^~eY@ z6U<PwaYQc?Eua8N$<Bh(r#EvYIWVfe;ABWC@TU{=ot%efPPMJgi;R~DGpLG$m)ZQt z_uvFK!Luvjam@)S#sJy<>`I^>m#ew!9~?od{DBqB!vj8_ushOtp7ObG8->+6ao87R zY#~jpTC~I&0dXK{e>do!BVrE@n`8AOgdzq@X++9WiHN)GMhU;hr0J;embDJhBr<tL zUZGJQmo#`&{Y@{AMu$(EI&hFi*{@f$wtduMwvBn?vM`6gp}n~VQ_z^D;p?z;&mp6f zYshd4vr@{Mz>3k}dm62s#^M?IixDvveqK<D=<M@~_d1dn>D}#TJbc)GJK{NJRPy3L z4L7c7>ZQBMh?}Sm>1&f&^*8KX=7&%nB5Aw=udB%5l-EY)aE~D)ZW$ZE>f&Gwc6^z` zruMjbkwDoW4B6ODkBpdsV=%8=RgKug<A2^LiH+3H_);r)-0qToZn%z7uf;4<`V*Ar zkkmejwu`61%uUd8GbDYgTAO8QyZw9mg%0_tD5mcfD^(cC0LaKVksmzTibk4T9AIpQ z!hFI{vXP0BELz-LIef|{)t!8=%JHh&$-+b~PcQRk6<yiy;LqIZ_K(8r^zVd|;YP!^ zA;j*{SFVSe<}dxuPuR9XXJ8hAf@{f&ms0=3AUJA9S4kC<0)0qXiA%|d1NkTRJ{S5g zYbvxBphBV=Y^%wozf7nyZig?zaFZ%?=785TJg;eJ@B1?p2V1jbH|ca4LM~u?lJ7PF zc$oUbfqsKU`i)!p(i!##@=7%Nqq!X3B!l%U<(Gt`4v>HF21l#-GpjKbU<U1cB>A*G zHO1OX$#R!yGv(QlSP-0&0>Vro^G8o4wa=roA4~($QNATC9O3>y@1MkseMSET!~`{V zMqm+i@@PgRy4><YP94NmoTZfR4>!T$`|z<PNa@_3wMhy+hhIB#5nC5CD!{ywvi=2T zhjmADp7@|Kt8-Bm#u2uDhBrQU4GnCt0U-VT!NiBvcYW-A(MXo(9y)L<MpkR8C=RbK zcEn#m|IZgHHc_pCZn9!X0E5U$XlU1H;FPXB7`9<Q|F2Z8e;hW|yQ6I0?DmYmos6pK zgP>Q|+<J#n4`%SL!`hj`Mvk@oZ~N?x^ePn3%qtbVx=7=sTP$F*7E3L!L%EqU-BchE zbP{NIYDT~APp7*1N)<_7B|LjVvs$f!mFoa0z3lIn>$x@79UQyo0{#n-@3t;DGZ@aK z+*-<QNgw1?#6I9!sV3wJWC>O{Dff=sj<fTLq3)C_JLGBE2*o5NKxxNcaB9XE@7*7W zjXF3OLSWZS9z{?drzk2v$s+JO%HrjVA?F`^<w!K-sr&jqGQHUTAs#E5kW4L!laE|b zXaUW#)p1%8LX^H4A7@|oqH@g}+5(Elk~s#(sPL7*u40PMVcg)%W-=9Cmer6oE>D58 zSQ$a0o(k_<W)pZd*{3Ov4_&rD@Ce!Ur$4A^Q%<Y4>EegdiVX7~an?WY=2Z@kY%;QV zCd%&8W3790^22%D>W|PKm%+048+B9ON46Gk^cQhBl^HD{($xvKN87VCnfb21FB*tG zdU>+-$h{{{?x9^5v{SaC29x3LleI3^0jpB;9FwlzBh|=LRIB;87|iQHFKiFyY~=ka zD%bs{TQVn7<K0qetMy37Q)lPLWXGLqo~qmD!RLESv_HI7H7n?bzuosOyZ5Ked~Z_x zY;b#%OIUgZG>c@vXTZpH`iayhuq*>BbV}7(QLZtg2$rS4m4Whm-!4k==`YYN+a5+R zRhg|=8*d~O$735iJpd*@A$)Exx79f|(f)3Ixy$W!2gl^ggQLev51#Lh7nm)zA0g;| z%=hn?fHw!zEq$r_7Qv%rpKq7j`91jK#{P-|GW~q!MBf{d^VooZx`iDi50<l(LmN#T zRqCs1GJMr;>&RLnwRX$%+RfMM*>)meEvx!Mf8xL5^FIG7;4fgm)Nst1dmwi7`dT09 z&sU7xHJc2!m?SYtNw$%Dj&wL$1~4nF%c~#Pl%vF?qF-9?-7ILo8*gr#RJZ6P2jelb zb(WG5S#@nQ#S^PjAhizp$_>B2_zMu++%w;0*f7i1I~Qymclj($N6jmlr8iW(p@4Y> zkF?I`5%ff?w63qdQ4LhsXF`@7Ts4xXSGTztpJ*11NRP1^QX_~@ZXLCt?thDS=T#QX z)H>2@oWxFS1q_l892ikg)P=NC+HBR%Ft%yl<EmnHRz13NO8J?G){$Kjl$^DvijY$u z8PYs_QG!JK2(A2l_M`EWn8t^Ky-K5?itiUM&FRMD$)|p8d12*$wE)aLXro6DSUy#X zt9HmH@9~>_m+H%{>I?TF7muo(!elW2I+SgAxbP|Qc5~bMo4-J&^Pyb6z|jdx&Ny%? zEfZq)7f{*#_{K$QH8_+ppT+R~c6-~oOeFL3`(NV&t=~8Pd>>NW?<(4HyXb{aj6Q4n zSn&bvqQ^<y+s~AOf!j$~WnhC^r@|zobe*7_)pel_6|B1!%ADkO+*VbHGW%%aRPM%p zc*cL2PRsK69)WD;hm_)$pYMXkFK)f4<L{|?Y)dsPL4eFo&f0)&Bp|BnJ}J;FEZTrJ z{sM&_K>=0o79twsy0m9j1MEAi%-`O9=UA?i`?`E&l9Nx*@%SffG&e-HJj5%&`)=yi zmQlJXg^v%s=6X=fe3zQ$tiTZ@Z&K^hjmtEW;N{<jFZRLp6oM?1%7#eH;zqpOS^A#z z$wGiwb3^Of#qQhkWuLf-DFjReZlkG1u*5L%GHt@Y^h_=iFSC=+Nk7Is1yZ8<zsbfh z+FR7;<VnQ@AGL5@4}FAn%e?A$)y3J1!OOCp9A-x|-=zgFl&m2RGJThwdYhNVlq^VQ zAlTJ)D7F5Z1mv`PBp_hj6yvy&D)mB0F&-BsO*6{EXI<@{Q}Agg8H~%@SUi1K9-#VI z@DYE$(CZk%%6k^?1VV;`pWFN-kv5Xi5b{b&n6=JBwH!2>_Im8~1gX3@3lH3{=N@DR zed{`H=qO9)`RubGoUK36HTuV%2H%@wHb<}D0UnwKlKBgKF~GrZ!zPJa-uFxP85*a} zRTy$s0E7;K-^MRV&Ll(J57kko5Cv(&lr4Vph?lyhp~e<u#wL;z4Q}O7Z7&t3D01IQ zl^j~fA^zb>mv0Fvif91A;-rpiP9(~Ag2Dai5|o9WK1o>VkYzC%9RQLD49enLh?O>` z``Ou=T64G;Anb4r9%AM{7i*<B7;gw&a0(mwPAEp}*N&-*>lyw^4cjd6!g`>SGQQBf zvX1zK%|SiEmG~jKH#|HKs}*yj^b~lrh?zc4O%2dKA?l4Jgd;ON<<db;J+A}x20QN9 z$r{Ot*j#{dl=Up`rI6sVCRZO<4mKbI2Rr|@bBr}yb8WDav}?vhq?8q&#G{MbFyG$+ zUudTKXj+dr^rcgvtU+Zw`wLd~{?d33RS-R4;#_#aTf|}kN>!#G4^@-~V{4wRZ1G4v zY!Anrud}oimQ(LJ9BhQzVJuTpvRHd~Q${CLtd+H7Y8^;+@=)#y%rZN8F(Vz(^_A+6 z;`d13=nl{Hzwo#Hg@Ti<!)-h;UF?w6|GZ38uU)tRQ=>*1Y4jh%ZtE<*HEGZsUA6L& zXZd<-E>?weS`An~#J3ukw{UU!Rg_3<9c$yldH%%pJK0J=(u#kX18!LtV$}|0?mvaz zJ=f7-DT(mS{oXCpby-$gfZ1ei6G_Ai>qh{V6tg;{IOnE55v{@j(Io7!r>2s5>duVk zs4JVX8Wn!aw>^YqPxU&zMGh=6*t=7Djk6WIX=S_+r9Y`zQr@Sw4Nk)POUm`)m{MJN zQ~u#PB{J7dEG<v$%?>>2da!J#*x=#qnk45i@gh0nkFr94fd@D$@qOAs*3z6+r0TZL zCws?-e6Kya4ClpHEPW;W{0`)GjYy;M+vFLq9(N6`4?w`zBZ`k~)aZp#C~7E*GZDcY zF!kB>M=x2tU~SkSW)V+0cn%WWhpI}~iDi$PZN*cpp@#>o_woxRzD<=D0Mm_*XL)Qd zB%EL7Fpskayq(!niNp)`XP5t;zsSulH&9lZudSz~In)-#_;v-?+_7qiT3J~SPydgp zEZ}Bc?;KI00Sn4U8NO{9T`>j%EVr`{G?diyWqr^qHrM~7ys}udXS9ud3!B~Mu4=ZS zr^dTAX4)^<J2DTfY~^MiE!Y1X`bF4ulucjZf@8hn=0=Zx>41?Q3z;qJVz`yFuys#u z^3dd{H^$@>INal-R`~8y+eq;x{Jd;^-rNn&iOs)P<fc6(n|h?(cXO5uQZi4xdHO@e zn~}5Vw{6@BTF_s=i}mM0uR#wToQ&*;NQMp`j#e*AB@yl+??w#XC@HD8-Seq2N5h!c zaL&<>6>p4#Jh3&ho;Z{fM0EC}El;=Q)T_ed1-)?LAxXXVD!@wBCr7`!X*6UB7Z=Vt zuk=3OO;_CDNm`}hy1@sLZ5}6Okdfet@Cw52dnuy4Qb|Lz0{=%0(tdpbmPxK%RvI3! zeFd?x@9;dnVq1Eq%T$OsOEbrR>c5DqlAG8stJ(^3Y$xd+KR5D}z;u<>rx$Ouo9CUY zBnKR620r@m7x-kh-cb8~t1>dHSJ=?ky`NB~OQu@kikgCd|H_5Ne@PHAX34YB7^}4_ zBcx|qC?Qfm!O6?WgXU%*{PphO_l&5XsAr8JzNN@(rn>c?Wvg8Yc$0CuPm9YwPiwcN z8x60E#)$^7kkHfp1zt9ZXb7b-Kp%!AEB^(y2|vp6X1vo6S8e$VjG9;0W8!}K02C~p zBTjCzygRLrCn|jTOK73YVN4av%B;wrRqs^DQ-8`l9Xbsp$r;MkGU)s$f<znJ7ddm> zO?dHqpPAM)`Nz_RN7k?Rsctti7Sb+EnPE!kS`86b%h`1t7T>-x%>DTD>-5(jqDUj` zS1(t&6g*j(T7r-ct@;?*EIm2Jm%e5C#eD17C%yGGN{e;EsL}+)@$V3)A8zPtFNoQ_ zH<LoO76lAjxs?{G?^F0?d!uZaSJ^oa{ZDweu6fO;j-FpTA1LmfsEo=9i43S7Y|OYP ztL~}#{*oVi&Qka=vyA@AFy5@<s(WR5jkm<+o%$D>w#bEIw)0!PN%xHWP{tktL;}qT z*IDw`?q0pmrn94*f407<tQ?tCvMPT*l*1bGxW<q-i|Cx*>)`x->dI6>1Lm<7(zbt? zT$1)q(}ojA;T!kF)Rap)zToo*^Gb4sv4f?@=D`y7UzY*Vwx!rFe}Uum2aD&vI&Hh( zxaoC5*hv%LyiBr(<=lhInn<|Qd@c3PRzHh7(tcQ=;etOBS6~ef$li171pC7yjb%QU zbIYp1jK{J`K2lywRXNeo8Q&2xb)FIbGWW=>V5qgHP?t@nCem63l9VCgvEa>O9H%HJ z-;{HjQCDQJBCbWz!m|I{2s5d2PvuQ(xs!Cr`wwK#9s(bB3B&Z(-6zB(gCsjSBjR1D zMx_yPb)Q~~oqp8uzE@e`R5!bqF16cq@m<gPNrB&n@`bG>k=O-u<@<V%f!^VoyhF0v zTkzwdc$~{GH+Y%7VvsYO?J7RM6?QI=NqwLoMU(!`Yaq;BqBl(IPVyLEoJsR<=2YpC z_hz!jud6cDuAdmrC7ejeEwwTycevI}O+KDH*i5~#98Ls?Epux6zY|}V`%f*uj&_}^ zdT{So14^wg*3U@l{7MDQ(SL)M{0scBu73vc9?jfgiK<;ZuT0s{aB&gUp;4x=_wQ6= z5++`Vq`zcckLlA^Z<V6^!^HGnYvAH)s_BicqjS;hnCFRk^|MT!D~|{`oqduuAoAJ1 z8t&ISvhmgWu4u$H2Upq;QaZVB(bxMGKi|zbPF*VnJ{PcWo_LJv=9qHA4n{!v2;JVn zDV`fMDjiyUB;qRaHe6_EvavZvvpBW#I@dQi&#opvDUZ_pR65HPde_%y5EL&PS)tRP z<GbAep@mb0Nrfu`8@aI=S1o`g8IomR2Z~3j&wq0fvWe^Z=qqs+vbvQ0{L#%;X#I_J zuti?)j-#B=XaL2+wal(hW?{8YIQrTF*O~Zm;&9Jzq@$(LFq$@b;F(th=v~&FB;q*Z zziv1V<&7s(b0>=23$`qVB;@AYq0dX(^rxlGe9JnYlW_p$1TSE<kCab$zvJka46?s~ zC|+n%sFF>$mHV-hJ5_#@e-M>tXH@qO_b1h?l)=;S`!#c(jD*mqeJHojpLbr~yt|TW zeECl6+w7>wQ3B0HE<BAbRTm=A9T*|u!u%8V7oce(sgdn@`}i%PXBdGhbmh0tby+Kz zhv~tSay7dqHBQ01sVi`sDCA-pQ$meB_)w(-NplxWb%K=_9jEW!GPE~mrfSSsk9QAT zKRP>7lc(iVU<W&Np8g|A9#hiKHF2?q)?doG{XR7^v-pe@N~I&(WR^}L+_dHC&dwFZ z7kfT4p#UTQ?K7N}=i=Va7;^$z+0-T^q?d^jGo%6OnWq?bc8;-n{h890SI@QVA&Y>D z(O5Sz-7gG@cH)|#*in3lxZFnZF!vO?q8C0!cIYA}%BPZcnsj&2hMG%X6kVZ;ov;$C z*=peDh=WVzgu}LP79vP=v23-Ddw|gxSXoILC7?OY0H38M95dfcDzr?vvc>FcG{>Gy z%ngm!Xh0#f?h$G$mk_Fzm^CrC*GwE$bV*vx9lHUMkf<t9d)?SyK(MFgeF$~>M)w<d zrJcba<C;BhnR|%I-Hna%fUQ4SQbr5)BsS;k*vGlVyUpfuVV>kFljz?u`wd5tOlk#= zeSY7qrt3ckO^D|gVBBL0$ZdH~`w%PYWdX<L89S_T>^*uS_(@Z7(X3Qg+KBZr=vex9 z>BjVpyclk@Kn-7^2cwYA(gsp)PcEI3pT=l@tmuAKy>pz&r{mopnMFSO^-U3kONE>Q zBrRNrbLEUO;-kJB$OYK;&9kM{Moxvc1hu~aRjLSWo%oa;#7$29t8P&Xhv-iduF6PE z{<HIYxwgf-slpaZ=IW{ADtOq5f$u&E&$E^gEM7Wl@AEqJ-v@)1UTCYSRH;<WI(3oo zf2#ncs``RX1wzbhQQ!Jyg+~0-I^2G&+V3jTsN0?g3GF4JU6z;KGJFO{Y!moI_;q~Z zYF7ut%sl(D@5#`*G)&FJACbPD!{l3kFbp+fX@&=%8#-Cz1SBA?ICgv;)96mXCh0&& zK~I66y;&5D+pG9+Y7nbuaI(Hq-PALk>I8P5rJtuU(c)>0mUo4HB5_+T9kkYz`XXgL zF4J1`gc4ymV4nRAb=_-hH0UfU_)rw{KuPePge@zAkdSbcg4PiD(Z`X<t6BrjPCE_l z|H6>^hLR{q6e5IV-HexCm+fX*yyj`;BBkm6<tRZcC)DCYsXWpwRktm<kg!w{KQ>jJ zwf{9Dh?`nY;oVh2v(RGxP+Uef<C~?LeKDgaQoK)cZ+&1k#Z&FIm_@aay~+ULnw~aE zgZ+RNj$730ih@Ky;pCrCZ!cVBb1j<@^OB>8>$(^-9BGCtEx516&PcCH+kBQa2z`W> z8-;#v@2V*Q;P4FaWq$umOfB&%YB!;RcB)1N%mBNbM@B*kjt|H2h?N>@8;z-}R+!2) zTQv?6b@u;{>Z`2fzAIaO>NIBxf03>KUE{k<Z=}?o_QZ{Z1-Qzd;mb+|pWzvVk>k2b zN;LSVPx@h0s!h5h>`AR0M)(LqT<=A7C98ioeaR6XFX{-z3Mz6-k`NDe9*^*p`jF(L zasuRAnQLceCR-G+0iW-@v*BUn0oEwZ*LH_O8NSdP9<Cz4EHPypTVuvV45}M$6L3Ow zT|zjz|J06!{Z9>SW5cr>9g^&SBHLV+hL>^Z(oT&S)o#GaO-%2IYr42O3#CHoR!Jk` z<%i!|H+vrzqj6i7q5prSSgx`Op8VipppkOE1D_b_JHzJ41FMw$J9HawHvvowF_3~7 z7$78U03|4?ms<89H=&ZEUq0^)V_l5ryBeE^vU8cI@W3%Vy{d6-HJDdjQ+!r_yx~|a zzb?`~$-#qoXPlW*J2Fq2`+#LgC5Xnbj8*)^RhYPo5dcb=y*5H#mZ9GGq|AMbZG)vE zvD!E`{=dRYS{>a#<r!O_5iy29u(Whu8O|6f|5V3R-_8>awMe1KD;LQe?#{SRB5aaD z?oKYQ3b7Ag!wDKH7Vd0bx`jOR5r59;v;N`x<rq5npK>W<ss=wA;Nq-4)|G$ajA&?$ zMACRgEy@*g4at(bcMr%qaqx}zJ+`931LOA{s$KuaK~!9|dXbeFt{Glr*%(yi(SkJ( z&fhSOHLg8#K_%pg36xqGeF7KC$ImoT^9*`!PUEqisCC;n{nB1)d5L!B({pW-YSk(| z!zNGZH#|xENOiR|Cg0Im6P0$asUa>z00m;CYTo#LLUHuezT}Vmv>1Um^^KXNAgup0 z^3eI&bnC=9i@wv#3`e+!h1P@$tOAN5{$-E|An2LejY=s8_QIjaP%Lnx?X+Jvs(X3= z-LyZ{btA>%4i5(;+g<V?y}u&i^N6>Hpd(eG{OA#U2<!qySiASf1MqBU#mlfQt?XjW z(nX>iohL1J2t($LpOt^U@fYrXAM~$Oo=7Gu#oNI1>f*e(`3roM0yQTGKczfs-=0KM zKS{7!DlMyDPV{B4hEpo-4%kLWk~NPpJ3k;uZfkA%3~tLh&--#n=L}b36%MQ%n`xSv zg!_uWmnLxb?0g`<%AiD;NKQ_Ckik+@{=+bpx-*iUMIgYpg7jUsI<bu<2`IC_x!2}p zo7M!Ayrk7K_@-mu=;|WdEd+kM&%VK%>z9s4cJarBni4nXyX;9V8H?s>+$%L>v^$wp z$89FNE!0eE8`BkvMExbdQpGNeWct7SCZjU{YUq&aQ`1}GC_-F6Hyak(BuWvQ!udfa z`YJrb<}`&W<uE$gpwa%T*|R5(nf7E754>lSIY*C?WZBA0T`XM{DN`mDiP1FDEzql= z_OAo;cICeL-}rkojv(cm^ymcSp=`>22gR{4lb!0pxg@f&ox~7&LUps$*PvIIwl>+d z^z_;DRQY3Ehl&uhz<%Sc^}b~H8fj14WHftUC$HquhjSTzMSK9DUWZfzmhjvuKi#G3 zDz`b;(71%KCz9*5RV~j`eyP{2vtBz)C(+?Lo9Be9Hz!-%ZV%YbTN}S=NI8~M6qk0Q zz^UP!Q{e1$wm$-@<}3aUO`1-ZsS*;^j>dI!^V?sW<G;-#YL){)5q*JQ+jq=@E-EW$ zO?G?Z&L%5#zPnWJQR8L&+#ZWRKeVuz-d=UHSTr?vzE$$=$%2c-8!T~<(QNJ_vwQDU z6MO@flo7MKS33p~`}82;S#ED^#mM}i45P%aV3ke-W8LEi>l$tQ-|T{zkQ&4sWRSZ? zayljF;`kuNqh(Y+fP`7M#EV}@)h$a~x3Zlpsh5qK_CLyS-3jw_`f~j4+2e<4mxt%R zERzY72}kKBdA>{gPz;iBIUX2*T?@-!?;$N?4&e@)FfQFi*mlP8Utp#}N5<_>bF6i6 z<-+kPvk>}s$V_=&43LA@5AMLl6<Ry1Kh=yZr;Mi`-RGK^YS7!VUN}zjKT7dIz2Y4* zv@QI(WhWPGn;rJ^L<ra1GrZNtcL&TaxxFhd(_7z|L|+t_p9rW|cR`^nv(qnh<%Nau z6Cb4Fcr<bGjCk#jQ2MKhsglJ`8?vnJ1|>j;oE%605D$(>$1?O&sn~fEd-I}-IRV&d zA4U9GO2E<xN3uA&bD@`gD7M~4B!bKfokiux35NMf;>9#Fma@k9O}fq{vmYuUQC-b& zTYoSn!L?^%nBBNXYxFIwHA1p;pidjvupbMS(!<3~!z4A>I~l~QCpx@V(TjmuK|46U zD+aU{QN%}6GG7>fN!oca=7mF}I3Xek4hU<9KFyuH77~vry4z~_P53H<d8zZd{M}Dy z_f~sdmC})7g_4CQyu7{sRDQxcLlVq_@}Id!70lzOr@TYVdTB4uhsN#~MIb}Fc?1y9 zA0BHh+lyZ6(*9D_7wqwRgO`o(zoi|ULu`yJ+kYMC->{*<WE+0E<}ZRAoq3s#r5|=3 zrGE}DJ=^uY|GpZW(Yh|?4f(}&I_Pq{xM{k$rX_N3)YPR4DXC_am|uEHCP|x8;*@T$ zhig|wiN<Q0DGxf&8lN=f{F1FDlM1pm<;@DanF$oVt-{I)fCt5}%;JQsrHJxrck2^L zPvg=+{x4$5jWR+NgO1+2*=C=gd21fEM4c5W5y!QG&c$)WBl#VzzW`b5LUH|mw?{ss zruZ4DeS4<0pDk$OaX0q>g6E^?qYN8+NchKyYl*%UxizsA{Ebs{5kA?EMXy%yx(qy@ zB+&9zyl8Q&eNf51s@=U5wR4y?eZIS~XJN1Fuk`Kp`<*UF>O^u{@{1%wo?g`U*$<I@ zhJp~Hmmk%>xM-pqgYH{-zIW%ZvVEi*dUAkl3GMb;bUl=!0HzKE;5cF=t09XpT}U|X zOOD^zuFaS?`^e)r?D|-G4(r6uP`pQN1i(?~;&ni_*m$f|PkMRX8@b1IYKXzIo8aK# zGnQWCPeD(N_AF>`V7qs&wz~&c`jRh%pg|JmI1z3>8b;Y4R_G6BIFY&^`jxJ#xa2B9 z`HTOr%Dn8;OOq=&V<Jm_E5ku+dFA<sg8If-osX>nCdLyDjVy2>MdD9+0yMzfcitqz zt9NtNgib||6ZkTgv9rf~lqTyPj6o~~A6Ojq;u;yU^#)$2c{_gOcKr)zD^i>LZ_iwp zS5*H@DmmNmG<$Kh+3GZ52#Sm)nHNDKmf1k?%z<CCU$fE_OSpQyZk~CVFB_agQk{R9 zz=+KQzf<tYd^k@|DSL2NMqu36;{0GMpQcq>f|``--tL`{q5gQGgHE{y_Xg|sr>{cu z?cUa=Bn1Po4XSuA%&1>~#<aJ|CqYA6rCQr8V=o_CFAKW5C!Q^{o70~qpa2zmz4JJW zC!J?YK@(<YG~YbNxy4GcF)=Q0M#nOgdX9@DomAg(C?ly&<}_aD_XPX*Zztt8$Sr99 zRFI=X4w*am_!lL%THAQ>YMD|TKIJj=1rN|eH*mz9$oGL+HF}m3|9-ip)c@%)YtJnN zwjWKo^0`h^uhGq~Ng651dR7&bgS-9-mKb{SPy^{w0nQhXl1>{-Irrs}=XYd<g+<P3 z4GtK!*%jkpO3{ye77e{RIl-L!p%(@J4jte|uH|#PmE7AQn!@zhwJK&#PtfO(&>2|= zO7y3B<Jm7ZHSv(BDdJn&T{08pf*&o?8RWtaBqcyrx2Qom?I^jhpaW5Panu5sChEic zp@o!gOR5Zye#|*jSvnV9Ozo0yLvxJ7#TBO7doedsVPHU#@Vj3j_8}ko05z;8Wx#cf zS_z)Qo2tX3!3RRaRI;3S6aX80Kj{D+N&?3l6%~7*b5B9>4T(UkSAPPbWZ~jNS4iX$ zMn?#fg(DM0tM+0>hf4%=G9sX(ggAZ3Qu^k4>Y8zkobo+SJ(-0@9J#v9d=|A}0wd_c zDc$9jEWCe(=!Wp`y$nuwJ@?4`t^5bs_kDdAJz=iSnWaVtkFba(S|6Q)TtML~L23p6 z!@#&@Dl+*too1M(pUUbk?S<12r2dvsTIhKAep(x87$(okO!4x6T3N0fVZImpm0I>O z6H8Ch^d!=-a6U!0RG-ctWyGAFkZHkMz7m^<Y~WKa1U$gLmrDJ|K&|otEXK2<7{cQ2 z=s@_YueLPJbp52HOYJu`xm-*@x#ahBu`KbZL588+_Hwe)>t20jk^fG){HL}39~veo z;I>qSpoBfv@(P4><l#1!>mwk5+Fdd)#_D#x3#&3$0f8j~axr+FaQuHx$r#lt^6S2; zawV;Hh_O;!ak*PJ2ydca2Pb6#WjKX_Fl(anxTv0*8{%6U80NE14`o(4GTES1Wrz6X z*Ob}`{J)y1FVJ+O9MRoxb7V0?%3nOjVs_75i>t#9dDE<Y&WMwHhUmp5fo-%s^vd{- zDIK<*%>s>TeQ6_+7;ydJ+^7ON|3*i{hvZ=^*KFXNB=Yx6afkG{So_%Rl?OsbT@aff z6#{GpX?|NOucM_=k6xl_(D8g}UKT!1abW8OQ$qO~E*jWl38BArh?S&wFgT3`XE{R+ zDJ#Sky0UUH!U0-%(Qmf6Yt%UYER;W~O&NQHKk15Km+T*wy7rj;8rbCQzGfJrM|P{C zqdM@~`O|*gPmi+YMQ0x~7?Ep;jBppVzY#L@x7UcNh@_HdkmL{R3FbG|(=TUEH|U}+ z8p+n%hl{<cRJ`?sF2z=|DPPt)x46041dh1(vO_lTIr|#|#-7N#DZd#{7w#DLaj2$^ z0&Fo+gJalHud$B6xT?LeQYXFo*r>!>xaE(IPC5Opl9N7tzpwFX{gYaVullka{T=Ma zOEC_KYCOCTdMe}v=006(fe^n*IF#azl`yu3*5DFW!AmIc7ho%B#%ZDeU1RfwL23nF z1-Q`Tclq{&)PQ=be!TLgRlhaSu_1(y9;Nzpdmbm<`!53E`G}AF)>>TmL5wJwUrJRX zLyfIV`DT%?Wr?AktRB7k^3u1ZsmuG>d%ELx+~uWN4pe8!+&!fk{spZ4qGh&>NSD}_ z@Y_iVzgZge<!O%DYOF`gXk5GPx3;T0kJL2bMe?W!Mq`Kh9Q&jA?W{Ar$!^Yk9@*X` zM?xgJ^opgB11A}$86OxIvPHmY4_44D-yQIAB{yOGy<}OExbo&h%l06V#cM**vcg#q z`dW+Uf2BO`$dqEmM&9>lK*kzs_v+buG1urxu>eMAXGxdySV{hZI~TD+k1*;`wr^L@ z94%p<Qt|Tasd+K4us+dsypJc`yvqj;`1tR&w<I2@<|qzp5r3COWzWuBt*<s2?RKaz z$9$748LSW{M%G787yDCQ*pd)=OQX@~@PjAJp%M&?vFDELo<l=vceO;qR1QOuo}P2| zbUf4BofVyFZHplU+F9QDlC>G|WdBq~=qHNwXRK`|HRt8;UP|bGs%{y2+BO%EKk!1k zyVzi{cCg&s@mlyTnbLXRul{DiPs~bg`4QLUUte;r*sJYeJFogGE4=ZFN39cF3iy_c z9nP6vPYJUWI=}c`7_V()n##34>eUIRFARw_W6^3iDtC8t)xCTvzLMFzeeQF5WZ`(9 zGmU%X;P(E<SM$D)&dzG8GA9{qGs4)gn!88t6rLQOHkUY|le-+t2hp=5gS~V&_1)B$ z!J!kB!-<2Ag^K)Z;z9!zn_-|+Z8mOLNL?g*-)VOC{SeC5`U$Noi;4#xiLh#*#vJL! z&a&|Hz4U9H^nhqrfy^aS&xMxF1mvIT#S}diQ9<n|gIJYaiZ$>t#%lT_LC)j@Uah*x z7g2W;G@H^-i0o2mb(H%=T|U9^XxyJUjty>i-_QlZYi6<^@7)S{9Ja0<efy8ZV}~HB zl9W&H-l2$OQLI0RgsbP98PkGQzg~^rEU3qD)3*x_Pbep3R`f$hr7ocF?RSp)3^UKM zbIz&!`HT3E?$GrXhBoFX4M^I4iU0y<!ObOF{D)>bBlK6kAvI^(J%;0Aml_(sd4m-F zJ5;<|;z*&SwnHV1$fLhN8)+ztXzhc|zIND^Om};_QDsoBmiA|y=6gh5yPA-Ko>^;` zqPprrpi)!)+xy&)8nc^&rj;AF`lRp>l{EA9d3X4P_XH^$4eT?n_iP^*-9fncxPJ?n zxIZbwSzPocPov679OK$CEjnlB2M7N-qLQs(2X1#5P-`K21Amq5-QN2;`P97Gf6Ham zP!W9%bb@zycX!VvEfdv<Fs|&~@YrtN0J4xuD`(x^Z;zk+1t@pv(tCzU-@Rc@S$5T~ zgpzG9Of3;MSWh#f;o0F^?rF-;(Rc(RV{+Q_c^#i9y1%_Pa$<g?xqru~s?0upOt-;t z9${cR#}N@yA`e-`3iZF-lobeM%w9!0o>Wy5)Y$rjU)9`=UQ2><?WkQKSTjOLrHm*< z*~Q{I=4M+1YkT>|jcL_Bz9-Dom1m_?9k?hjnDh4KeH87&SSQ?PexNW&iX-n_osk(u z|AcL|yuRs@nd&al55>0)5pZ&lBod3j$u+;<GFAe{i%V90dA2{pNUZPvslHqAr|*R1 zfDvy!JY!pOjQ)5zix(HtilVqvd~eWyW)X<r<9GGhY4G(q9gtW;^uI0u4c_E18`%ep zr#Siu_vW`*9{*D~|EEjDMIq@)XbiTb&EWn(u2*Qh$0APqszZjR5jPN5Ch+Q?(i>L- zd8d>=d#dm}fyPZVmZ5!u&!Hg0HZ6)oasvyT8VVm+3zL%zOEEa)V1sVbQ)deon1Ms5 z-~8$ti~&1(QvP%&RFF7PVs^G@>c@S<#Bp`>jEUj(wcE~utj6{<BHE2>UM|f2vPC#O zm!O`3C9cww70L35sWP!?O36=QkfdD2^byJS=uN-5BVsW;sm(jW8(a#v9uD64LYT8f z@r1X|XV{Ig4t<lc&0=m{o+G431gDyf0ri4x#v9tv5tw)abwR6vF%Mn%i3UvdASGHo zr{4-2GL>!|mv3EFNbhGA{tzi`Ls1D5F3Sh1;Piy7RD-bipad2iUud)9>A+6#KT#vP z<~xs_Qrr(Lu`zH6B;X!t3!X~xt;$ZKym>08KehY|-S`K|7;w;^HFx!eX0mg<;JALk zHcC(+W<f&$C)iy-3RlCOwfRa_`x&MKi7r@*7~Q4y|8%%6)8}_6ZAdfp4)e}4u6ZR( zdp;Fqr&;#evvD<p0)`3y6Y>&O7|KdbW0NRFVv81aU6@v1-;`N6(80D_eUjqxXb8)2 zg^p4*53uz9(+C5GU+FyOEBJXLZYy&r(!;7$$5?biVu}i54lHuQ6W(#Qj3q4KM9tb{ zS?Ixg(?MuPXCK55hb*LrG`8B%k-ml~*MQoqlPLn2sOKlcLlmgds-~pNca3V1pl`KX zA~`WxBB6#j940a_flt<Rz^EKZ0(U=1jHU0SH(w7$pJ)f;2(lP6pnN-QTeHQXP>sJ> zcc_}DlM45eL&6Pk-5Hb#o)7vYsBC}7s_8>>%+iX*LIJvF5yHix<Id=~VdE7(F!HEQ z1y0G<iIH}CI8Yd!oC?2<m1l*y6M~oTl>$kjgE=I;Y-eM8Jm#$Nsw#Q<mdY0GRww{> zZb>?b=QT-XgiAL#1KA6r@yQEQJM_BDA>m%lT;bDq8!jZ@ywwH6MSnAOATLx2_`&BQ zZEf$c#s_W{SS!y`g+k>X$8gNns@6zJKZ&2Qoj37=+pOGdVPim<I()X~byTSkq~qZF zejmlrBhU6HjZ*hk8yYig0vOCdU4$Kg?n4;Xogk2e3xL`G!yG=onTbM!0P83rGq(kq z>ySqZ0>PTF;agH3?>bMk+Jq<HCPcR-3`@ciOP>(Z!+dhMS<yB~nbK~ex3t}x?!NZ{ zOiZnYM0_7(-ZA|qX5GNlV1Oh7UTg0e-foErkL>fwz{y)61gmce9?hdS+9F_NEA}lH z%ucP1YAFY6Xaxpgx_gHFEvIO3QoOV!`Jb&UM$MBlH8zD7(sId_<IaJDq<-muHB^6I z8m)=A`8@(<g^k}h?{eC!8mbcPWTv-A$TbsQ<gjL}qXdO|MYT$Xp9-<^$U`t;DufBH zXDKfRsObfdKb#9F+IuqfSk+4X$vMV^NpGQw+7iM99VY)6?5@7w*L!i1oK#=yM2RvJ ze`Kcwh1?n~OJfafgL|0E2u1fO>Y>^B3}g{-B)jaNgst3iO-}iX;P#*ID$ar%f(T-( zcs2aVpKkSboi&A#xb~CZ8Ojpr8hB4vgrOB*Qwpro`Vy@F-IHfQ*Y%T?yM=a9&^@iL z6QV_-0d{t@r^0cVdg?}PszrseLO*6cFNQ~z#7*;BPT%?Y=(pK**}1|z6uF?(Rt)#6 zf=9?tE7i{r7@Nnker`7aUo14A*GAd;y(--upo&^*A#}Wif)fha+Sm#73|}X@I|(Yw z)=%)<YNKy7pc`5zT-g6##bG$3)9c|6(I0KBZjX5{#cjoq6xKtoMTmeM8#0xd1i!aN z<lqllq3vY@bh^nD`V@|h#LD?pS25w7C5p{MalP&;;n~bR4CP?Lte_II*I{=eP4m)p zb@HMLIj1}o2I=WL*#~jQEOLpA^**;KY<i(5HAp*u+gMcAi_5;kldGXg1_FNTKD4aq zWVJ_WISr<A%<W3a8qh7axi86YYy^cgfGlXA-_bXVek7tsnL_3Xv6d+9;_+B?M_d^i z;5XeT9U!A>SE~V;!X)`<2qZgK8daG=U{0tQuDj;_tsqsOs6Z%nHsNid=^e7%|A(sg zj;H#6|HlspCpuZ7Bge=&*0J|FIc7N=vW}gU96MW)Y@*V!ju8^FcgReNjAP3x84)5f zvNQWWz22YSAHTni+qs?R<N3I*>%OihDD8oBUF(eMtu&^H2xE8*TlB9EZf$#eG~Zvo zQC2)MBT-FPVG%ixTnY>OZ<N!gn_5~NbT#f((y0<LF}l3UWhjC`E+kBC`D5q7m2BX1 zwhC?{8T60Cu5;<@#Cv~RYXuQMB7U1HO!wDtH_i)X1i1hebaLlIn4+PJGxT2S*TsZ5 z8&MOkLs!LkdWc8F?X(d|MUBb!uX}@c#hxNX?nlC7Va!bER+^ySVihh-_51I+m9MsM zQJQP8FO~EJxj1&^S>3YZz!%0(KDvy#d}J-J`5|^W&~~j!tUUhtF3)b~0|Vn*y9&kf zeFH%<+SlPw$5`dx`sFoCppy&E5OGm+Ar<Y`{)V6#j{5|I@?E?9b%`6Eid^|Y2~>OA zC%+N;!#N|<(fakASe+6z#PY4tx7}EqZs|byxo)i2%*4BX8M>=(O(>QmK-qbiVCCw_ zpGzZpBD<7%!Cvg=w5^#~d8j9*jQ)*yIWZyAz+m_}k_YcP3sNcM{obh5>@c_ZAOY4^ z^HYOYWmhqfJ!c?0ePQOkIo13*{jQADguTNr)z!7)I&Opq=|1nQ!z>4VDTx-f=NGDK zKVQuyET{Fo3+lbrv|D&Tq+$DZ=|~8N`0fFL>raZ}4Qfy!ixl{Sfy?GiRQHe;J7QVs zdE5<ms-$@OgSSUx9ACaZ%0Kq~@~7tOB{9S^mS9`_tT}nd!@@d$y5J>dch--k0B6Da zkoQh)kmJa2k3UNJb}LFJrC$`Kv${7_PdZiFEB`FT2Zd1rN9#&{wk8tva10B7af8K& zZ*-64?+ffjIBd>5NGSzb6L$5@*eORed*hQ~h$l2l2&X&G-qn}YGL|(M^xwh@yvMNM z`s3ec9j5pEu4rujec|@{oWJE4JiVB{?)GA1w`)N-Tgr7|lBXgw{lPWg4U!aQ)yKp| z#>K=f#(?eC1E0ac?N&WD(*n-5xoGb=xc55!6&eWTLd4RC3wYZSk#xPT=e4-0Er`&D z!9Aqs^flyAur0giAje$OD33WJA)(U=GUAb?J1YJL5lf#}pKNcje%CU`wvg|t6snhk zQm65}$5Ymr?AVZUJyLvVs=AHSwut@&@ju6px3eO5leg~@dXO{w^2J_>U9}!>3&-EF z6q<S7kF^5qb_yHl_-x1sVU0`&IX?Q#PVzq;1>Q$g<aSsFH2til?5-a9`SG}w#9euP zM0?UA(kN_zsuK^o*r~l>CT}UnIw@0D_5Mar1-O`|m=m(9PILJ`@6_Fksic=&BQzp` zp7g7-N4#_CV{_4qLg@%ORLlFjbuTy<96N)49rw+o9DgkN2YNBH^2jQ-f4p<mcL3dQ zt|q%W@lw%|S0QLCAV@k6a6Gfo>9s!-ul=}_8#%8RF%0kCmKOU5QoPW`T@|X_*m;J< z0X%cG9jQwjHhZxfTo2gp-|a2Galh^0Xgkh3>b(j?tR$||sf;E}>9P?z$yc?6sfH{< zXW^tc>^5HC`UWa9{mGes0%>DItOpb-a&)(@KG-=F?_c%pI}EBBqR6+9Y%y?!4)G~F zgx6$rN9Ll{tIV<(!4@^~?ges4qtKZw`iv$=Q@)fnyjm}Cw&QE^L%r7e1DN>!`$7G- z&{p`2U4Y-=%e@OHRWZ|>J0(Q`t=??D(-$x~BIMf#c;X2|Kuc|QOU`|T2&Eevn1%Gm zdc|@6k4KNLEVG@q(C)Nthwc5j1cqR?d^)i~F7K#~E^C<>TBgoYO`Sb79^o%~M|l=| z#djNcQ;(qF!I`*n0X7i6g;P}rl%0_=&t^)Nou1O3lj7{fXukmYqk#*Ony&L91OXp4 zejlz*26=+GOsGPo=Lg+`0SzX>_boIqm-i7hYkt*MIF;)DUQsr~tB(>7H)4-RpWV7{ zI~63;T6Yrk57aCU8fxE0YmYY7Q<>PM-~6z*`R0woJEytGe4i_^=RV;YF`tTNnevUM zGBOM=)GG7)Wf^;NdA0H!<e&8^*s~)Ce1#7k$lujAn{cztocc@%x;&ZomLk@l+E>1Y zYEwQ&S44wKE7o}W+Ie|LW9)AQu%s76Q}4&{nVC(>UKK@j9$!HtSz=E*m6ydU9NxYX zs!yIxV_YCBpn8H@xl7(98@B%p|6?FWV{{|ByvfI{rE4CeGKzHLZxV*7H#Sl@!6(|; z9A))&DeFdTEZAXYZSB!2G$)oRTd=g)+zvx9nI%O;Q=b9A#W;r9WJaoik@0^ZcA9mJ zyfEotLN+XMFPlHpDZk!_t!VRIrco;mFW^I5!Y--a{O_2SDQt<q(^uO`>j48EF9Ys> zpgxq~)9Z%rQ#w%y?zeNvFPy=Af0)>4qSPL{_OisZ?=3X|c>ed4d~@G*wU$AlW=)Vu z4kxz>QOr|DjkkNv$V^Yk;H{^6mBxjqm-qr^y@LzWsRd|Zos-^=!%ZTxraWDGt&-|> zBLghlp;d=2!zq1Qo=RWuF}(~|aV1I5qxs*SFVhtvAZ$&oS`nPQQz3GnufKIC9Px7A znibjaMiS#b1Q@a#5gNo4SubJZW$s-Oikhw%v(e2<xZ#CI+dG}wTnjCa8K<#XhY^=( zsA9|}bF>AZd6gO_hhZQDL`eDXAQ*x^x?H>%uY1MjIShSZ3>er1073BC{J4#)LYpQF z(wf%u@lmTGy&-%Vyqpjk(q$dsI5^Gur<OSlv~XO(>4UmeZKzah#qFFABL|ZGxX1Da zJ-m2)yPnY4F>8&7tMjO5OD|8e6J)9(VvY8om_j7+cMBx$ktrV!?ngwY3vfl89kAl* zt;m+yB^9T0#h&by<qbAIBoosYQo*+kGxNNUOD1zM@FMgPY4$`to0*wv79M*#CY{8T z#-8T?!wRxIaN&k60v`FTJ(TGNDx7{0gPD(O766&6k6H&*?B0&cb$*n;`L24H<N8}t zP6RBd=^3B>v+)QaAqGhF(yyYteJY%+rX~C6XDvA|J*4y1b3aXNX`*3LW<0o`#t{qP zxjgo<xznF^jDntRVG$;#U-aBxc-BsN_q)J3iBalY)l2bZrlG0?riswE+k#3W{HU^R zDciMPkSIquz7ww^oP8OutoIRB@%hqE*x<}>V-}L?u4$UoLtN_sdI>TBcoG>>Kc%rL zv1{NKjmVJCUcjZnIP*jKqrpGW+|s)*4<=*SoHx1p6&~J&IyB*#+e!KwP<LC;5PhWt zD~bBQOJm%6UL_h2=<37&z$9p!;Cj5am<4LI%6!CaFSa){#Yp#|uE#R(;OvMhNNYZl zjK>J()Gq`^T0QVn-dVhtme-IP3MDr>@PR<xdmyIDl9F!=&xTppaDpb2h)dJW)%qKS zG|(WGTE3EK)Vwn)+n0(xcbBaZ?7if{a~v@_4)_;xX&;Ea9)%|haUL3D#o=CEzH5o- zHtL7y1x77E5U5+}XbS`~bs74^<!f4AXqxi)R=+@4YYhLRa&kJo!WUT~sGaD(e+9S5 zAiFCRcJ!HE^9=HrCmKj*i+_6>bE`o^Ng8zVB$CQnSH$c}7>yrYPKCC+<U&G%K*IxL zMErT?Y;Vdh<zJBZBMT$7oUZFP<;Wt+n9CRrQyMs2h7}g{ti<lcsKr;8ijK)-n0Plv zWhrZx0U0-9F|Zr-yNSX7wsz~`W){#{gYjI~f5PuR@zT{3lTp8dH_G)Qm6BK%yv(L8 z`W?C7_LOkUsoun1trBf~5zhocJwz+V*$M*yvNK`AL1r?)g`s!3%L1g`8BAc*3O=D= z)=c(_{hVQMS^Fr+M6w>$|1cUFo7q23)X`4KmrX3;osprAU?r`@IC(~pFN2>j7Ea_3 zjrJ_AdXAY1tD)_}ZYO4{HVd%P4(YL4>BoV{<`b-h_D2sfXmF!)<7TF8xX2mzoj_y- zU&c53hJQQI*Fw<vbYi-I+4lclv$N7ByZq$9cgbUg^Hk|{r`D%~^;-vO;Iglud4w}9 z8|}omRi6|XJm&pA8?Bs?x!(N|bq|(U0p{@6WT~=m$m4czN`}vTG<7;q6YOI0pg0wy zhJ~;WaR4x|6nRWxDuAnutfcUTPsT0Jhs87>FP7$nRvzAYal>PGpZi<qbOVf%vE&=# zQgQZ?YRmJiOd9we>94_*qP#&uvGco(0X#u5$Qy`+TRB~9;YQn0hj8YQXt2b_^^a8} zV)9(iE%~ZH?y8E)MNr#37Og%iBG&u4MsfDUO96G8+@SUiPL|Ge(vp5xogtGEVPtOg z{@e>{r`zUEn-`HUHB^8`HKM^rio)GG2;(ysi;;^%*t}(>fZ<u$`7{x&lnLNtWh!b~ zUZXUpc|P$~$cV^#1B&}j&R25)#b;;YH`O%RgT~$)-5!KqqW5A@qR9|3;wSwG#gf9t zkH$b3GoW*!sKpdygI1J+iFKiCyNWg#GwZ4U@DHO*Kzf?aJc5(62tvcVo6XQaS}wMh zQ*}>w<k8mTGYP84oqR8Rm~OBJ2MS+n3QIwB>LaT;^Zn16N_0HcWS|QQ1&Z`d$#u== zmgk>ozSI^}I2tUtMGanCYoa0Tx5@$TB7j0v8GPZeKBKS^QaB9otZQbsboOQO*i=c; zOSaj<-54w*WCC~~!lmWnnOk>h@M86>cWtBDz7H&f(kUJ74Wjch>BS4koViZ<G0|A} zJef)~UjzWS0hN{7lGCe!B_SbU$|$T}Dat42gY!c8^(YK4l|_7Z<sb}03G|t%TH=l| zSHqvLu_1rQDVZ2wH?`APv8JL;g4_9XZW6ZEHPuQ=^5VU^2OGD6S~GI>eOXtyoX{02 zA>JG50Hm}o5!tRV^pyYUr^xf2=bTk+Ctqd)I#)QANv^AnCnJ_6!AOl=Vs$OWmNJzA z2!VlW#ksA<){VgFkC4Kd#>c9^y;s=m>F0rG2zy%b{N39)2DHNlP+P550USM2!YeHO zY0rhpv6rG-Ypym&nT%S)NvulnhjXD0_V#MbB4C>EV0Q!6t&?IGuLPqA;6HOFqES-x z2T$2*?Krlk@zt<Q$5ywhVq&O_J|q+<d{owF74whAzN{7-@qTQp2`e3bts7vY{VN0- zgzXdZgkN6@xERSDddP5FAy$8Nj{++KWz0IB5uIj>|3JC&{gQ=cyOaGAi(j?hhv<J_ zck0RO4-dYzKkJBCUPp21k_jFWdW05G0|t2(#0MZ7!liC&f`DfdmTsU2jjM5ViCs8i z0L89bOz`unr1;WNg`ZNQ0oD^Jt>B0OVMsax`i9dUFmfkEfcFs1S76H9n!2=*pGXp; zHU%iT*>L8d<`tvxcjVVCn27-1LN<xvd0&mCi~vJMR;!fcVVQ9{t(}ob&W*5fAJcI4 zuGz6-KRK^zWR$#}aI1>-?~ET+Q+D9NN+1c18pspOdx%UkNzFx-eQ#gl{pEboeIrXL z<i`f!Xg4Km$3S(eXdZMqLiJA{c5TK^1qq*Nc0k>oSR*~So7}U@7Y5o<VGF=%$O-Fb zqs^a>6%*R!{JJR7Fzy$Uw3G>2TESt*On@(ayH%j30Ao%h*&D>l0^loO=(!fm_L}*f zDT;ppcGF7J9v*AyHY-MuGFY~5uY-&xp8b^(Axt;0y=WgSN5d?I>E2Q<Dh%gn%tR;U z)R5RX;r!wH7+W>)iazbdBFk55=Oaj>?z~mNVP&_q(i3=}Sf>9O*Yif0V5;lrBvdKn zO!>f6Yp$j;${e+;THM4XJsz+~uHHG3-(_pEOFtJ=L+AZghq-}<x;SfeYRPE{bYzAV zdnP0I)Q%)27CP^_B|8_YBZ?aSB6{@1pzoR^Sy@`ztx$qa5#$xB%j)$h3k~6n`ohQD z!ZTl$%@4Fn;sH-GeZ<?8+v-;wZSg?x2&IP+=R@)@D!%VDRSJ~?;F$k@j{(*DA{sya zIwt37Rx)Qjvq~XYXph&$vcUhRGtzB<g8a-tPdJB0#A{mm_=x{(e>*5Th%LK~x3leA z)yE^Z@G36v{wo@AZNmso>AK;(h#3sz2u?7r8Q9EoZO?IZ`O67Zbu2U9hc8w@Vo3-* z4oy5J{Kq|5`rMOGZfiYDZg9LuK_%O(wDV|ESzBYb?pVu_O6=oLQ*%vdPT1AJd+d6k zcy$PkKO0yNnzHqtkgJ7~Hq(OHBf`)9adxqMUgdzS!XWqMw)+I_`Q@H7ls<+)z?>N6 zK_4}9HQJ{jyDaK-EFIKD2AFoyfK_yVFI%#%s-Zb?q%K@HO<6H14`m$=C>Hp98A|#L zLg}#t3Z$5(nw}Z9nL7UCsft!aM67IPfDPRG!2>^kxZ=gZ6&iKdw0L(DQ;@{e0u=5D z+t@ABq59-T!Z(JPDP;Zw1qPUOs1{1cQ{ZV(g~|he8b>hq+k`!KYe+g(o`GJ^x)~)I z1Kbyt?9k<R+Un<5-1r<hJD8t7S1cQNUXv{R`kAXIqxQV=U>s4+oNz1Yd?%Gq<>2;_ z;nQJ-F#_cS2*`Q!`)_y#qAD)1=5Ut{+pR-`3FuDg!H;`@t?^Z^v&E+vcrg2<7w-qi zp&>nK$owY0+NQv-KIRlK)##7Rkhb?qoanDR!%-r>%E>r<K{~zoPp6+TISB!|;pa|b z+zu6Q#}zz^9W+egy^Vgkc!!S{u_C&xe6uZA1#_RRs;MGtEJfOVc$dHJ@E2QFuMX(o z84&Jij)2?o_}e9U7dd}BP(p-XY}qP4Og#E<Z|Xpo6y#J)R(SS}Tjwg7)50cV)kQRW z9{PulE@*i^UEA-_y}Q}BZX~8{{6s|LEpHy(mt8;a$q%;zh}=ip=~PZRW#l=$ZL|Vk zp4#IBoSG1N5a1bBoX*d^tKI!6Yq=hlqL<Kdg+MZVtn?Ja(1GVsLm)7)ftS>;dM2YD zDu0OjPgm5OyW_@EuQBHV`uRCxJdSg4<H(TX_U#TIXCl;BVXxW>H`I+4)Z0fkN%<>E z8@5QPR|-!&kJq=D13;E5x^!4i+3up8Xt3lByV#%k_qxUGo?X_qe}?xkgqAEYn+wH! zM}=PwF*A?9#4GFJk@ie=>Y$iTq~gWH<M(1MiUsH0c(iG@g40e>bs%X5{H;IQothw- zH3mO$LMGOPawS^xL90DqOb;x0_D{&8NWv;;!$?iHG2vUR3`Gb;b<8-%W?F?5hxQ}O z$ij4T^zlmwzKqRIgUs=4)c>7`vte8q{1N@ytHBYy9W6}HU|^#koXnmmSnMxeTl;o; zU~jLrow=rrAWo62aXg<m!@cq}&FI<v?@~C~Sg+QTIMrHE!#sx9r5Z!>>IyU5=+qTP z63743f1{4Lo~}udE!}M2$M~cQO0YLj1UL=2|Ej3E<S|h7zC{&d7^GA(ot+c8KHLS8 zZA9kdf;ZC4od)~7vg#X)h2?9@UJU0kUmw<?R4&P{-noGI2WnA_pT4QBpUc-C_o}r1 z!)Gn!7FADCqkI=%ZmQurg4m&Nu|(PP2k$PF`?z;E-q7oI>b&!+#eMu-7w6T{zX@)a zL+fO0$QB&zy>eSCk6#oXIOd0YF7G&ynw^ilRv$Fq8J?CL^V`4rT+{FM%v6&e;+tDK z2eDh_IXfTO|H|fSZQF_}gM78(vX9j+o9<T5xDw~)AT{Lt6^c)-HqUOBkfpP^IA>o` zALje};-fFFm&<b(%BOr#ud?a3kG>4WCM67#y}AWg(iSgH_TH0ilk;FJ>bR{);M86} zryOH``>Vo7i9!a=k`A}>iIPZ`6_J&z!mGtXtbB(Eao0x*65&-}el#>KFX%drYH#Wx zZi$iUh+FGF&F|5+ioeQEOn-ad9=38n4q9K=@G*hEw%#$Qfu9fWfy_Fhu)iI;%2jkV zc10;jR;#Hw<wiXJ(|FK&`<^L}#VnKHhl=Ho?YK9&_Z<pzYm9C??~|I$A3o>OMuoE< zu*-z5!Kvv))BZO7jT?FN(d3?Jm(WN}(>)mM*CRHs2}?cy?s#T8udX&JZz)f&=<i0I zuZwkNYT?nAx3w-^j%&_v#O(;-kCoEJjZ1GcmW*9#Pxw`r7_xx4+jZPNe^Q`2^&F*j zy4~UYK{D{(hrhW9=g&7SCoFosC1`6&)UXt_CN)h+Sey{RM+#vt89q3_vQJ1govkHq zwf8riF7-P<?f21P(8EYDcKuAcXf&~BJ7ip!s>}ar6MUj*(<ePenm^IIm7N3>6vuF^ zQLL;c*7%WzdKK@ZwS!KtT^>p_6>2;$(kAV;puYu^UfZk?c7d1?i#2u0A4i3^{{_pr z0fmt1KO)m^GCtR7LZOIvK<Q-JDI5M4dl%1TXvfr;m(400LE10P)nFUA!o(_N2Gy5Q zr?>}RJThb((J{(M9nINnQ!sG%Zz+iVG50W)`FmPVX_5U#CM8_uNpwZ1Y^U_mkfvNo z^a8HqE12A%+z2EH0xxKa#%nwbDx>m^a$JHUPOeC7Y1p}o&hU5<=uzef?gRK|79c+J zj`KL<89Oz`j+)f>WTNqwRkt;fpriAr$k7;jg`ND$35}=k2J?A40jXplLxa0>XQ(R4 zYi93E>J-(fFAKnsXZOK_%<IwGDOwhJt@oj7uD)H{q~WtVthO%OmAwxnY|*kmNRk@# zgZo`PHRC`#*IoLy+p;i*v`J87cTK6*q>)vaCNDX_FSfRJK@j^JrpD5G4W^iRa&Xpe zw!}!e-G`5Lb&;%=1qsCK37<rz(V!|ms>G>;xJQ)#VBYS`_EtCOX$`Ab#1&%vrP~M4 zW{QVuof88x@u5Moxy$@d?o%L6(f}!QO(gcZ(*lI{mQBQN{}mWI`?8sNLfXX<jfXdu zc$+y5bh!+Qv&Nc80Yu&zd7101=y7e*Eml*X<OLG%ylS&B!K5BW!+87;#9IsE{rpja zNyx9+()ABTre;%a$mS=@-bD(0_Ta4f1t6|W;p~h||6H1IPfw+EP+^q6_&1VUO?8s0 zA@1BM0J$*ktgj(Bd?hGN(%nVW)gG<_BTFndh!RG~)1*@VAEQz~@tkeY*x3a*vnJk3 zy2{Y3;-N-oT(kb_arTz<hUpp*Uv=4BVo59@y=QEv@&3KVnsjY}1S5Fu`<&|s@lsq* zXhO$T+F49AFO~A?^`!j+$!0LzFu7<uL@+B$Ul@e4q|O^LBtf6Dr*epIvmC}D?Y2pb zEr#Th-qG?k+xLyfk?k+cM6OsAG(5YT{Iym3iQ7k;cBV&wG!|t--Ey(UF_AQvdL1gL z_dCAT*Ku)eX01c^W?x;gH}NjG30F-FZvnzL>Fz9sS~7mx%!{0xjt`)t5TkWalU5E* zudd)?EF0)7Ipj~iJq7Q}fyLifO>)KmhC$gb%woE*VN-EQ;acFZ1ygpS{+3J)$$p}V zZc85o0|PAxb)F<2ZxXNI8Tsx6dBrNSpuKRBzv?`HI2igz4ZFRY1F=|)`7SLZN<BnY zyPJvA!kz%%ICyG43`h<qOlu@|4QUL3-|_jH4kASmLOe1mbLHXRzv!u~I7^&}7JZ{p z=;RNw@o89xi`<fgd@!g-_jJHy$YqS`+^yMyCM-I?U}!Uh=k{)Xsr#q%PXGuj60MRe zF^uQL@CJ^VfFC^>-r1{Abf1oUxt8Tp%{BSl^^tKmApbxl^dnW`eO8``)>c)9`q`{) z%%cZK<dPtE@{^2pnh|%roJ+%=?&cGM4D1_>4x8)aQvIh}DgrD%OUelu`96L9EtwN( z&Pb<nH=^gb4N|q>M|0tU`_An=E8&!0qgrbN-I<2TE^WJg;px$nrq3_m;?*gzL3bak z7-MyoYX{SqiG4l3i+p#|5vQvWt%rLmyG0T=?n+6~V_F7*%6yJL`ok6Ww$_WOYbKuC z@f7SnpJTl{BpP_+8ZnXeAa$?@M)?2`U_AP7y!-F%7s*c?OUQ^MP|!x9DxaR|aKbvv zs+N92z10pgn0E5B861MTO8<}20zAu`NZ0BYiA2;-x~UBem{*@3b7(Tv;^vNwOykbA z&Cm5*@mgSM<ux)LIvcOiGy$J4u)Tpk`#%IR13(bz3a;}ZIH0%OXBT3%mwtDks&G&= z--4k9JD67iFAlWG`-9tR8pXsgK<N1y?QLf=(Ma`n8@>nQK0O_+`OkkM=XLlvrl*1S z12J}HJD<W?7?T=b5*B+7><-WXpN1rhS8_j?JRbky`JH}tAqWk`y8$8y?i#XOG{`JQ zSIYN%%f?LOp<n)ivgG!orjM>6UkbGRr{2d7tWBpbq=yP?hQf-e^B8Pu1+=L%aK0(6 ze4Yl;@wJ}qE?u{hZMe=o6oNahB@!6y__f5?1Veh7BX2ke#NOpsYBXA|ZlKb~;b9n9 z7X=>g(+OqKV`IXW!d<K<Lusk>T;GLUd%vP=He)cdT2Xtx`{jAsoJ-!yEug&(>y5T@ z+5DZ5KKxjnv<vgC>xOh`^HLc+*CdG<@Yy|z?Y)4KK<%pfDso%y&gkbFoNd9mqzKR4 zs<duNk=0-a6w@-e)}gROU!Ia^NgO#hF!GVdd~{~#&8Kom?%GG$FDGi3<ppBX3U5>c zM%|uaQsElU_Bx^dBjeTIKc474DDykqxL#0|jf*X|FPhOVv52U{TVk$w2Wx`Sobo^R z4;ObXT_UH=AfeldYqti9OX_kEyfUb8hNW<ynZK0Hn+OQUf#X+h`UigsZ;nasPFm@5 zm42(RT$4MOey7JGTUBId(T{B0;_)ilE+LA$_-{<X{f{OpM<?g?8{+vX@XAiUq)0<Z zKgahL8xbUv2m9u+{-3TlT-8@IN<~^BzwXt1{vZU1)%lx=0UqNiCpJMBZhx(cbH`64 zI_i}LULIQ4<rcDhsh2wY-0h|$0UPr7SCkK+?pz;RI#K_e{`E$@hwC;yVQy|hGn1dn zP=5yry>u&FohwSM|50R?VQc9`hjLA{Wm7h<OqQ+a*F)Jnx!A|m`qjTDCtu0X(yr}T zIp4TcpSITU$CmUb<h%U>h#^sDPu^H}gO_{ou6PEzF|vHYliVq2W_F)%_6>~hkI%80 zL>p;7Y_VB(-lOB~rj=!sb{V;+a{&KQ6oyRFa+~xH3{w3{4xG*tl)nLj#A8$+4KV5} z%HR0a4a1p2?rn+ir=?b-GVc12sIr#&^_1S#7-c_Wd4ciDs0%uv@T#E}?Fkw~3SS8^ zt!>cD*0scx_@Aj&Eu2DFMq1p2VKsB=xJ;E99Pr!by!ym(0H)m|ChFED(YUBsXZkJe zbx5<H>%T~cL$!5bM_E&AhI)qFC%hVUvT8Mi9S!@d@SrNrmi;0dFf$|Wp&SxO@Ut_~ zlvwpF$wg+_h`1O{D4Lm}S{4A@U<hWGaIJ0>X&*WO`5+8rXZvn&y?u+F{~#RH^wQmT ziL?*Mt$?<I3Q%Vi#JO~zXgU~W6e$@TUN-dfM$27HOaw$*XT!-^Pw)zQ7s*(5E3C2< zL^X(kEs>4@zm_#o&TPMKYs%_*10vz3uCHy5DMSDdC&kKO7K=;WZ1L+X$TYWkp|o49 zY(b%dq2*m-Ix!^%U`z+#l7%i;8Lk^QID(eo34@!u1TGMXF{pz<dw>yr3P4!UgaMTr zqX^P`+#_lWP&>E*+`3;XArNbHsgSy@in_{7$bV@OgXPVh2bu$_pHAOeV>mClgr9fE zv~;g3Wn?@&9DDI;{LZcOg2}5VxSh+M92#i{x*+WfBs*#xu>JzbtR9wk$|LfZN+`UB zi0{qAgU%8eUxXM{tHG(ZNWQRaPJU-r9G7Q=S1-sb9u4^z!d6BK(-kpnLus$elH#U# z=W^2W{jTGDh0`YT{s7AasE72o-^Wu}CabY)pF;gN5~qN89oo&AsW!1;EiV*ardoSt z;gZ>jdR%<qQuCRpM*nSf%b%Dg?&MgLi}B}R%9Ir><FR8U05`tf8a}n!=3>hI=<pB3 zRh1FKZq1(T#$LbZ#<96nU+;J4wssEVZ%%Brh&|LR1Lz(W{IaDz)drM?%=*2M#c(}2 z@->d@ItxG4F=)<tp-fZ>wFx+*Zi*4T?wl}0P#cPJ0EyZGw7}sJ0k9OCKg~W)@R2=r z9A&t|a@-`{%P|-@OBEN4!_a`YIz7y!{2sH|ZD1Y!T#1wr)j;!DSu<e`=8F6m-Vr@} z{o=|vVcH`8H&N^_f|m*6B^DloeOd=Ns)ZBaF<3d#fNAXP{MlvM{H=4Z!R?=!3JGY1 zWyzy+-PN&PyG7KUokCD*GFB#BHA1)Ur5o(E0*)5OuH-y(H-@^HQMU299j`0YOU*nC zCsH>$7fR^sDZ!(f!>|k_hT6G@pDqzzcmC`;dnB{NHiR%%#Uk}j6`(swL^KdqCn&A2 zsi{`o`@Eo$h0fyZFbSuO9+RO12zjB*rSyVAH!deV(yBdQd}%18M|#{g&Zr%SJ&vp= z{x*PWSW>?)KG&+WF!2BF3aEH&(Krt~`F+>9z~GHbzgzyzUQzA#4tkq`ODfu+;J@HC zi+HP-&r+m*JziQ&s4F^3pSb^(fFCjM_F+RU-9sF=b3k|kS$0bFx;2*<CRroWEx9EH zCF-+XlobQN+{{n<a!CgaE7T~2RPoFnMYl3ouwtQ^_m<N>2p_Ai=!YpP4&&WMQ_NOD zuQ-hFk8>G2b5}FMSYj4{u>PMHHa#`H-pyRAvKaj-3!@1Evorg6>AR$1lw911Zf{;M zC7EYaHXkIUz5?sp=zAL1@8!qMo%9)}UAi{R<-KM{W;F>?8-yH`LtTdhl}UP|dqdj& z2fl;lmp=^gA3&Dx-1-&r<aXQ_m5X{`ysUP{dMUnNvS#0SUA0a!r1I|N<HXQ-DpLeM zI6R-%95qJf=D%|sB{2R2bu}R4ypjDv`n#hq%rm3D067IuC*3ER$qyGY;-<dDd+INi z_40QM=R@*mzI?6PxH|LWq7M0M;W~k0y3BQQ{rt-Sakn<hm2+((mkmPiB12^+8(f#d zveMqwXCEf!KInZVYyEjcu31Bb^ESN=N`^_vQ_4K^T-Sp6=j!9TUmwNkR1RN_uqH1V z40GA%U4lFeOJWDFW@j+HnP?o0;e{P0-mD+WQPi+>W66pAK_z(KR*DH5ueC34xlGvc z+OlGO8M#pU7klYgV%yoH$Dqbf<t6b1HGW@d*^a@@AueJYYY;o#z!PA$aE$xftkJoV zIgY5+`Uk49_JAFCouZ-&vhSZ3wNn**XY~iE{R8ckt%<(gEjKr+e``fwe$#iNdb)E6 z$4LM4=^kD6llGwdAt!eZ0XJ~E<g=^e=|U;<V-}O1kW*9qy~4u8TS4WzjSm({DpkLx zuf5(itr}GpawO@(KTA6;7%#9w;<Y^3b1DYQuU7PyE>_QoeYy@AeU~O%LyR=UIy8LJ z<q008ywO<qS;+W(xm%)tc{4E5LAL<UnCww>DeOlk{>r&Tkpxca#75ahb*PkfYnu(Z z5O&`zii&_Bas~!whZ>9m9DtMAWabpfTh>VJ#<S%niEB3VC_u{j^7r%hdi<il*>_bO z-&(?DySkxOnZ6JoAc>G&`1MQL@Hn6JXmV<d6@n&#wB3mf-k~{k9Fa^Q&2#>bycApg zJJnGsB@@8#e`=!?+g2O|42K%hQo)MN>}*Vu5nW}?I^cUjVi(?yK0b6OCL=X^TT$Ix z)nrCMOTMsQX5-1k{Y&#%#0@%>r;Az#o&#G=ufwZ{n0T$<cRar3zoA2`%~=4JM>5li zbb>JOqa#B)l#4oB9~*ysd({9=$#`0$ZeOPTt(Ip|$Mq^lVrT#<6iDm8T=JNeP8;0( zh%wo|a3?s;SN^%9wT6@FJYTqPW!L07Q?vgX_AOp%BVhv90;J2&dWwq0uZxQh4LuQU z6L%Io^^=k3HgnSqbrrKYE>c~xD3wbEX}2(O%HQYsP}|(sG-t#RC5ni2=%w0<+vW)f zu=`sgY7CaqO$w3`rgqRw$oQb|8E9L%(&doCJ%{0uS~1$I;(9TD6qheS%gW#S6zK-6 z%UHq=j|`DUhd3Uoe%g3?mA6e7Q6}*C-3!y;yYm+7$(O$f`@W7ffen_x@nn4|b1(*x z@%#IS(cmlh$Saoyh-P5>2487P%K+6HS@!6wvYW@j^QUvWtm3Pe0-rE^Bm^?(W&bVG z_OEWPMRT2-OhMW#)narABG<oJ-pR*JRFe$J&9zsY>!Q=Cd`BZW7wm#W>OSK{u3H-2 zB-DCw=Ghk^OR`DE3F+a|hT6#}DzuQ(ezC~b0?Q0+P_C9Xyex{>MB6Wi#(D<R8E6xb z>6G-z_A#!SUQMOgffnE0ITVYgc>4qMYIG&{XE%XMcg4^LFVV2U5uV^$Ymjl@Z)90d z6I<q`334bs8vei7z5nzm1pty=_b%BL94_6;$p)U7Jar&smU;f*tk>~G6TJ4(Er$tu z4~SmGU#GU0o_rzc0ou_W<<uZXRJ5z8B(I23V&Y*VV9^g2re(0I6e4YEf)d8(qNz=& z{gobK(~~_}l5(ohZSbGLQ}YM8V_qzAUgP?6AMYH=)zG|)L_=XV-Y7*lQHXmJsQQGy zTMJvBkj*np2iWx$P91N;8o=ukooxMW0Ses;7_|)*`EtoA(=e~|v8ZCGzNc!{sfZe_ zMM^8Ih4RQw`kWoK;(tzm9&hQvIK+N7``)^eY<*|I2>bJ`U#(Mp7Eu}zPPd@U-xd;^ zNM2#xgMjwJxSKu}olz|n&q`g1^wvU9fSk?qv+1z4byi(3F|sgA&#ed+C_Nt|qZ%UX z(HS}&p6?Zp!{Gvl%u2b^SR6A-O4gv?+_HT^Av2GtYg_L&!~<c<WLm3-L`9i%0x9<% z*j8bC*n1p*_JpgU|Ikw!OPsQi2zD{HWbOepdu2?`!H$QzSVe5@?~3l+RM<^1m7mgU z(;vNVNZCl8L*e`HRjVPeyzHnBMw5801b|*NQnNQQs9(m-<U8Yh1z@EWjw$PR2#zu; zV|x8F4AfyqAX?L!QNt?VXoV4*A$41uQ5J(6NBYy*drK-if<3;U+pD@yKfNK#8>ZT{ zlV0U*?i`J_-V>N^H?-<Nj%c^C%31fVJ_J!sCC+8T#&g*nCKNt%-ptHWc_HTe^@3~v za#_oy9=wh5b*RK+(k!VMV;rxBVShet;s)mN>Mrg?c@e~{3zEt$Bh)(~fd|>uIjC!M zf_kOf%ziA}P{+`q#?wzP-7J;!QkKtaXVV*x>Z>cAee=YbP9|Fv)$=;i<*vy27zd_v zN$=b=EMlJUt;0525RmUBlWI&zGMmKe5y*7PCsc`Qrzk~)Kd2gkVCI5KdE(ShS|R(T zv$g3zgp#lL+lDFQEg|Qc!lQ_@?)IGfeExa6<E2q;>xNAkcA?TFUhb;BY~kW@OMEMz zA9~HW?oBkHm9-c%c1<{RO9tWy6oQZa_KL<~O-_?~5eBt6<1NKV$5^j!_~)0XN!)~6 z&6e5_jd=Z%bsLn4x88laNGy+Jq^I++NPz47m-n@AtD|vtk2~$2-vd1OSu=>EAW>rO zF0su;ZrYNUFw`ynOE!c6*#CnCvBihJD(V$M&(H$_URf^DtoLs&#vpyE4||Bk&idXG zoFT>ZLP~brV{ez@19{m8O4<2h=ic6rark}x-R>H4yhG<|WzW0Jm9{F#_?OS$EFMsI z%cpX@=m4f?CK7kq_h0%CH?{y96+~H2SZ!v+!M^3^3+Dk0_h81!>8ldEaP73Qc{{rk zoi>`F4nDLTO-Xo}u1#zk_X=uxePRxVkZ%7FojmmvtYT4Tae$rA0x=YX3M06G6_Cx7 zD)t>GsADLAlJm&$;%V1aYzPK);Tvt>KhXD}Wt8;<POQ%|j_mCMj26}BzRz6`mnUpp zEm;C*wk1wB^S`#C@VRkf(A)FhL!)hsM_i+JWuyQx&*1ULW^p!fPf?z$_q-D_xswsY zTgyk2!KlYRe&6u(YR%d;!q`ntA&tt(7CV(3>4Rpqh+U}2ox2GccBXbarIN7XtL~)d zagWn$lxeQMvWqsXW`Aj*NyHk>pA^G7XME-nzhilt_1oTXEG{a(Z6tr6LnL{2z=&OP z=_!#p_crr<)ZQu2Wt%kD+~_=ol)^STRqnU0YkS{A-!m`!)mQoMq3>kCKH$I6N~KCq zpZwh{88Xswc~NR_i<SS~<MpT;0g4}_d^4m5`OJzWIVtRo?OkBGo<=?@av;E2`?7Sw zw+y2gw4grYTPSVj@Vsy-El($~yp8o1^?@Nppi-YMV6?BQbWTAc_qd%XM{8p|&kiyr zH)&74>TO)d4@SpJHQaSQi<kJZ&&IVy1-n2B8+4touQg8$m2KI)p@t)r@m8&!6dUy6 zJ5@$S?aD~eJ!Kk|ZJ+?s(y2);U$Tvsmyw1m3eezH7BMu~;Nf9&d0|q{xujnH>=rRK zuBYGwEQ4!&o{bLxu0e^>@l*tSD|eLA=Y}aoy9$5GqpAtECWjcywYHyT#@)C6Fk<H? z@~L0<)7ZXB#gcknvyn9-;#D$+)c80HsY#a~7%e_NUzb$wprTo(o7%JPKxN_q3F`&| z{96W;j?yWj>h`)d7aQ@jd+4PO`P%j`#${i&-pjmM2IRAC6dL2=gBx~y(3Xw6J^QCq zzy`Kw)NZDxTuB)XLW_Iz9rVJj1?O*YymSLKu;o20RTyQ_V0hYEgpk`v0fcD&-9_J9 z8A6ic4R~-NzfHxWpXT$Ix$it_re`LZ0A&{`tUX?q3s!oGK`-p_c9^gy&8rAcnx|cx zSoIf>ASuhOhw1zI&n|g*l$(-<pH*p)NPESeirm?egP@EIli5*2i<+(qb^8lj-rAdk zA&cR%YGT=;?R2xG{UX7RQWjgOo<W3IE*G=lwSa{sQc#R}geE=1`|lqYKB8Wm@z3E} zex}+{<M*IAI$8oF6%3*hR06y&F9;-)eRfn>!sMA!c|CZPrtjC0g4Xg6HILL+lo=ob z?aYI1NGo?2?{-wlHh;EuN>*15>6m%)i#ptAo?XDR#G5e?+*Jf17KKsj(4oc{dxln$ z$E)&)@!5!o9XT4E60%IL2jEwOb?tYE%T*wgr}#TzWEP*uLnzs6KX=r_z#;h7I^|2~ zO)yIat8DAx2!$^$*yvXE8tvPy6RocsfH(qxOQ?vql&tb-;`BIQ;pn{8AOJY}lid;q z=&8D!;hMi*2s!R5h8<Q15Idz$R^FkAZe&q*Mk>fXU)5`B(sypibdlhp=2#+jkAh)a zbCj{v@-^nz(jF3B@a|!&qy+bU>=*C*G&g5yh71F;9&(W-Obo!bkX0G{;n;neis|xr z-H;cMu>GQ9!*i}=^gOt+C|f+7=~nlN_WXF{HiC%$qujk$^vtM@PT<zO^I-38KVVD+ zx(wQ{zGO?CsxG3~P+r7Hf*s5riXq@Uaax|<=V+J#r6JgkBek6x+`x*b_3r}4kC|F2 zw#j?M^CBX;)zu0nuKDL##<50)1<}SB`vEJV{);Stn%UydYtOVLFz3R)yhG2!@=Qni z%d(BDB{F)#mevy*_S7@}Px*mC$dC)&>$?#V&lC><*BUK<vBIIbeB-73*Ih!yeBIGT zz_N`g*;o;cK1uzu`jmV4AZ>w%cCFT%RX}Jp0x`v^`l$JJi*dZq03@1&PoEyV;;hkU zpi_4I=Jd|k_q6Hyk-`4S56Xbc;fB#NZ~;EI!bk$a=#kJwogXY(2dNoU$HRvYH!bO| z#-wiQa1ak4(25G#%hx7-{c1R=&Y9x>iWqaLNg55_7tFQ{H_K&D6%z_5sMEGyqA8BS z>1ze65-ol@YerKzDVa)ywNeJ-sqTYkfLM!v`c%7<4LHItg(r2)Dzxoh__jNaD=G|S zF!}7{b*L+tM&MBWHNPuyv&9nHfD{um$P;MD{u-pPFr5%KAQiOi>C%!$v*2Y^mrW0t zunYjxu82Q8($t8IUj<df<-P-3{w^&hj);HV`UCoE{*{rxuGTn1j?8ljNR=yo`YHV3 z8_UnD_YET3zAi6ZT{3Fx{<?nUiug<AHGb8+IM(j%JyaDCkFS|iwyo0eSGVF=WoT#Z znmNZWNhBFTLs%!%;Sj7)n~Swo;Q>C;^LL+oO}qF=t6@GM19hTzKgerL@s&Y|?9|kt zH^M=w(7SIy-!j&~JMgQVrX#JgN_}I2dEA}7WJ$7$zc#OI^6lwAkmF+Kv1nG4WHf?r zKH_`&6^{A;Q8T3VR?1q2<FdJIb&1orT+d$}FmF!%b48OkU?uTNJUbkI$`=ZjgZzpG zv&t~_4Vp6NYZ6@(L0Vnj=LYC86q^uN$!FF~Oq##1h`E2n6c{AZkkk_Z5$`NuKg9Aj z`$%Exc#2q&?_qhk2`Wey{B$h%O~v=$ILZD#JBujx0IK!i#mXU-#pc`T^ZQqu8oo15 zKB?F+{kxZYN0ZHLNy<o5sUMj@;Zl;(dKGPOJ~8n>ktC=v8X@8;Q!ysKEp==DEW4f@ z4T7~UovCGo3lfr_qJYdIAYKce^}RkwbmYS5B~@&==zUdbV$6)13zv~uFHXYahQGYw z^)H>@R|3pahO*w;6Rh*x4cm_96VhjXiIiKnR4-2ib?j&_gS9SW%hnenTR{8h+t_68 ze!rnWXJB*2yGwuBU;YCDA_f>ST#A7uBiW}*Rl1w|H?4}y)E!wXO_~{OKT^Z_Ml?x( zZd?VAOk7AWyOq9+;*=|>Q94mnQc3^&U~Kr;i$`&%Y@@GhA{B32-Dy!4)Z}9w`WVOo zNuUHv131-sY2JOF;+z$8)eAMxcXrm##hKOgnZHh>+ck%&yu|e|WNk~$XKcRsco6_h zMjR@$jP0sCY2rIyQ?(8v0q?pHd&oafz16fzZ(&WjgU|~1d3=fiZooNi2QacFbWW|( z6d@&KaNdmPzA1~#4frn1%?rF${0HKANBr?~Ju%zG?DG9-`4Gv{gA;|Z{lmc~QNDx$ z1OW(DZwV58zWhCIxs`6Y%!-gHw=1|?F<vm;0<PxE*Xqj^h!g7cy4_m7YoKxLFto1{ z<4Sy{QCuHm^Dcq}Y66{)E3!x0MZJNJUKK5RgsHt(&TwdKXY?cEroNevNBlp~s8LMs zR=Dp1bs8GhD_E>lE!87^lW^R>MrpTl4;?8Sma~@V6tS<rckkI{*5MvfgUxz-rJ0bI z<`tia*s%puv5s*-2d<A-?$>q|m7p2~mH}kkh1Go(Th6r96aH|CD)L*#t8Z(u()-2f zsyK7Ko+CV0{7-{Xf+@Klxq2BV(P~Pi(gK34g4ka_XNHcEU1=T#RMj|oZZGW>L`^SR zTAD_;znl&yv)Ms2d>6v9yism>J3`UMmvvYo9(hB7l|P7yyznxIH&vC1hK<^`w=K2; zsWvQMspft(yq9v*TaUY{G0j$1$1@>ZZHhAPDF5s0iK63(;-K4IwsM!AGQmc^rp=fs z-|j&k)n<Ea^aZWntDdtdCW|S(S?#MklHt}Y#XG>V+KVn66U*w$w~=8<7&wOpNQ8`r zc=G54?)y0kL@w-&fCQs(F)f%jaKvfynu4-NK%N+jy3}#I<#`dVN+sgoas%TXvzK2D zB=Sd?Pp{o{?e=-ylwvvAYbCvqI5UFXpD=KlT~^vmYT9a=-8~(8qKj_bNhn#`IWapr z*uxgcn<rfR(2k@Y2lxyTN6lz|79?xVS(XkRkfpQTZ|UPEp4?Zr-T8U&BHP2MyUAg# zGy!Ac-<yq>x{AT_?05Bw%Hw?!LIG)FzlT8CAhd{>z{qLf>8zFyp(|6ftW-*8rg9m( zwtVqz%*>limE;Rtng~R(Il&}icEq<Bg`2pH4Od&02}w_nDt2P4%%3!4(Ty-gf(C~R zfwU~s&)3Nf7x>%hL2nnr5@$jUxPOTix?K~7e0W%vqRk>?jKG$(;JQ?B@P&-oIN1C~ zbSF{m@!jtw?z;&-sQxQ<&+ZaH<B#LfhveXo&BXdgGM{R%Y?zC={JtVXgK9KX2l8QB zC{vk1B@#jbg=X>LJ<$;$MmCUA5U3<YWKEs3P?L)S+8)nfsN0j&*}6Y@nLXi!t7J1B zlf7$395FG=#}MjjGP+hUp$38T%2C^V#yPk2TQXc92y%E}T^=3{TW_7fAi}*8XYFS9 zvPG^a3$%C`3n4S5J5>SY-bA9C1tCAjuJfnpnP%LO9hkK(C8=TsX1GdK`^p~D`g2#U zE1jTnYO=+`rQreUGZF7ox;8rL(DNInBMCY<RDM}>ck~vzqe~(FV>xq-x7DE*%I}Tx zDFM@U5XW5=qe)LSlR4TAXzCtzn;AD6#0L@*<5iLjFWtUvP;}eifsd$x4>m@uy0KTO zsvCJk#-FXGs9yZBzK%@S`;bSntm9Kp1WbrQp?Ork(qh*ZY>465?PC)oH5w8lX3MFV zy5O-zjcGMI`C64i#5bSxv~q!P<@F|z52qQQ)Ll||GbIFA^;`|y?T;e?=mFXhYfy)x zMCpd;<voknZW?(M&*{&}GtalZQ}UPcCIA1o#I^mg@_RCMM?9(MHuX{Bd<N6^5}+mg zeGjv7Q?ot_Px7j%KoBO4aF(%3Af<E*d-`P0n}R1gdDKCTttBg6?YEPfdY?%tOJy_b z`w!N$QHvOI@g>Gec}1MDM2RHFXq1%)m(OIz(I_DPaVzh=skef{t<}C@?d0|+$cCRA z#6VhGH%$$pD<!xuQ|Ex4e!ToC(cR6X$Pck&JZOZK({+(wpAu5I(yD&~8BN4(1$`;m zNJCJkgIIeauq+yYdoH|-?$R<k|2jrDg+?3S69y!uV7k_^Oywq}TkBmLB>w{L()&J% zge*}y5F9=mi#j^bxyu!&F@cb4viu|&h)N+If%Nz*Ty(zO9=<0qp%4vbv!%Dar368C zO+K660n(b*_Veip90gCS1pOrn$j*7r_6g75$9ANpW{~mg_}9-H)cX3E-3;}8Nr_=c ziN{~Ae}9RV#tl`B(a5?hRVu;(<Apxf5x$@IO^3fde5^u%rJ#i(wB#8PmbnmSdiWri zmL@u#!vv8{F>`0zyn&cW9orW%YEqRP_4)qtLQjSG<*97jm}YQHEEni#_D}GW1e4^l zLQ^i^x>Zu9bi|rzQc;d;t{Xz)p76Cs?t0<LSo(#NlInbYtw-Um?e_VimbA_~rcTxN zT)|17Cu_EmE{uzOjIw5r9(=yf8O2jjD=S{Qae?*xHx6_}gYbDJ=F*sNQ|%y}flJv- zl9gOIflRm`XZW(<5#4@H&9usIy@r}E**`aShOd)7vrC@{SlG#WQUL-ns5!T4o5ZW# znUNJ`^(&|Xg%>(E)_dJAJo^ixX#;s%M$vt?vMhV>_}K$7g`y}L0n_A$jVWIK+P0Dk zyD3h#1wRLh%R<|8B;tM6eB<&1%pbcKfRwdvGVVxW<X9x)t7iR5vp|lWgCy46C>i0- z7Ira^;zOi~OQ^<~qrdVp$iHt%8F5zZ8X#KojRI5=Ws$L`CGbfK$gXI;hmmDvx%E?f zQ_!;|`SJTa5o0sV96xK6`Fihx&MVKOW7&z{{Hj1ze{!?XErRa|!3LOyhqA$3fxOE< z$JQ#|Z5yX6OK{ELR(iGV{dxGvml7PQMyOJxTa+R)SfckMUY~LSw&RLrx8d}PR|QjS z4<C312`!4>1)3(UE<s$&72_)w^Dr^&++^XSketI07Z9Zn0(zSs7##oooDig~t~PEK z%#|1suWB%QS?_#FF!zqRm0nO${(8AX?6Ae@Bl#rgEaf+j4qljWXc3E>DjvS*rAL@r z>W1m-;NMw&ZCrZgwP}X;%3V&`t4r4-#n$ba>DCP+GB}LL*0FSU-hJbo#9MA3Z~xv) zcP}n%E5>tY79cKpf%3GO$r%aBor|kl!S`0g{pKZcyX^g|#a?$Gcm)WR3oVtmhmCg@ zDbllr`<BENDF2Szxv!M+aNtSy?QBWe63gp(>)!1Sd}RQ72m)+-Fpw#nAZPi&yXfG1 z4@OczA)#}-*Rz$V{hC#KD<s)`5IG-v+F6+{Lox|316%@c0`*gtMbiZTf&MytyM3+M zjH@(R`Zybumb97B6t9nmL#aF{5>I{^JZYADr&*rYJGwN|Aj8>5wrT?1rss-`2h9{o z(yBprFD4b{m%psnGQ+y~t>`2M9K4x2_!Avo>qT8?r)ei<X$7`1ir4q=9)z1k396t( z`4^`qdWe$nC!qD12uVJk`9ZYmtx+vtj{(vJ*Dc+^4yo$e7(ZzqY^KuFv=WPQ=APtA zO##9cm&O?(YuaYn=fiNc`qBns;cBUxdZjf9Sua<7{(&T2j0CKFCNdW%tT5j><0&Y( zjH0It6#|WPjL`X%ozzv_GMTZRImTv~uoSNAd=q1on-EY6Wx;FGKKH|Hs<8`%nR2Ni zx&{os=aPb)bH6nmzO0))%>*<W08<}9+O%YGF*lr$FY}YhZ$ihnS7@YwLcKP^ff&J^ z|3}kVhDG(gZF^{xl2RI^Tbe-{VHiNVQ#z%k8x#=9p#+BRmM$rY0qO1>1nKVZ-M{~H zyq{-2&E9+VTI;^9^Ry5EVFKG29(Kiq`LHMlpMu6@%dd+fSk{`M>_XGYd`p_mLJrRR z9g_=mM53Npt2H5vj$`Db{C%usMW~VF=_aLK`OAg>v6ai*lfYx+_feN7$@}Pg6^lyG zVxKh6WW(GBks-&~q+4{l$`jO%J`=Czgj0By#P^r#)r^)=O{NoJ6Q(D<Rz736ftThn zG=sB`Y?Bl;-)={%ZJGBB<L4WuzE_O*YaPO@H5&?7!s<)GEbg9YJN`NdgSj`p5o)lj zI}5?y?9v~gCk#_9)+y)Zy5v5&8=`C{9@Q+rnS}0E)7X7{pA|YVl}#6O((@WMqo{a( z4GbPe8!Yn8f-uSAOYHhegVk-TgiXj0b}TJ0j-HdRxV*D>Uta#HIp)Q-sZXxub)<#1 zY*Pb#L^DzQ#j$`R5avG_ZMsEuQ`TH`n5vni&FrD`_TW{RLydw8NarBPcMXConoVi! zgPw)x=NMzjJZ82t%4@{J&cy<u1Y?Tn1f;Zi*oq6q4`CyLtxvLcx3_@4Wt?26qiFTv zFNaaiOMi?oBRQ5p@RCb@D%n2}_%>W*nPc>AcA*D1Y+917r29whag?QKUZ$9s6WB@u zdEH(4nE9ZqSR{NnsJT8TXI7azwKdt`XIA=mvhg2iNR-qz<MAfDRq{$alKyK$9Uj|J zqfMgLb{qGP8_z}Ya{EQ`?wJeJF5h(&!5W6w%+i^~ox>8Eu>?^_4wd%?RS>MO_eJH! zA4o7|p_5=OjbT3wazRUly=LRzX=e!3n(567O^ql#wUyGyevy%PrJA8U_WBr>K>j)5 zs?=$^K|>~bGhOD(KTr`FHx{m&pF0p}22DQ`%8x1Y!nKS}7!V60{WN>{9I{o;7@SOZ z-QA6Be%uS?l~Fg;j`3jluCVZ3*WCZx65}~Se2hhMULzq2Lc+-eEC(3`+RHG$H5QWq z$9BZ#hxv{|cw{-XY@y?M6{tm#9IU#{496M=XBO@+yB7J}Lk@Kk+LkA3i$Wk%YI2qb zg_b3`)*M_q5RTW0I9Z~|PJ!6p7TOmxP!{fILjeR}<nFPyRTQ+yR%ofBBaf=aIQXFl zM3rzjb|r{zo|lt>weWdS5AMG}9jofRL{fi5QnQF{bn<MlfPoZ!9$1oHk)QXGFoH&S z$V#Y7e-V8FuxxQ=08RFbUcI`UBAxA)ZKg3<ekq2&3bw>%N(RpEj@Jm>5fSGbZ5_;L z0RUQ~YX%oVv|Yu1@=D%2q5&f0$u3>+Xd4C*+7hqhdkW+FkKnO5Uv(iJ2x~bN9>U4< z(GI(Wc_81e-^F!FKs}3h^Ya%&O0lvZ+{64^FQ4Q41?JBO-jo3+h{?*o|2wPi7RzoB zP#5d1u$`2^F@t!6U_qw*_Y{i%0}Rx@ak6MlHsOGNoTk%=Po+y(iQTqHyhcoxSgDo0 zb7HiN_T2t2I#u^U+iSvXO1Yr~T1B&NgiWHN#9j(XY?1!)qBvNIFa|+qmQ05#4Y~p! ztLx)Xugqis2KR=BM_JTCeB=YS>bc$pM&mBdT~>YN%NI$D8BM~$K4i2efivo-eWV^e z{rmW9n$yp|ipyZHB*t{~IIXP-V6ICu$WBJi_g0X?36h9mC=xqZHv*^c_65}Y-8mXp zDuem_+*I#2lc1aYWY&Z*vMO)jp{H$mQs(+Zg>+=?gLYF<wCKNA%yn6Z8Hyvw$nM-9 zW}b_btEuI>kSy70X&)2DFqr@L3YEfrMup4<GPw_ZMy2Xx1r{p(W%j{r_MtQA*QGw% zc`@kF=$4`AKj+7Zu@b@BN`!648Wd$K8h9wF=r#2386yXVzegYS_YdsscQMc!-4qOz zz0<By+J$+R|54ls_lveU)-?Y*P|_t3tH_J~Lb@9(TEn?8L9U3$4)+shvOfx=zi8wf z`Vn0=6~6d=-~}V%;_t;ipJ<%QqHO>WHP<?}#7HH7Wa$T3raHABb&dafq+;diP9HQP z>&OsMovIVgml=_V7x$FkcDGz9<Q$2i!-yX!(u~>miM!vsP}<+0AN~Ucl+XGyR5mz= z`|3U^9_TM&FI2rEzx);~6z3ldzS9x0Vy%{G{Iv0zdCj#xHK{rXBS82hPI1>-X|z6> zyZY5O1qtz?)_V5C@bAD6<gxs1Fr3$96|u!P8(v<xZ*@W^77Uk4V;LM@#Jo0<(9`x- zbA?F_iX~<_3}24sbnZD6#@Q`?AnYdEg82ct_ZgW9M!qBcAYMel-*wh&0H03%10vC~ zIlacLoS&yG%|N8;&i|GFT$m|a{48kS0OwH0Vg2ttKo3cBB>zxztbjg5TV@qEv-$;c zjMZH>DQKAx9rZ=(SG?QuO&4Mgt}efv%CVkPBET?+V=ZjS@|y@xKgz6?B?f68+id#V zMS3hKw_M#&H2aIO2Y$$OcIF)SVEE{(tQpJbj{Pr)q893w!9gUQb<0>}fPpWvv`SD_ zMGq#`tWa=C38wrCre0zJpj2?nT}C{Hy1JLvBAhA;5|;!N8vsM+w(dBawvoNnt9EcA zau5pPV9PIC@$#lwey+}z<ZBhe{tRS^>?#{5|4j(hDVM7#i*jqAWPkQD^p)*#wxFde zR)W@#Xd)EP(N}w}BOFDubUdd2(GJx(IMj6<^QmtzgG(wQ?6>Vg{{1l&>;4r>EnS17 zSm;|wgp=QlpFL{^F)4dtUC6xUlw&?-P6km_K&PV6xZXJwhCr=&_AExIjm_Lr^y{*l z3O+WKshK?aAm8R2l{fFf0maiR(Kak(cNWwS+wojrgLFEz2C%@r?UIJHF%KzN@T1P8 z5>+kzOK7l-DOe4PCfR~o@GO?CiMr6cTjWql1Jm{$wUqcMxP<yInn0I%g|whYQ*IhY zS0V;}4>^@&bl9@Dc-u}t9=HhgGX@V?k!f4Udn8(CXS#Kdkjxh{Dtue1WKp}&DaSjl z2~_qt@tV!TAFjc#qFs0Pm4W#=xQ~^PvYy44SC=|cyxgn!5_7X=6!nU-{pqrR1FDmo zr|A$jUhkCCKH)UG+3);*Js*U|39y|@H<IpY*he&T>z^7tT6TNm-p?3qJJQQP!LtsX z{2l}N6Q6d)%Rf(DD%4+NsQmc{`gC)zt8V6lhrBD>LgIR!NSA3s8Vg+cm&7a1-m&v3 zQIc3$(S-sX^ns#?Nt`z&R5c%VljuMT)Hhoe5-W&Cg3kGsPo3I&g{f+b@}+yVCF&(# z7F^Gsl5NPi4ivUxcR~!2C)pd1G41W`SBdOvDjVt>t;yQ8T`tB~(WpKmQ~@K3Fh62W zC-#MNY_ejLnJXKepo71&6>nudm<spp8ZFUk_q19yRJ`$Mu={xj05Ny;+_16cN!y2A zI;Q4^hn2Mcr6IAwSK@X3y4L{R(ol&iXHPMf)2C)zMQ<zJ1i_-y=PFn`K%bCLRD^i$ z+U|z`KawN!<TG^Bdx})*4#(vnTE#GP7{7XB1!A%o4eig)YPpmYD}9BQHTby-pls$a z%e6I#P&XD#Nh?w=T^Yz>mx}I}kmf?51w&NHSdN%ON*PGW*3V6lc~9g{hfZ9H6@uOu z1jBS5;R_hUk<Yrg-(gUH?c#J1)BS#VEie4;lIqU(XPkP7l(*9+84=qxIoNDII!his zns|ij=1;<=>U3=Br<VLL9{3UwTidGXkV9QgM~*LYoQN)}87l{OcvVzDwxZuR>!viI zFp#wVefjNEFA>3^3=}rq=ZxEj<X|%;HF{}eafy%YBtO=+5EQldQzLAQX$TnnQe@#& z%P3~5*!|sgjE+F$RNHhs6x7Ez4dUa62e9{J3msUCpm}aY(-w=Dpx6bE5b^!T2oMCM z_wXRWdDdOoo1kK`w7~p_gJ0gct5n^e=k>A^$+?0>ZVkozv|CSzng)MY;H&+BuwyEr zbdh{R8>26<ofM5PE8$ZR52m?1$YhwKO>KIaxR!mgOV3-u2SqJ%?+5L)00TSby5b(H zUp+4r;!>dDJY6Sl91$$Ktd?qeOSj?`wAMY`uTu1rOzwSxO8@MSx68X6B-m2GqjPw1 zY+iSXbc^|PKjD2K__bQmYFavVz4Wz<fKc=C)cEsV-~9{ai{rSb*MpTQ-==HlSGxHX z57a}LzB(b61b@~})>r&NOtvVnDJCG(JL3Fu?fK{JUrrjBNG(~0tA}gq3GPb1GS4V! z*st_m7+EO=L4S!?l8vI6iV?XcUXbpfTNji30Yfyj7Wwyh5ijkKVf7isN9}!i*(5gD zJ+>lrI|G&YW|c_DrIPC3_|Mh9RnC{3scZSQz7)@UI{kN4Q>u|mMKVNXR=4;W*uV_c zH8jsyg^w<h!ak&hkE77x4J$~1(9gR-v)pV2+Va1eU4z|FXheg4CrKq~_I_j7*dnYV z7?zfh$nIk$F9e|pIw+pi@n;_NU}Pi$eQ6Dp_ZU#TdcwXBoa&4&Nl60-1p|eyHBE%k zx9e!xRbtwV<^|Ytg1sEL72oLJ=bY@qX&a>^7eo1#)SeHN6w|}X^f5>#ic9IM*3-bt zD-qL^<B5!GKkCBKqre72@o7kO=tdWY-cCGdJ*ao}G6qnlLe-MzD>_-R+-+C}4<w@u z?C8CX@=GDqK*T<BxqlCdUW)Dnid&|^0}1g7!60IBf+Svf!Co;Mtn-SoqxkC5zuf^T z%Jq7>B0JK4bHYg&q7LsTfwrB27?VcVMUJCseAdT_&4OHuj1e9VucN#N7rW4wO+yLL zUqI<jr0Gu884NR^GK_g!{JycH&q<NC+Wpc%Ifg}q3fB#&ASu!3*H(OM=#nh4q@rC$ z`(HZV!^^eANbFj3&|MVysZPWbOh(R<)%6@IL#{c!vb*K91oQ_r>7*I%{GxqCy}S_7 z<KKj@VeiDJ<D9Yn<XR+u^9jg)p9Q@KgrXHpmW#&qFRyw2Jc>+XYJWtPMkhu{co%Aq ze4@%@*N$o~)*G@x18;4@xeTHfp7XH5HgeaR0C=hj08hQYk~F!vPF402OMKfOy~8L+ z(Vu@%2BQ5@A#abWr17$VOVJ>2=#`u^S5Hz54i6+g<Y_Oo_o<6mi&)xxWz-e~v==0P z<-piBzU;)ylP&N37-iK_rwq;bj&!iOm@G>*Eqi9}0y>wLhO4)RaP(74vy(ZR9r441 z=rqFV!up=&ZYBTm^fEu{O|ceXyiZRnP<WmcV~(P&SjxhPuL?kWR?C3$HVaUZz<mx2 z`2(cXL;~`&lQ1Rtf@bsjhzQ8b^SWbYpaW=Vp7`#E+co&wQCZkv`b-r&^XT|X4*C-R zs!a&WD3ANv)8k(J3`Ut^Dv_z}{Nc_vnW_IR!f7VE)B~fx7>Kj3O+<*k8Us37EF?}P zc0w84aG0HV+IJh9_>N7gXd*BY*itH1<dngriW8dstSez>3Kn0fzi}#R0|af>LGY7; zWV>D=(ZL}FWVDZ1R4WV9Z(YlgQVJn*oUW=V@bXW%Jnd@mM-(;vfU;Tz^c}JS#<)Uh z0pl-%n3DF&LtnZ@5F*z_I(;Y@5=r4=v(Q94lzu6(YI|x0RV~O6p?PjizaLWKAqs~l zoxO0gGU~H6WnB|c*<f$UHy&vcb?^7v%=^ad5M`ckSJ4=iBP#^okzlVk7&cC}bRQ$( z`vq&2;})02mFd8T$ipdGH0?%FkW|srz8@6T>L~4sU_4bajhECJWFsGTHG9Q{SnM0M zs>m$ec(dDC^&@t9HI`H+y20xjBo*ToWxt=Hv<3UkSic^XlOwj~>whL93dp<*9oQrb zJGXR}3j@vI0rInDtiY#Mc4v()1mjUc+8N4kL8RSMz0s(uyW2^e1zbD^H5SE$Kp<6% zD2%-PO1-w=8>{0;G|YvE|4_Yt7V<kz1^5~W1qqNoG|IDnRj@&p{l=dIi=5IvC|M32 z7Z1rMy~Ue^V5xqe9iX=E8X6@O4Ds1vE~tzw*Fgv0sdrV5M&wd!h*(?yk{_K|o>o+R zzGd<vMZqq#(dr^dYA?W{gwl{0U6Ink$VfyF*k=Z-p0MtBM%<0{RBDKFnUGD7kfl7^ z2snM8wHLG{B^E0}y7bAUH63^4rB$>9$o5wIn#mvTS(RTB$hxGzq$g+P?o0^r^82dJ zWIYP*VOv^)>t?L&NtR9&PaHq}16eEvyn(cmX0$vGA)SMqyjO2d{2xLxL(5WFx=1sf zbN0Nxzp!gcv}r0v6AB73spuqgs_22X?r))e`Ksl00zxgxK;r5q{H)tc|Mq@<Sn;T& z3)d4b?$8A0cq1mP6!y94#(Cyd!mEg451XC7=w?KR=VmS97k&*pj;$~++R`is?LSa~ z+$T?3@-MI-ddD?G7IWv<g4NF7L~V$;6LEXbMqHM@(QX#IHJ!W+idb!GS3@d=f=4C1 zI>N{#yxsLv{(-O>GPzl|4L_U;CB~%N33L9m;rj6c>sYtv)c0x=dn<gD%qsGs*}OaG zB1<xT!Q{bVk6N*7QRU<6d~HN1II6s)yIW+7c-g1y?FOvjEMXu8wvg)YjN{Gt0xBWr z@n+5-=6?QX>LHHZIC?bcKtN^o)I5#fBL@w&_a%4D?TRbS4J@QFSq|-ooO*DX7%S-c zg;Udw+|;{`JC7ih6^C?(HeVZsnU5B{15!guB>T*sF9uNS{fX~l<#gCH^NrG`N-WPj z-koZeJBYt=EZA?VtNKp*h+}EWU=4b4AesobK={+(`RVpZ_DafhpO{}OWfawqe9&rE zm3h6(=A0Jkm4usF=9$oZjyOJ<sg~QnyO^E5YV}yn7iHx&Tr5rd;qPdaU~E!(a=iJb zhYNENskNJFSj;8JhWC^CXrPDjjFgU5m$DY(8q9AzRPnoV$nzn~sAFVzr-=8W;3(ni zDXqd^V~&b9?OgOeFvq$wMA^p}FWhzRUp=%sRaS4_%3*32Ru&-AF3FevfhMe~7(R5i zbxbq9GM7ssQY*-b&>*1Of$yv~REDZxV%1a`ID3vMO*Sf7OksBB^~&c@-cbZgjxq!9 z>dB<3*qRU!&)-LJ9Q?jdf4>7=Q*VlMkX>o<mi>*mlX6fgU0wL`*@T|KuyCJ%fe|lb zSVFQH*}%P@j!DKTZE5AJtvPPTxeoK7_XJ4NKH)x<f{enC^_1Uo-3t~<qtKK2ER0f~ zQ~p&zj3XO4Qd_30xFgp%OuaQj^#8hP4fSw@)%zxo^)7|Ced=fnP_Y}(HT)JgEpM^M zdn+#im>Bj(3lhr&U%O01zLr}cq1&SS2P&-;k}BlVicpTDT9s{j%XiswZmwtf$Z<Qy z&_^Uz#%P|fvu$PEOOP`#cCPh(w`uygDl?W)haLfPSK6Iv`vSSsz<|7cZu-2<MPTCn zxFQVF#|Xi#D2|u6P{b5kYp1k-K(#j};YA4pc{7Z#>c=$}2CDUdwk#4Bs}}m$45D7@ z@L)OF_r)|iFM94!*7OIIEUAz;ejHqaAC8e<m*#L8W32S0tq{NHkayKi=zC90#*76j z-3U35lWk^lbGlgZ;5N2My5pn46s1^xvcyEE9y)MH1#Z{Sne8anMiK2Hk=2L}ElXTl z@q6zP{rqK<Jo-ULj-`Y%a7?VviPX+NP@$DVIpnVJ)!^LjV&WpdfR!{|xC|c^%|=AL zAED7DkP6w)v@i(8&ukK=%}Ah+oo~iq^8A%!P3OoMO%%8jJ=^yVLj;3&*)+@$@0HHa z@#>AXS|5gFY^zTnJ<BwWUb$&(Ne~87`ZjIus2lHkUX2wX?8tqpj@I2NfbMxre&T4r z4+6o1R<EZ~iSlV$`%BVd7F!(q-W@Si99+Ly;f;*q2FlfjbT+~Jj7JU~Bcs0~j-2O8 zHM4Sq4Cb+vEMmbBo(0-Z%yB+8dxq`HS2ekm`teC-V=PZaRn5y=DNQVelXtW<20;gd zv*`*i_bVM88((4x*$tAdTf1gmw$=RuEqI<mXaYN%B8lXAdRrEA8VSmz>Ee0s?`@VO z=>4sCv%jQZo*T|NIu+i2_DpfdN;h&{fTCS9JZYa6_f_{-YkT}sF8-VIw-xe`mmj1a zAz2c8i<Gpl3mmAhfEkTB7vGC~=#}rH7GAIHb+uc8wo47UITqU{zqemaZ)V@?72?y5 z2o=qy;V@kBtMCXcXbfF_I^Ayil7{?NTP`F_72v|5&{)%7lzJ`FbZfGfyOOr)W?Wuo zbU%B1YPI;8aOs08iHsSAi1x!T^hC7BKj`a^e6B|hv?Mozq7IrUQsv;TbyTtYX_Tm1 zR>q8=qUd{)Hd7n4<>^py>Ay+ye1@VAk0oj9G6w;-YN}-2R|Tb<OF~<Rqpp`ZL~#nk z5aXEL@Wl^Qz_0P31rrKJ!5dZao5=?Lybl1{X`uO=tB$m$<p7n1m1qm<e)rWJvJoi6 zfqoCg*_D%C_B#XVCB2AF_YpyZX8k$BK2&b$Y==RlUu;^_LI5~9ja>lpoOu-~i+ZT! zguw87{73(vYg!dNc8pcw9p}vUzt9GcKk%zp1Q@-_xPvhh&D}=PXJRz=|7o>YbDn!U zF*RB9LQh|6M5{;sP6#C!kL878+6DJv*SMy~Hwc`H%F(wHRTaFD57U&@OMVR$b(FD% ze>Z$GwX|uP3{KknUX|ol+nW&I(Qm1P0mW(tdiBi07w>abi^$lV$;FWO6dEq<slM?h zjg?-0)D2Zmj0vF-P`9;sgrq^YXQ@}&H|w$m%V7BA4#`yi)qfl+IeO}fSRSo=LhY9& zT<?xb<=Nc|N@=9?WYt2U%OnFvLTEN<VtzIw?CIYa?ODr-bfZ|Fv8l5z(6cePg<@kZ zTcTZ)GJP!k!l9WpW2nXVi(ez9C`BkbxCcEr$efRz9-vdnx|Vv;#bb?I0?)5j5@@Ex zKA}&KuX&)~Pb=IQC{n0krB{21RZ?lhS1?P@$3whJYx6`w2=s)kXJpLr4GiMUccIh9 z9TlJN>gZqT_3&sM1!~{Hy(`2oe3M_Y{=O7BmM_)o{(A7Nd?OMZu+hDA<_P-J(W!D# zBNwHeFyE}B##2DmB%tKZcrUI%qZ~!(MRlk?PhckN$uDUom}!+Ss!E<N?CpiOS6QEs z<>KOc-F>fRFS~2qET|W)u#Tj<t{g!qj5xb9DzO`t!8yla>WEN2l%4tDfG#mj`N>4# zTV+W7<|h`Y-<%d16VFFY!Da8-gPRCs?5Gg9hy9?;u($-`|B6_;;ONl8NswM~;u0 z3hMuSfkNB<>Fm?bI0szG<QM}Xb89uPW^k92YY4#WI;Vnx`b9q_4Lxb9Y(c_pB{bC= z0`@;W^1l5CN}3hDc>EQVG}F*qmEzz@;0oyyn>Cx_HZ|>f@!JhtVRv{WELuyX%D7pq zLI3@`aztXM_)qg9wwG+AQRTtz4Q;f!c{tTW2r)N9`Pa!pU#S&rZ?QN+F})EY>)C~7 z<Ygbz`D*bnP%12&Sr@P{vqW%g&;e*vAE&xaZu2^-!T|=uQj&QEHr;xt2&qSY{&Fv4 z#<)zEKo0Q_n+`00t->DAI_)ga!WkdIU5GNU8PyBlXakRdbNYp;Tb8o;SL?Vc`-Z>Z z*7SU~Sz+t?|79b^uX`e0qVinzo+No65rk9d#o~L48)%kp(0(71JWl0$$I^jv^DjjT zRHWL`mZ=|Y{z|m}14YGa))ug`pe3p|6M0d&u4}w1sa1MjsvdoGeh9TFTzg-kw~p#P zMlw%K&wZTe0vP(v;qH&;>Y*}W0I372ZHRq82ihUqp^BS7Rq99zMhB0AuS-YSaWsgf za4$Q?eJlRfkPyCPiH>&TkShPl3&=yk5`m#9b#SfOG!Dr3GgXqL_1%2n|4?i(!Sc(? zMWUr2a9-pfFsie&$Fg?;mopjC$Fzu=hom=>$eYthlRbw5CJ-#NICsRc0wkbC&<Xo_ z<HBp0Y2EK}MA&(mGu=8eIu1+=v}sFw82A9~$a*M`bm3rO7~kyi8NVpYC8qZnX#JLE z{`9hPG$BxsV@z&XDC*WG1+qkeGPu*}3?lLd5l3xb>-@&)HY+rucXjvp%mS}49+lW4 zSYjB_&>CX?*m=rnTLAguy;&<r`!#*G(8AHmJ;2q~$|=`8R6#)tct&!@Xl4fCS7I8( z>FLn&V&y6V)xWASzMVaWjcpTxl!c9LG|5-#R;-L_3K+&d)HhGJRJxPK!lxFH{dU5t zN>3x`8s~|zg)i}4laH9s4*0mwU!=9s;AMNzl-FQD@$l?9NY{mm=Zv8FQ1d0SCiXqM zvu@%p5NX9J@NmuY`fgw%N2Wb(8eMae%S1j@h3VzigLR4f{1#d2tE&NI;v7n(|8KCF zpGIH<Iq3Qax;r%)occiQc-N__q+n@i_<%Q&(!VL1e1ESqr6si3EBrD&`k9aEjWYz& zjYYN`6Wu@GHE<jPk7Yw(<gF1&xMO!D^7?~KQ7xKxSR@hWiUfj-9>qONF}MmrW*mD4 zit&|fMoP}>oMobZn6zwnG#t?beaZ`kxJI9s5}QQ&y+>Y)lvo$MQNn;h0mz_Re;ISw z>qUt7aTLTpRAHHQMKL-}bPx@TGT=eu_->k5E~Dd;vLyD;rh>X54*Q{NED~7im5}P& zKj&PNf+bZw9t3|;PlWM_5?l(@yv2ITu{NS1S>K`ZmT;Ap>)v}e<ClM-i_vb3aAEj^ zAVbV`UX`p!LsV^(uh|9}Y{jB25&?{mg_LhD6RQeH1Yj>o`DV~Zcj`yW@C8cs1p|rp zd6KJ%f?7u#Y~Gbv40Ehvyz~sZH8}@62co<9jITIO_#bfMK0Y6b(}<!>O2-qL6+s!G z<ik2Q`fUUl1dP61Rg^oF7NwC8!UD?Qdb9~NY*4@Ra2(Dl4JAyB58y>C#ZI5vUlSux z!#%%Z-3yxMgbc2GK9(y9mz`ti^;lYEHdi)&ooc!p`DHjizx-j@;xY5*#~YdlPg;Y# z_0B{FwV7-!+s=HFXJm_{{G)y_RmR7?Av_r+kp!=nCLLUDmuECmg1fB*8%ydBk<)K8 zG#Yk?xY|Cufqe|`4{80g8_Z){Z=5Gzg}tB1ZE9)j)|qYaa~|oX1dwfBp`cNp=g=#G zq8@wpg|L&h&A{@Lxe_NqWyby~XmnH)dbY9>8e$HO{4V{Xqv=7zBTnP*tCZ9^9aqDV zXZn(FniW{0iQz#`3p5UtrL7M6tzoK7$}I0EF7C(P`=%F`uVSXv-Tf9Aq1f^U@LnO? z&DuALUtNkE?M1@>fyNWE<iip*PTOi<&vbWJ&mVRYv~!&dZT42?Kgdth^i@^qfK4W@ z$j-b?eMG+wY!MDri{7N2RBzD4xcg>u&1rs+$7wCiL|F-y>Lwd?T_gR|?p3@;!CmL> zAT)vbz#)r2i+I?FXW6t6ViCqEpgYtGz7NT|`LE#5vTyujgskUXfA-*xYBBumXTtBv zK%u-xeMj7@Q~e72QZo5ur6Bbt;-6b^su$p{TR)xl_;S(#FnB<WqREPq<NKi@$)SK_ z`4v!rAhuFUHuXj>TNlo_pY4!QKR(Qi5JK2p<6XN43OJ;6iY>}tP-20_V~9G-89HW^ zdo#;;u*Nk%YyN)%{>%sa@E6}1*WDaS>uzi+O$?Tdz9YlQ2N#X|rLO6K4&M6YCwZNo zNw2qwszrUJ2tOO9qCv7ZMF<QdqRNG21vGR(@3&o7e3M-oC`0Mz`ZsxyE_0wlML``Y z;gIphqo`))Av06bOj#oD;=#?Z=)$A03h9=eo+8}CL4?(07Rxc|`=;dJ&RM1N2aNP( zU0>Mqyrd+2Asz{AmO~RcT9Xgt<VWf(o(wyWj5;3!tenNH7>OWlVUA!CiQ6*E-gEi) zqtW8BZ+-Uro7NH;jBZTGqZdgpyj+{;N4Fx2u@)3WKis0<>k<T*xfdC=xBvOs*!1i! zFYP(cV~j6TN3OGX@nohxM6t?CXWE{oJ#TTvP_ASFrLTR{SslOx1Zf0}hZ#SgW1&j2 z`spd=TWboNapcyqWrFQC-VqgaYe)n-4%Md&teaLzw3q+v{7cQErOBM(!JI6D{$z0S z>T~vMOEkAfG_*_mDMNqGy|&#|4rR=<Yx|>1M~h2yhOE&R4etYd1!TKI;!<K_`3u<z za8N?mas>)g9GbH~%JO!*Bp@%jTzRY)*4*VVv)`-bZEpF(2p#+r#UIK$hS${KxrQZ) zH}^Ixw?b~_t@h8WNrR7dS)wPuM3mw(WtGp(v=PIeJKd*LCl%q-f#>=Ctu`CkS{`3N zoCG|-x)M6%>(zPl55)DObG3b{;2#L>(YaK=Km94U=;BXOEE5MmXCfOY4vZjQCq{<# z`ThgxKhX*N1AS>!Dz=@~eKnVIdM6_)8W3M3VI&Z7xZdPk=|3^M_I0#=Zz=7iXy=Ka z#&cgC*$57Jyb&CJd%70Y7MrCWl~mlLbSa)u5{TkYeIuV22=GKo49h&a{+2p09DXLQ zPhl5=gABu|yN=UugEpBuVhD6rW6;^{DjQ$|=D$COG@~kjM}yL*jrXp-_T+|-UHehu zVY={BJ&)0vjvd#bKHa#d%*Xi(@nl-zK<~0n=aAm-gfU;wsCH<}k2S@<agOd%^j5eL zD!dL#ul}!Uv5O0nXDIkMNQN<wDii9$=kMOf1Z70!?>^SZkZ`<jbB*&wE9sLqWhXCd zOx)rKSra)oW}y?E!N265QQyfX)fp;~7AfRX8`0T&W9y_)OU%`x-PLSrBs9?$4`T3i zMSRpY<koQ_8=i!s4=;?BC=4>R+})NXPeThstgzzo`NZoK5wwaG@EL`?IhWBsE%wA; zVZf|PyNtUEZ+bNsiWJrM?9J5;#&i*bTi(Od8L?r>F^{sqx#`%UjO}mRG=`WZN=x+W zVFGf5GgX{GgW+4tkGPnaUR39?LG3Dc4M}-=Gaqe%9Y`5Dor>MF5o!CF8H@<b?@&)9 zAQP!vvlmu-H6*Ic06lD(Pq7vs6eLvqD&WXY&VY+U=hzkwptY~dCTQNBMbCO<CA%_& zt4ZH$i7+af^pj4173{5jN05&r!bWDkgd)bR>Q5l>6>Y#<!=bAF?_hvNbZ!)!sMtl) zTy)MS=Yh6kmK%o0y{P>D`^<bdv424cXEB|p2>F3)pZk!2xqVHe4=#nkxy2vB#`;|m zD+YFlJ?`%h?Dp4&wCd?|N~-LKdkPP4Xa`C6C|+$K3I!r(j;&f|n5d_DV)^giNIrSE zDU~UO<$)=;ZpwyoGfyA+A9U@e*3d6UT-a(Rs=22D=Qk^P>TN&Akupw!ua^w_r$>s> zg(OggxiN6cp+ER^Iicvx<Yft`qYlFj7XRX|RGOiaFh!FktYL#lr`=F0t@<rElO*&a zOKI$_4XvrdlSB1nC-zh_o=?R>FW^4;*}3<!BPx10I0T$7{SQ<wsKq80b=>fkoDOa6 zrb@(v`~y}#ej@7q@Y(*G?v6C_xS83lq!&1(O?v-8(KS`DsgsyV9=bOkL83cDIYxm) z#xM`6NOf6_Cx=G8EsH%4N1KWdMxeGXglN8r#Ee|FX3@f%LYLLl^K-E$$sQKylE{bO z3Z5t;T%xF*{T%LgH`afZzCZg1!eMIjZ{j<>zAgV*f4Re~z+~Irt;XSihE3n?N}shJ zO_5Y^?f7Kvo<vkg8l-yRllS!ME!hjh_vl^>=X}6VGg&P8MKWTeNj!SV?~7q7IjzBk zLyXed?%6LZ8X1bdFhl(Jme7JLeJl=5Cy5W+zXL9Pb+_V}Iv*H63H-Hc`UiR?i$E2P z9Hgy@ZP_2|Hc9lr_A@rwM)g_ZRCtZHAKGyA@-F`=U=y0b&1b9v5#K}1YLLrd;zM62 zFFBg6!oA$AAHTOJy*fXYFaxe4x>qefb7s}<oQmWSyO<526U8$U)(FJ&9eeMC{_^Er zl0C{L7dLVCLL|#gbzMTyr7;)d(w}^5H1ezSs1^1Vx#wX5(!9T0-LuUM-j$8$=maq1 z{S1;MKBp}0K=*j18ty1B-B}A<z@O<0GXx7YVMbiXGSJA8CNl8S&Y0lcAF5Y;D|g@c zhR9_|=?4&Ng82Z3=2Nw1K67P`-8Fv`rp~viM9GKUu*dy8YX2j+iq8d)5%l3(%dS3> z#r|q=Cu(kWSJifpNL62vgNOu3iExb73_O-!Ac58(25=NBL#f!-Jjr5_S^4OVIB;>j zzpW<NckZUu*@f5%3{*BOd9YwE{8E%UQ?GeIb!epar<t7JOqG|gXgPJ#C!mY6g0<_G z1rqMHi+j(urlh$SFUOu5%S;HLbm@|oeVQ3aSNPyJ&|Ak(1q&GQ<sBiX(&(RKPOvN< zBIOFJcTo*d5`@q0Y->VU5*TcIYV%*{qpSlB@4%hg%Bebb+5l--=^Hg19!69ta_|ZJ z$0)uRX#g;5JxfItchb4cYohH~P5I@kB8Ld?37i_)(HRzC0aPYUzO}A#bT_W<{x;=S z8g0*ADn1rLs&ZTjIc`Vs_BFkPoQ8}ET55z)<}UINFj{VT(@c|CF^ss;2fvKx_N3b( zKaQ#nhOA48J<Z6}h?mVrltH=6zp!xnt$W^&M=|RS^YIA@v(c@GJ@%pUO{D%_e*uWk z-H}&?r3NqbsUUK0yl32-1X=Qrwo;UyI}5?k|1?0kQ9I~yRKP|H<?G$_%aZbnkK7)j z$)U5avUI@MVT|LjS`(R31q(Q^08S_2hqH191Xt_rN`~+mp*PPteSrr&lKdHa<XE_~ z*tmvXY}pSLR(8{&FwQeR+Sz7u-l`yq8UV^l<5U&1_><mXCGaTJ1(vml{sVO%W&8tO zrr(na#z-vkaaBxm<wVnmAPx=M-<3F;W2EbEAh^QRZGm@AfNEZACZo^sr=>a1KYtR) zzuqSr;ADt?)6mf~;r|CBO8DRpq*Qh_dgaUJa;j#dAg5R7hWo1tyWF*(nYL5yej}NA z6Yk>#g(4>dT@s_}&fT62&z|yqDIZl;C0RdOfUo-k5@!E_!bS-6n|$s9cQ2NHxzT6V z(axLNEOyxJuZ68BoFLWoJj&U)4&e`}W;l?cBkW^od0M=lZh8e`5)TpFVswg&r}+JW z1|NpnX)T<TTkSy<+C~XPyN{H;ReUKr6`@R}{srga(BkiE2Zo5Hn$R4&xYu<(Xj?RR zZq)C_g;t~}_TT9pT)q@><aZ$)4!U8?qWlL62Sz$W22P3l7_G6Ql)LC|+_LT#saIG} zod$d42v0g>qu8T8>XNk&-msg&2qQn_RGE_0dquG}Vflq0MM1k?bUPPq@3kShBR9*e z%wf~?M^nCG#IND7v{(Jd(-Qena}KoOLn|DGM{gi)Htyx!sSemPp##A-Tkb2(VlO23 z!&UNWm{a6+Jk59Ac9b^^U^-6(G?!3xd_-8^Yv1=#KC48U6S`=oD?h`|efqKNS4Zt% zF;}A!4D!EBJvYe#VbEj^Q*G^tjEecqzP1liZl~4#GfxTVQAYWfIwac5IMqdCC}WiK zyKhT61@{6!xLk^JIxp>zBLz+F^m0o|R^I0{o1=(ARL9lS>82;O!b@whmTgyz15$tC z*H1fIw}cfPXOZbKeny0ANP!o9#0ckg#5!?WJ4kGt(q=rX+`q{)TI@zg>0^ct$j-S` z_V9HXVObr$5<^~9^^{z!rmK`qo9*5nF-^&i+}a*dDF#TA$7A5iH={P+KfXPNwmLZM z>sK6+ppGv?EL^Y$1{lPmN*P(*3c`-?<P7VFT<he^E)WWKx~<;L>8xqpbP-OH(X*18 zyDxpUr`fEiK#okkvQ+0scs{Mm3jaVGl()r(U$LG}JJXSI%7PiqXF~09Isu++pUR74 zNwQX}CD$*Noa%)pI9>l5@UwF-k66Ycj-Z2rOP^BvjWy+W52X+KQnFh86;GuiOJHbc znL{h1V%Iw&BmO7cz(MCp%tLx2KTo&0{Aw$oxetTF9PQRdccyt(DdfuGTstU&MG3gk zi7t;?Q-r3S|G)&XKGJPyCU)&)@fw9F4y?~f=j>9@<mT9)f7nyCD(hoJ{ms=9CCl`v z;~u?b#)B1rSZwqlwc%@dD@q$-{DCuFuQnc-CVGZtd3tZQmfpdjH+IiQF*O-t1C!?; zByZ%#4T^gXQTL`Q1Rfc5ZI}0iU%eP&7(L|ph~fn*D<d(*-C<0O$5MZ_u~h8LH4C{Z z&wuuv?P3v2re%zLOtOLPM#SX3UE4np?ukim%2dfAqhVsBhJ-~Ei=^t^T&n$|(ynd{ zAyfVlj4&9v+A;I(q&pY-^Fe=i(zWdo-ZDnzBJmH@XS()8`Brgpa`WzflUDf>*Rz~~ z86M!B<*9zBor+GJ?e99)Ae$B0qV(Az%4I|_iokO}0w8SkWw5pLSPW2w4UU~64jckz z@2pbdX5M}&3`4V{`b;0O_9Lh*8+=_!OY%8sv*>5WR#=~iMJn5b$!{U;-wcaD%PKAv zEqZ6{Fx}{RJ9%<ao;Sra3K7gX%~0_eJ)MM9fgFq4nU4+eL)yt@DXirzNn>noC29Xa z?A)No5)P%tyl;B-6Qr@))-hIUZt7oAI^Zwt^1n^vorAh8QC+hG9V~3D>|96_vfTpf zlK>KL9I0-NSX}YqL0e<^qIRb~Qa4^Ew&<nEZ<y4UMf1Q;yYolKO9zJ^-&|mZn2#D% zKkn4giInO490@f7)u}8xPgh&N(IjN3Ha(0sl^WW<G;2I+{WyRKJL{LQ20D`4<!5?j zI4$(}ll^3@T^dV4zRRDAwe59;ch2r2ws5Lu6Ldr3oZStI_n%V7t@<;p?+Ov<tM+Uy zTEKja4EzstR=3pDkWJ7bRxRscu+O5<(B`6F<7(#UmXkOy$1dP~cZiJkEYzFJ|I{S! zTy}}qcNM|ZFoIOEMDZHpmQY)GJiolIYTKCG^egG0xG1yp)h=+AHltsz81JP-(IeiY z{c$`NV#GDK$s&7`G3&PX=vc^^%9R}D8ATFFhi3aJ6dVrWk<bu1HGVMG&F!%f^Hpvr z{OQIjx~?9UU2@p>$qatdhF}M`W>PCoWl-n$JiQHM@co9A)LOIW|J~@Bipt}7h$Jj! z5Ak{-26tZ)KM^Hh{VZkP%ugO*oIf#-g3*L~G#1m==F=BIFq6qOWvtG5K2&taFEWrm z9%+dsme{TH*ez*rw&J%T_ZYh?FgT_8nfA(?a8&Yw2v}@QD%?G<*b!;VsC&<Yh-C-Q zFnk6=7v(HXDYG_FwyWf78!$Qc-+?)kI@a5k2JORd1xHhA32j_mNrw}*HVR@awZNnJ z=+(<U;C>ZXrz*y_%<beR=ni8r))m!rgEV^YS)or>x|H!hy3AmF;<xC5{n4Zo^1e2Z z|5+vAd_TIe=EtciL#4q;Fu=X|$~h_K8mR`4>}F)W8r;szY)oGEOoZ|G+qbCaGq@!W z3=H&3pBv+D%W<t1&t-A=a6KvzJtMdDZhBE6C8`(O8$1&B4@7ufWVgi=>D5#~*8Khi zpBL_TA{kYrK=BTc41p}+Czzlo_QLodKNValRpK~bhhN0};wPZv#lqST(>vM<el|c| zx|%Et5qbZmXWiFKiVhyVGjAc;!Cp9Cr^@)}Jxt(>7X6XAOa2d9Y3&_Sfn<#E(t;u) z_I&Q~-h}WSVEhOgsQ<iT#3+r7ta)^6)~0o{Z@<tN?A+dbm`^<G-58d|L|e9`$Z2B1 zVpuI2$eZ=8OiyZH`>3Tapdk1LIB0Z4cK7qElf2<Klde3BP)=L%#4?o{G>df|yB<*= z<W?$KD2bg_phUrzR5y%~EM|g7msVXDK2aO*)fMPmjfq8oFpTD!KJ9c3aWH<p<XD53 zSchT^YHkr5`Mgag&LgABLWJ1{U~vtVKq(-{4O;0}G>H%5dj@r1t)s*xFhyRUDaPd- zKcrt(VigIDkdl6^%nyrpGHZ#OPTxIHB$E1G_eSIcY1jGa$0NPh7>-A7;*uvH3R@lJ zx4E`voOm-QPx24)<8}lW3t)zFR6=TfRv*7V!J*)Ll*Ov#nOqSyH$URln#Cifk9d)z zDm)k#tW86LVG6z)LWExnoTrb9VU%#K_Bw(4n8B}VrWF?L<`RXBVN+yALEov&Yk~xK zGsiqdO`Wd`U?)0UD;W`b#FD9QPErG$3-oo4)yAgJ1PbBT=H69~9<s3>qDk44`ts<H z4?1!kZD<1v!OhZ{J@Fi(F&VoVM*36F=w%2!OA$lY6_JnHk;hu2UZ6147R-G=+d=4f zecIr_y3Ul+T+z=Mpw@`flhCJa8TKj7CXC&npp!qy+PpQP;6e!MM<lspNYWBTL{Emd zhBL2IJEVT;#f?^b3v_o9QG4ssuu+ySO=rv3fB7F-SKjq7$j5%z?q6kRnzl)l2vP1u za^mwE;Axsf{X#m_5v{Rm1GYn}-Sa@Z0PXn5^DL}~eJ({+#W#8CQ5hQ<x-dI4S2`K! zi3*3dz}%*6i!PrOoNV*zN-z^Y-~K+I@5NHh8Mm8*ot>4VD@m9&4sDt=LQubN%yPta zsNv_PgFp;qUnl8vgV0`a2MVLkm28Nb?}iFsh`kF&yDF)h(3!?;O3pW1gP$JiH{JzR zGhlgq_d!RCf)J@+x~V82ld34+OwTA;P*4vMGE4jt>ZZM{(pB5{Q_0Lsc}y!b4FChw zB2Ahb8i|I5%Q&@NacWbRc9)8-$jT{91?WlnKaj+)pS6f$LFdT3vVDV!)O6}#wfJsl z2H8I-7Tp^7&nyOyvm0z%wH`G*h-vr!(8}r*Jsvv`9QgWea&=TqcJCXenmmhX`H*{c zRk?f@(jrr{>dOt0R&SBMpJOWW7utQKIJ;89a4%W*KN}JlV}QD;sBD9`vSu15>u;0O zxYIV0!Nx6JcX6aU?Yg-~aq3sVo8q0#AKjZyEaQR|<IiTCo!<fPFO|*ORpnl-@2xDZ z=e;hOKt$6Dy`mKy7&Gu9?z}4oE^jJUnOl?O`#~QM$I>TQNcVtrBdj?va?isgd+IF= zan{-%(^zVcbr19%wXpt@QJhpqN``Sq?0q-cmxUpVxsBlLXyP4#Whxu@LRbxcnOT+l zeZlJ5x9D!z6&{&pT)W$R_?|UjGlQk@)A><e%|*ZL%pl@_Anbq-6Al80Mbw(A@3Tkk zx_$@y)vohB4j^WK5~%;RHcXP)N9D(`v&3saIZ{CZ(@LpMc3KfZTECA_&C@yBPCryW zTvg$0Vr~2b<#jMK^`<wb2&`zR=l}}TC8~_L6tX3jIR;5}V{hN(ghhu_k=hFvKcwG1 zZJcnr(QJiVG3jS&<T4uRG9a5r&I4Z7D?~qSx2o2sfS-%uY1n(p)NaasQN)}UPv91Y zF^*;eM%|&-W9bz`sX2PA7tXwQ8G*+0qx{EFSFV?Y1C2JxVtipmuAuepbbSLm6UcKM zqF(whXcAp6!0Gvl-jIDSA6y4)zpFBATN8}pq5x>qER@tJiVC9m9J9~<Ht`J78_Utx z#^>9|VTl}Pla*1E^ZsYH%cf7M(0_0Ei>kb8szu(3;BX)p<I`Cvt!vyQ-Z=&cN&Eh$ z6yB>J@M6QW>Trv=jS5M_57)q?3)@mlXVQxU7h{$))-Ya+O^0bja$~|Nssy>a@Rj+} zVPp#<=$kok9nXmTp~^K*wzHhu0`~<uZ0OK#Y-;PC-%PCJt`znqi}Pe&d%1KN<nNI4 zI~8@V`CH3Y22RzfU87ziIWyA~9VkOEKJ=*9M_^(&@h)g)k#a<RU;z>*RWlnTuq6;2 zPChSB>$}2q*X4Dz7TY5>hf!2@*gJq?<TzMyHACH@jmD4|>zZDelHX=*@{Im>0u6+x zc%U*c*A+eFQRouXuiJSy$9Wyn9Mdis2=O<sd7XWBgDErYvy3vLfT?}>u=wehgOEqS z%5;$Zu`fdvcY8o$sS#ip-kL+R5q&l<A!gxnXgDNVU(-xv;eEotbe$vq)Km6lr3!^y zdvy6)(zo$!d#;SLa7qx$&{CfO_1;#KzLI=*S&jd#BAL{+ZCZ?Xl*7E;(Un7v<fAr$ zh;IhndZ1V!sZ*=@q*MN`_g34ah>v5}a6AFaUgrlIhwUav^hSv0W%blrM1a-)BErG9 zyC<dqhE3mL`=A#{OI*YRtgTy##1_#M8Xdsj<Azcg|9HPvgFk*nq<1pe-qsb?H@AkB z)(5a}-idSLEDcl^bR=0h&MdPfM8xOpTvb=suMljcLZLOSgVVY^McrIWHTd$Bjm%iU zI>v9V4=P`h)7YJmf^e?1ephY(l1QXsnwvp{4q%!W#C5uAD;Ha9BB~wo4@TTit*{jI zjqH4xv+WA<2Fzis40@sKgaaV=>&N{rkL>7^Cj)gma);XImX@z6T?l4>rf+UAB{f5a zSeGpWdOIYn$EZhDBsv^uJ<Kvl`0LL&@|y}qB)%$`ekvTx0zW7T2iOY-*v2}mdJR;z zC_eOn@8T+D<;1lvy|B-ONyq_H)Z#EJY=*pW*`Ce(8&9|Ni62GG%wOL(%AZVXxTe`k z#-97C*SjwdlO|l@t;dAXH6FO|0AyVlP3Z1@VJzz<WiLFl5K&B@U1Ag#w_2-j;Us~O zzc?Q+YPQM7g|B;V2hS|;cj6phROS8*gX+!+EO~ZkYO6=xVh9r>nvdg}DIS$J;xz24 zTBaCn3n}AEH54BqZj!$B%tcB0J^X{QdIcL2>cf~7-wgGui^Fle(|xvGsazq#qBZLv zEJpa8YViZ@KAy@S)24|m1w%^dbY2(~gxeG-PkSMFHMdcI;^R26C_t@LIpz>l{k}yL z!1mRl+nCb~@6<Rqe{e5b-btF)+3sB5QM!C1e-Ti-mp)qTS~|p_`u5wdD<v4q`S<)s zltGal@Tp!95wHcF@@Hyt01NI4R(>iWoF5`>?{%qm-6LnPhy@B=lD-=fl21$)VVW(w zfgyG@vdugl1tfPFb@d1e<eC`Z^f{}_m#hwQeVt-=Eh15gxQFbtx%oVfND%6EE9}99 zjm>5a;*9pb29%K6B@3~}xPS&NLaxn&d?zpqVs&D|7)Z_LOS?(g!oi%c_UvmEpXW6S z!{JLwnYGi2<c9+Y`U)Kld=;Qp<|S#+kNI8DWOz7uy1Oha$()Rop;@5244TireR#BO zXRNNbPDo<^tqZZM20DZ?E)oFAMPMjm|Ey1e#FA-if_pk`A|N@k6v&7qh6#M{Li(KQ z{r>%I4F=FeV*Rz6-69VZG<XWK?L%z`DKcl>Z__&efpROp3fkfQ<RD39<5}4on1WFy z_G4m>V*xj*>bU)Nfc+Xo9d*>g42M^mSwf==6%ueFf5ysMvkf?KYk&!c2y^H>f^NGM z@4X9wi}$I9+{<!&_pD{}LOAaLN?>Ry=69;e0ZuGZM(YIS54I-=4;&)|<LpLv3K-4+ z*p|scNfPGQk@HvOsIdAX!TH|AHU=~*!kz5R>P)0v0Z+dCGjnz7Fup2p^k6>M9kc`X z4uZ+N5NU(Ksd~Rk9gnC*@dE^!81Ca9gR`PmJZMQzh|H48Z%)7Z;eGp@Z#vR2WpY)t zWx>)V?71H3Sr^DXw&w@3&q`eBDBs9ekjvS5e%9!d*qv|n_SW;m1q(o4<W$eW#US5N zpS7bJ>)rF(y5)A}htI7l)m1lU%b{k4pt6S4r0Ne;s1^vt;H#4rd|QG1;mm<mo-tS7 z1PD;;=x@Xt8^87S|8V5nsr#T50qt}MOpYC$^@0Dqp1q1H5~Kswg#B`bxrk;{N2Ghu zRNbwdq-~{m;e@AKFP{m!^`_5FCg>vKnn)>Ic}G&)L{wm%IH7qB=;jp+0u8y5IcV^B z@cDM*B9_*Ni>=t%SfS<~hUn@8F`a@aKleuO0l7q6e8eNyg`{H0rFfjo-qs$a&L??Y zBP<MWEb2PrYE^%peU<WPiOi|==-D&YzTSAUbp$XJ`S>PbyG8ufscQMB{_+ZAJ8H>% zyV3xjEN~?k(QCAj=o0miF0^RRN?NTz=C}7{jLgRN4z`V7^w!N$qrC?UjLQ?ElP<N` zJF^IK^B>s9T#4V!KE9h=lx5FoG&X*+-Vl*w7Z3sc@xhd%Za+17ecDrSM_VLtOTU{| zab&Q1(2X%oBynGId%GxX&5;lmHXnQW=WVvu-rXSPwMI*e2+K}<ma6YemV1`Fz9piv zuV_Yc_@FysGa4ySSgK3qUrn$5<NpCbLBGD-80LlDR&|K+9Jwk+bBqEw%~n>U7_Cgk zdFGM`;b|G7jFN~?3QjUd-vjuhAh9HzliZq`DkvQrpb!Z?L?p3aPo2Yo&un9#e-(`K zWDlh?wNm*6gTWpvc5|cz=8>ARH5eneoB9td*3L5&vc>6_9gi73jz<LctazqaoU=xf z<ym&Va0vth!5oa_c*y4?imPRcnDbSh)a2D<)k`5yYEp4m+uF~2Foxb4CzygnaVcgX z0!hI30H4~Z192lDU=z&?4Fbm`xRyf_V4cBtbAy48slff}Qtnu-rMFoM!zosD9hk0h z{AAT6;-#cdK~vKnpkhp!Jd6wscH;;6s*^(Qs4qxPYWr(9xnKY|sJbjvp`73hn%Zgl zB$q5Q39asj(yu!%P@LDW^f$t3^*xeEqTb&b^&EHmS2j$_JQ~LvXPtEKL(6w0Xpt9o zH~<b!dtXWX7HEWWY6L*N{UOUA`d6d$-%IIzHxy|cNb(Q;^ADJs>LZa0h5!uq;<#lg zPs!I9#wN2y&{Iu=(6+ZrScm(gl0Nmag1{hUZde?Qob&HjNq-utMLV0qj`dpIh@vc4 zAonB1W2PrUl&jdu5wTP1kbn+Kp8o)Pv2UZaLx};;WnUGhpI=R^rCcZ->kv+L9i=2x zUt%hbX?@;J6eRb_vNv*d3}l68WF5g3BU+8&d1J`re8h5VPZpma=E0hORFS~#ikqn! zk~@26+TP%B2iCE<t8CX!Hq>GMjf~ptc8W<4Aa29C`&NbZDP_MB#TYG|i2K&w$4iOg z3J0#kg6w`@t}8Q6>A0;gKQInR{UDwz8RN;4y^ZnZ!-rSD`5qW9uOmS-Bw@x%?Hj*= zSvGbeEdKyPSz}SklZ>BpS;AC;Oq4mtw{PBv<+#3y)-+X2;Fj--)#G1QV;r(;*&5|y zmkyqpSb^*l82VRJXo2B{0Wl|($Lb$ZSh3m_E4K_=!wv`5vs5rD@=kW=03!FKAmTD* zQ%a4vcUJ-;s-WP4IW=YErWn;@<=FnBdHU8AbFo(-pQcu@<iDIqSgF{YWR6MudsfnP zj!3J2L(jU`;_pv%=TiRwh22_S-Cqepzo5Ah<N_J9{#-ERXU%neGu0YSvniQ17-mHW zFh!mKIL-xZWz+4in9Ffz15b>4l0>D5`{VCWk}jPP5h_J&x%nep0rmp1lwoQ~eNMRM zdK@`^9r5tKrgoLpx=rkg*4l-mhW##%zv-T9V=qw2YGjf*Eqy|yB*n6$jtgLpKA9EG zO(vN%DRFUaZ!-cNWR@i@k=Trm_2NGtdQN-$Shb5ya??VFC1tru8KX<B*a1mQmN*y- zwD&b=>6Daitg6*cPB&kH^gLe}`eOG@w{xvSZ+5^LZDEtuc;EcW+X3uy3GKyfVfc+? zvYT67N(k;>h5W(V7~>#g-;rMq{5<Kdwd$DXxv;mINfilMC!HZXol1uVh}=dF4?K^h zeJRlPe-Q0u(&sikJ<?03>FuUW)-Wx_#nKQ%GZx&0l6(IEuX^=kho*~i$u2*f9#r!6 zG@*(&tMISIp1ITA1E<|yopV^YI<}z1G+wa{6n@1=l010&S5K-Y@w5gY4odewO5rSG z)wHW77J8&)M;_SVdsQJD+~ACla53~Ethd$roKFw0E}tw)PpJN!k-PE13zi?YYl9?~ zPrS~^L$|z9zOl7BY^LLEl5Wq)RA)Zby|b0I1YN*GB7DX$Ir>+O-}R5gOT9s#P0;k~ zcrAko9$6jTh&`htf=E0DQQN;b9sdAGYublW^yRJQp>HOYe%aj#8(08H1cS)P=LfcH z4&QE~o!|45spRg(&Rng(i`(SvX0|ODbVX)igPsm60{c#kKt|L7K5V}@svy=@C-ml( z!*<$v{`Ihy<P>G$i5;8#)gGB6KU5kta9uP~NBL`r)-l5<8BzB3t;~9nR5)Gkzo_Q5 zR`zkmvqF)SMr`1Ur%<+gSbs4es;;00^u|dZ_03Hqp|vTf{{TM|b#1R(J;qM-ROP;2 z-&0%5JC&U`<s{Gfl>Ddb^sX}A#FTl&rJhbigl#zXHL{K5v`EY#00`e6GBJ+eRbb?| zXuQ@rZ5O%Fk$+=&E^KaCU|_=k0Hlwn*1YfHhvD<0J~Ihqn$<Kep>dO`S)gB0z2uL9 zC){DawckO0C5a?QW>d&M)sY0Yw-bL*0W$N*>^_xk;O-30)YhDe=={<6=lE3WkA+jT znvIX<bhm6ns#*f@K6?UjlyCT32ZM}`Yv-*)q-GL`;YWLiaETJH>d*fGTz@tDogSfW zduc6>p#)dfR}1M$1n3?)6!%gO1byq~ACEtQn{8;^bsxhfNG_0ikD)sOjyu4|&-^X> z9~J81>YCa|o0q5ZkI!C~_)}+h7NriqsJ54Wk+8KyWn?mr+s`>)vHk0Y*7|=@>ASzn zYBsNJAsdjKGDyGgBMhrA)kziU9aq!3=S^z2FkP+9z2pqh%LUS~bYa{uJcIYGEFEV( zv_{KPw}$e`&gQncUs57{{nfF?<sq<p*QJfgw<hlY08gQgSBa$<#;ec2*Zmw2=8Ekv z`by7Mht<%f5fwQ$cTO@%{{V7c0}eYj2<!_Q)2~8xE13fH_Nv~W>)T~1+s1kMc;dQj z<7$l^J1T@iueg!(41SZEo4Z-8r)Z~*qmX}<l#lhREOeXgOybVY;_U8J#1S+6%y6TD z+>!wM)R0viVo6a8ss<o$Z_+-e=}LG#^YyO(0H*#8-)b(>Ynr8pO<G6#zc(_y!*V`V zg--1ApPy*&&2pONlW(MJ`i7qc^jEg<Gpu)Pr38QsgMdy6?TnF=-_21@E?QS6j}(+O zwYLVfTC4_^=Gt2;hlXS^%7+Y%+!_q4=^})~f=PV$?SYS{{Od%_M*y=0UP<5z?#sT7 zr^Rfg1oB3nxs4Tj00Qs_I2@kj@^WjD&`OK-mDg~O<_qq8R^sC2=a9(FmM12nQlwrh z$+Oe%j1KY=*gOIcYR`iGE-u;8w*Wb19lz^b%QNlUlZ@vTWv%4aWbHgR1Ro-?H9Jd7 z{qiwqDA+1DAC!G-Z)GqMDwYvG(t976`R2Izbx77wK;Ihx2tBb|T`yOO+Ez{Z0Anq{ z@!P<lI_qGQ$fuIN%6S#KiptV8$Q<MR<NYgyw%621<gkn*?Ih%6`|(@F)>)N`$c}dM zbDnBQH#17A$K_H~WS->Kx=lx80Kl@2Lwo+!iTU#=D9F39^Ap8k%34UnEV*vPF96kw znZKMu$Bbve%}Lfw5xK$Oe54N5r1e@o!K5XJjBsj1?5TwW<MnRyKzCgi@iJtIp}1m- z4g(JOub{dwtL!?vLZ3_2RW7WqVrV6a`3|ID{{Z^@9&6+HlrPT;k<Lgp>3<D+m91}L z+{Hm5e|nTb;C~xF8_)2=uP?Nw4AzCcTUm(5sS)K*{eGa=0&)O2ARff|ueARF7QHWT z)qMqTtwif4p{5bIfys%uBkP92{mpz<S&D@ul^uq76k>?ZXrK(|k;N4Rg%nXh6yGPO z{%=!1wkc_)mF9BtJ3E{W%yM|(PzzUCT{V@|b18KyfA?*)<L`q=j)qh+sWE~B1j3R& zoOk-v72M<?Bon~%Ky?GG%(BEHwrghLa*~!U&vTzYY*$fb)|YF1Q7+|@Xw)k+9qI?R zPZh=Koi{7X9Fr3olk%wLjdty!+H|&(x7ypAIM@RPk`L?8$>$yi9nD7o^ge?4)zrNY ztS+OY>(c3$Bo%2OxO;q$W+*``^v!oS--#ybePYhns`{R7CqhMLx&HtNQtu(gdxAG) zk30ikA?y0|n$`T$q+-_HkI8QycBsZHRt_rBbtL%`^+CmLete%#d`0+w&>s^ex4Zaz zKBYa+rav=xbMv*#?<<||fMs*r9R89{bCc!zhrlkV(~!N#<>ENW7apW1x&Hut0Oub6 z0DN;^ewP>8CCBt`p4wK88+Ek()s!$9Q|v}ZaCkT%*IT3fbNGRv6aF97`n9kiDOI^* zqz*BI#a>d@?yKl?V}!9SDc{xZX?l~Qb)J$)B-HIhF;9^cvCNVy<Gu=$T267CaoaV! z>aM%ide-CC)-L}5H6f9uEwCwPW0qrq$tNfGtrmF4iq1N#)ydU87wF^UVASK9tw<Tp zdvS_&?^iQ2GN8;S^$frOq!2(LfOGq2`&6RFV0LFBKtNR6kViQ=@s2;5pm(d<a`7Z( zPy{4`ssTC2bLY4E)S{u9LTJdPEmA;K>|lZRouuP-IL$8Vni3T3z<_z)Nzb?a_oSkL zU}?8-II8j}2NVUM)qsp1Iq_7fs}_Y>U^H%|&L}3NfwEZO0!;zwQfG!mc-^CQISi)+ zAH62;y$PcsUWQzXpgmn2#Q_5!Fu};pO^`uju~E4|#jpSeoM)ffmpBZgg(tD({{S^^ z15^-p?eUz{P$}9fWK|B{nJk&$u)!nkR=#9*ViXc{g#ZuWQk9^xf+<L=iqe&+En-C% ztujVkV-CT0xx$S3KT25L;eFA&M%W`NaBxNl{thbnU@@$1xlZ-ng>2)JFny05zM`uO z&ol*FXxmpjdvVIPMtJe`sA%?)*yLw}LP~a;x@%;LBz3roQrOBS;8f!$IRm$ExZ~cJ zZ4R4p^9IPz9x5mmQA}2rpqnFYGD##s!aFE++SoYA&TxN$QAG(i5_)1JD(qxA+@)|A z+Xp=V00OCFp8S(i*vZCm<27iox|YIg)V7(d;gKPd;bSH=cMMTky8^)+fO$Jx*w&^f zqk5EJ`<Q`*!*DtO0QJ=*)U_csLdcXxnngqfoyb*D<etK!;up1^NTpq}M#UL@h&Upz z)8uh*$rY+y%Q7@c8zFKF3}rz705I|OtTr?(8;YERgYGGj;1;s0#UosnQyhXo6<3|W zt~n<wgOQ9L4tQ3L{V!8vdU@Nw9B11#T|)?m<IZZl5`E2BsAM<;CbzmafcG*kSG{K` zCCPNG!?e)ld6i#lWGj4(7+`=$BhNgJ;N%`fdJjr;JKoP41;+356(kSf8sGG9KuLP* zgbJg(Hvm_)={ih0a0Do6N-8t$10g^If%F2nXO+E?)fgqaG`%J8?$b+Dmg^GAxNV?( ztM~V=<MXL*ixOZ1z*Vd^atPWpj>9;tt9ZmpByy{dKK0E<LNcHtJ+9=P+}3Mq;GKao zq<HRY0yjy?+nFD>d<v9_BaNAkTyQz!s_wQnijVxcGUK?He&C9u-_P2n?)~FZo0liJ zUgEJJpJam_{i_!0awH^16|=@G7TQ*IMk-R>7CM$J#Yy9~ahkavNZ)&5HjkATzAIho z$hj=ceEsN;QS`<D$?wf`-5pLgsMy}KNQojw@t)^B@mZQQPjKo+<Q`A^-*Z|Cbyu8X zN8iT?r;6BHYIhMrV~xB4fO}La#_f>t&qkDXZ7g=^2vMJ4agVKI%XY%$Hb|kd%J@<4 zYh7b_;xMWjcJfH7-eqg2{Mv$~au&Cg?ODeRPA%W_@HUC4;^;in36%%Rqkv6xHkVB; zvOJgvz7+9Xo}6L7kcE!~evo(qvZB7a)8sM8yVZ{v=AtsK-v#nxQuM8wI_R#;w;0+u zCqLS)hH&qVn@`a3T8mBUs;V}I`I{$_YMHLCSj!5ualec=iqX2<<%~5tM@S}Ys2JPs zY3*eEz+<$A92O%Pu76F`B0$-g?-(0j-|1V3Zhd8Kq%wof51K|wLU3@|n;6nHPzwR~ zH1jkm7wH6e^ONmbol4kV#0tNsDvT?52kT!yYW+X)Z&1E_9bKDI(j+V%8!L#RR_+E0 zm3_R93Gv80)Vtr{m_bdg_h)m}-xD<Vnq42KESq1}$swYT{{Xw&5yoA|86WPBc)`VK zG=GN|_ga!_9Y-nEb!E!NN`nL{f69@Of$g6Bcdtq4eudMzM@F$rYYTfBglcXA;Natt zpExAfT#B}_JnTe-oPq~oSxz*o7T)jA`Z{ssN&f(I)$F`q@A#Yy-8(LxB9#CWI;uQp zKc#cD0oY?Fx4m`xZP~HY<KG|>u*Mi3{{ULgyuG$bA!z{IM+HuCS4#!G<UzylTxZkA zpL*n2w6@~+I;ENAjAM~(V?_<o1%$$Q!Q8-bPujCIwz`E~XFJrKjpvXnUmetNvqrce z{{WXYn-#q9%@nOMXxx0GxX-bxN1cl0i&3U!$7+$!`HxaXlR5Ok;fK^#)(I9TC${1a zF^pE3y;hiJbVVPjN9xbEYbob+a5&^(6YpCjmk8pMZmpT(;o`Vw5&Dua?Sa7eqfot? zC0m%krbxt@{{S!TT8VY*YdITx{{ZFy?cCNy_ORCBBr0dOkPZm#Svsby-U?1p^#1^} zqPM#(YO+e|ueT>0X0UW!WpxQIgi>#HIQCKXs>DmET1E1-U@k@*^qO4{NB;l~npZB4 zvT=puKmBObwMMumJ+%qy``!)eAdm=T56T_Q;8rcwlZ#knjr~tV<S;z2_N|O+Hv&Fw zy^oQfdZBEjj#DE`fs)u$$MH!+;HEkGvTZdso;a0|qn7M=#bsVK-=IueM8ov3=lgwX ziEM@RPaXj5(YPme>dBJk<4#D}`bQ&aY;&5ckBM+v4&<CU_A1e}%Yv@b+xm>S1oj}0 z7_PTQ)*Yi4Fu7cgF~J773H2LeeY!b4DI`BJ{{V~Hx!;TZE!2HO(&J3&uAxquy`FSw zr^|sPI5|~i<q11ZM}S9~!xX-DJ2PWXdZx|y#t()c4*viUHkWpnx`l?D)z(S>0P`qi zPgXIG7G%%Nox9+V1aqIw--v$%E_5!Yy3!=Q(_}F-0eKD5yb=M7GLgy7M;On3I5_*` z;UD2QqxuI;k+m78>Wk_Ad8EHT(av&0#11e>AFFZWy?Vy4(7hkjeKjvhYWf9|XwwbR zt-K2pLf#z;kClc`Ke5OpSEmfi3*Fj0sp0Sa#ku(nqjX-E)1=*A^<|<=vN#r2u}LZU zbAo<Q2I2-jhbI`tdKXE2LF#P}OtjZ@-9t};4L0#)TXrMVjo2_&!(mu{qsikLz^<?H zH~3W5eh+Fto3`Dmbp6Z!0OfT^(*OgXl2AWOkKwRL@(0TL=9_sO<QI|2`F7{O(-rOF z%%xI~Cy$dHqMRkeKAQN^)g61(T?MDUqtQA{`m9s6@A%!cHn&$T8*a!XZj-)S8ToOX z0Bhz;j<(jWP^_Ab#sd}+hWQsHmgC%$pWN2Vq<$ysT_+f|S>EeT{{Z_*n<}T>bK1I@ z-$D3|3tU{rEpM2d+(UsdKk<&z52?XCe8Re<^}A;_HQQQJrSN&?x77FjL3sB!QX)H? zmA8#zO#Kx=1NIfE&2@A9G}mRjo-H!UZDuI<A6CvX$7o%|vi_yxjz$Rt0&!fIAp2Hn ztE6tzt78VMp|_giIi!v@ksv_wvW^i;1<Cqllz!r|=*>w}chm40T4^!zsK@VHg9g<H z-l*wVlxgoLmVQNTC0*<WJk}C>oe^6VTib5rj?}jnn?V5c;<pe-xds3}^^bLL?l`KZ zOGdpT{*YG22P5fC461nBwD!u?{*toc4<6N4#f+2Mtz=QK)8tT-7TxkMA18d*NnxmZ zutba_S$5%pz|K3@4<+N<up_>6obiups!dUvLiq%@Cq6%Fu~X2V;Thb6pO@uxjjdX# zFB}jHbB^8xX{|Mwg}<U=0Y=}H=f7fUh2q?AP*HY#q!GqGhs{|PX3jZG@}oIy4(7Mt zm_V6UBxC*G70RXBNT+E+cu>lC$gQMzB~v@NU*e>LX9BF~tfT@vf-#?J=z4DvxeB?* zk^WO$#-Db%&g_h1k9y6wzG$vhk@*~V@j!dTcM)sdDXQt$SSfR7BwI<(&9v>n`{38X zdX=%Vzqo;h4009z_4YSTYY+Jpa@&`1AQ8a&=Dt+;ivYLlO<7c7Ozgw=HD*FN$|$0$ zNKr);1FX!5=%;G90f)Xh{{UJ_Dp_`(;P4L=2T#-YJvlXu7MJ>er+5s$rSs1FSy(7c zAjS${kb51+AmC$f6^@H*bbrHcW4M50GLwcU&*}oW3X<$pE>6%%13UtI{{Wz>X?m{B zRMv+V74`CarlqcQzr(!}JNpIy02|h>aruAD<I{tVTWInK$3Faf*Nkgg<@ZqP65i?; z4|8!LB(ruOKs*nnVHYj5GZF^_p2CL4<F`9=#%f}kTe~81jGv`a1t4c29z6J{qMS^~ zk|Ljx@sL10yC0$Zd!Kr-nKC?VQ`172P#Z0rw~{#H?Nb+~8!`qe2>DciNY6a@<LGLt zY87KjP{`&m%N|b9$0QFKI2ryZ9jR%@YLW&`n0?!`kCz_#9sdAq(?-ex+@-j{=NLZq zU0LcDMN^C}aex8-eAI$!L8u+6k%};3+#HOI)R4&CQB3Y+c5{re$-n@A0+xgl$8!^a zGvHGf1Sku%fr4}2>q%0$&pF4^fNF-Q4N?Fgl0YNJy+|N;t8Q*u>gGwARZFpAsG&gn z001AgQ&IsSoNznTSR5L$A8P}_Bf;!yfN@M3tPGVK!;;FF8Nu_<Vf_C9YOI<}<LN<a z5Cqh2sH^9JB7)i0c%^Anvm|@BNAnSe$OAlQjPOUE0?7~~dSXqaAv<??&$UQuSY#1C z;)^E)ZE?pVAN^_p%>jD7IFhl!#V~4;v}6X&Ng3@_t5xxs_CDg<@HT)r&V2sWHb8__ zZKvH)x_~YjW|^HLNK~F9k2_cGl27-lidDO_MggS)ou5h+ZEWK>_Zh3U(YBN<qiqhV zLhS?`bCaB!zhzl(FoINPhC75pPo*w)oQ!jV54WEc6it}6?Hp5XmJvE(hQh^pAb@f` zjt}iv<!d0VOj{Ajk~1RhVSw1=gM<4VAKIXsF$k=y`{lg3nn<l-v5HN<`Fnla7XS`7 z7H_X7%~DpBtwj=S2&2}KVR(bEjfiGIcw#exN7E<yH14%q3ee37ESuIf+N-g4<wgnQ zo_+iO0HtHMYPsr`HA2XRk)maEVy+HD1A+}9IH@L(oY1i$n2n&43C`cG2@^>BKsobG z3{5M5xzD|Iy+6~<I}E1cPI7zJQj&@<7}<}Zbn8y)O6I+9q&g)?StVVuFC^o=U()?1 zkRnMU;YW(>mgOQFBLE`zHRn$wllVJfj*XU`6gDs_M4NquX8vUEQJ($rS_p2XxPAx< zd!MCUJ;;lNBxlL*Sh*yQg$s0aF<h?gxcR+@HJWErg*^RhlY;$}sZr0?vR3_Ma2d#8 zdkSkr_MM$1_seYt5aEwtwQar5VJgFU##o*)TANHvJQoMC@G9@jdQ*_h834;;{VDPi zNon$4Ze@li^wEGkZZ(N$(p=8vn5uz=`kKboHL)TpLU{h!tbIRHHmcG}z_I-!!1~uL zoNlb|glD13w`KZ`%z?yXa{<BeilzsAfcY5}k8ye~Wh}C8Z>Jdg)yo(=NqhtIWS<_@ z$t~rv=tF3v&AyFe`H9q$Gn{+ZLbh<N-U^ga`k&spJwH)<He(v_<AGaosgn?AY_a;* zv2jat467rZ-p5qZM(Z4K0uXRO7_1AZR#jwSfKT~rO{Z%uB!V);obk<UOu|)Y%Mdfp zJJya$NXj_gCq~CZm(jUZ+!Z?xc{RS8CBJa0_*nU71HEzf#nw>BND0_^t>vBE^2P}} zPn=a@J+!hiW4UWa>92wkio>_-BDL~c2b_rH-=8D7p>ZRka04FX*22yUb#l&d=yFIr zW~Q9oGdqqIXu-LJSs-R4$VmAQaap!^S9dZjDuUTqZX^%+)>BV!cwJa2AN~=mQu>|8 z_=86lA8!kR#YC2ucd=;6<W$z{+}~Tu&2_VTGAQyp{p%@nC}Y2|;gt5RexH8RF4>eM zp!OcsmmH}8;X)~I9&2c&TeF5%op~~r+&qFskhT|T1mqJ}D@bkRP5%Hh4ZCw#@S{fV z;mZa&0C=j_@AAtbYhYYt<Wx0Z1)*};ZemGcF6NQc#&9q*+O}GK+u7}9kqKT1^IA2$ zo@;=lzs`78J*!~WVnXqfVqh{`HN_roDE4KtQOLxe^c$Cy@(X;sJAJEXrE0O<POzoM z$IezVG6}33Z7tZMDcu7MmdE;4bLck~l0C(_QdM%Syc&fW#p+0z<ij<h{)W?EgJ-Ad z+;5b(DgXkuD<dpXw7JTF2=9~cTuqF!UGG+xWTf&W@N1!l-dXI{HJiWceCI!En9kd2 z7-qzvqX|ocCg(`?uHIt#R|5zKe`>nI^$-hdt?^7r+qh&8YPKm0L{PrfL;#WBKSNpu z+a>FZmNw-+WPIc{DLRX|`6-I>ry0Hu`qSi9%j4<qS&5HR0Y}U8j2|_^$E(KD360Y( zP7tx<j^h=)U0O9kEOILee?iVUtyH4X%JEJt=(Md@eDcLE1FkTqgZ}{5vhFUSXWU3u zJgSr2*PPwz(lzP1fUtEtcOTPTRgR>WEg!G3Wl#uj^7Hko!y=cb*xM!vLZ-Z%8O-+P zDIt}WS%Yl{Ab$0jy0q6Rwn)?4=Im<ZVH_>;;{zbCKiahVjqIBWdIS<bSRQ;DwAP)| z7^U1wEpg=0Yi20tlq`OXtVkgrAlAb}*RBhscPO*kqF|$+n-x0My{@ru5riq2E?D;Y zy^UyKx0>CR8CwT9Z16sn<jpuwQsDX=ZaMI%%b%0e>^19H{UWjkQ}U2~ym4J9)NI-) z_bz?7<bA8flYN}Y>MODT0EF;I{cE-9{d7H;GO%(xWZ|)0c=KxX$sS)((srvP9i7&$ z`gXgd$)#$RcKVKss4t}~GA`wCd>?OcdxA16^W*VJ_(*?-x|jI2n<STAA9XI#bgAk| zWXH5}4m0#_GxzWJ5^7NCw(QUT!c)11cLDzZ5Ur9eo~fqZS!xLup{HFZqcjqY$t-L; zFs{6LWr{}zOi{}lT>k)*@c#f^(AvgKfOF19I2A`t>K6J=vuSyG16|s|AZcxqV-rfg zR4@kv-oC{AR{j^{_-&}ks_6*#T`hL_hF{dy&Uy5l{{TB%$R6O=$GYB*_A@csabH7+ zD#(umCN~F6?)A@)n#9_8(=;1+Gz}~e<|}kv?%{E{oB&C|?rX~(%#O^*EKW(Qu9whr z4r6kJ{{T`Wkl(#k6n?CMcPeN6&TuQDl6t(E+tQ<ZAsEuso@&fP`CRUk^BeyFPSLY& zJLfshd}H^g1x}l$rdaGSf8Hjyaj<m_k2T0>lHw^;H#r9t*ID7MfX_Itd2)2abZ%@K zGZD$<swcKCp!UsT+RN#Yp5Ru0=7~iB92(5j(@7Isxdn$kg=1VkTNuyMw)&iDBJWa9 z7{`%XE3Z~H$G?+TOru6M$@Tu2tXD>i<Q_$3E_Qs!B%al_(X@+sH4A3d7HI5CD@r5b z*)z@w>~WgDq>)tzC$Z10xMDfyz71t1*p4()g;U*el1+6Q?@8!kuBXy<c@`xs(Ocg@ zK`YJ_I+K&*B~*Q@p1J9}sO+J<jo_FwZ81eCGNI?Xo^gSkjE+Y+IIBgeD<W`kZY;-& z{$1q&Ks$EtUaj!U;YO#|2^IABQR$jCSiRJkF6yH#v3-;RNn#XvAXhD+d@n5w7kZ|- zXQV?kVnq>;%t#9r!mtDd;j(gg+CdwJ+V$_m{-vdKhfnAlS4{PK>oIB&=KIo*4<U_* z`&Y<2cVvYMppeYjJ8`sP+H&xYS<0G^m+*355_O)Dx1P&Q=-4KETO}zjp>Rg~$3Lm~ zP(t<!53xRLim<(TZo;$VFMRP_jCzX@KTsLKB-Y<fyAfcO+;iVGlvbV%<tHvrUeI+m zj?e(-a6Q+@*jIvlN|V*=O-Zx6=kM=csnhqkWd!b4JBU6j#(JjcUiA(9z~x$2!TM&j zd=xcQQB3l@i5juW#x|(-87KWos*)`gZQRk^Tr`nDmdMz|QI;yabArU5<eEs%(pzy> zUPe5e&<jklv#XFeLAdkdAb-qN(8`lbZxzh4rJBFZ>8N%^ByK7R0GxQp$?__P3LUnR zS3D9hNc&I`MMys>7##Wbpe{i<9C)B2rNXPQR^FiS*v4uopb9CZ*jRdDg5YOy$<8_b zvzl50R;Hy1LJ03thX*30s~`%tSe^$dB9&p>8cHc-1v^U4sH}3RFryuYN&f(y5zNsf zs-bZr?QO~l#zEko#~^Y!?mH7nQ^*_~Q+K2tsh71#8ysiFMO|>?A}HpPSP<nmjkqKX z@sp0<*i~5=Jag|tC<p@>Ijq^5eNyh)+BO#qp>5#X7(&B8C^+tNag(39;;?9;1Gz$| z6-uxlF()}WJpDMuC0%!NZszq2amDhgZHWS|6+(^&lZ=zxoRi6_YHSY^sunF6kQE>T zNWmk6$F)FqRb>o7Zr~pEGYG_u=`%Eb3ydG*CZUm&8-W=a86CwzVQNkRI5{2-MHB?& z(Ym6FSrkrlM{3JimU{yfk0VPF0t6qVDcoN?d*Ju?tVRGa!N|`9j8vit$gAz5^?hHE zzSzrf;c<^V_xn~g6yO$asRp6|k1DK^7|{29yRrLJ1DcRpkAvgenssVf3`|$jS5dSE z-oX6W<nRX>@&2_eHZcK%VB>Q3G$4KT8>u%Mos3ym9Jgu_Lk6Y~Q7&HLvWp4bf}?=6 zr-DZZ2e|E0D7hR1^8!W>0OF%})~d~9T-wWL>3X|As~DAbNU!pq*}*tJLxW2oP-=UM zMPj)C;2sawooYZx?IzvFJ4Vs=s*2U@<I~%*bDZzzKW}Qf&h>19$dE?&Ni=fDEUFz; zE^?+e1y~YSA%Pz*I}=I<3&hHb#9%gXed?mMb8|>WYNVq+K~yotWJ-9(2i~z{I2CqL z6+2Z^tHR}EMn>T6VT0}~CMfM>n%QQ!k|^E4oz^Yhn&-NW!*?9xk8iDE%OgnTnaiHr znB?GaDvZ$~Nv4W3D}@6!TB(tM9qXy--3_O8JA2krmXv6tW=~G^`I*#}uU61B7_4Jf zWd-q&DxROx?ex1=dEyT2xQu5TPDejqBloSVT@ry;gXGtnl6u+Qj~yEWc1|M)3*?HK zC(41&!R`KITBVnKpf*6OUhSNa2W|lNtS`$u6nIG5Titq1mf(05WV5`ulOI8!YQ)p+ z-sFam;X(duX)%s6#P?Cark2*>$wpMUF+}G%87!mPwstm>1XpbDVVqVKvk9DM1If*8 zZSFSja@a*2{c5p-W|PR$q-U&eZEnQBC%CO$-Nxw@0&(mOQFV1K%Zy2ZSaMt#X2x^w ze43_5?ZK`XwY%iDd5PRzuoUo~^svrdfFmRCz^GF4Z3>_dB=<F!Wu&Q)NrQpM9`(<q z%Vc(UBN*;x*x0<UouAWwIQmw_tQHT|jeuk=X6bf~XAoutyMB@EDyza7m~!pgpB1ae zQ90w8&Q!QHR}y+_x*$M2Wcd44Pdv_w3mlP!81GvavQ~k9Z=XH93RtauMN!V>JbkO4 zP-kp%B&@`?o=^xUXj99M^}LSo#Gy+D$Oj<ys_b<2B*ws%BPZ!f@Y@z5CkV=R?LL0A zSejPMWu7vXQM9$y7B*7cD|Y(U!VP&+ZIPTI8+q<4jlL89X?7N9<2}VwZ83dBgC+(s zoYrq7qUjqfF`CtW4)*6<hT0}r@@{uy+zQXK)ouR(B%*c!*!<r0!9A-i=;C9^&e7b~ z`%krxQi*dRS0Ae;w1nj3?Hu`W<BSt;@i#MGJCawG9IoEjs_gZPi+hk+K1}d@{b<t> zt)4|+>P5S52=7}9zstvNCBeje?MkzQex&HlW`tgc8%%Q|rPPy&nQ}K3`WmJ#q1ivq z{-Eu(spFR3_#LjQ?6_n->y^H_SZ)ksJA-h2t2~Xunm6g=9L>f{bJKL~W-^K;I}Uq; zn%ftSb$Mf)GVQ?!kzR47Y6-NLT)4>G4QJbG609d1mNIZhYSCv}Rig=XwI<@|=^{u( z-#G<KXVeFp&i?=lS&yn@I}c%=O>$nVT)M;<e8++fcGhx6Eu)AqW<>z~D)^LVYKrv| zmL!^cmsngF;6-FA%f<~%mn3?D+XRo05BX9?%(>n-4+gaQ<9d?Dk+#_o<ma#+D_OS~ z9C+7rB^TW3D{;0kz)ns{V}eC&G`m^t)uVD97ns|~HOXr_z0hlEV?R3Z8$1(H{{V^S zjgmts5(CPf!o2-(jIE>A{+~8XX<9ptHQ0#~a6^NVr?C~G)b%?-9Fa&2fm`I~^#1j_ z)34*yZPs)v+YR)sPP?b+(xb=7l=5~A{{S_vSjoXS9QY*_m8^-cr-NCfL@qAzGM~%0 zVn51!R@N&^yGyYQgvkJHzyroBQ=?hl=+}FRGb1KIJ%FyON}JQWHx8wTV;T0Yct=u6 z?9WFenwyke#=oCJxYA2kkq~0uUP_*8qO+de><nKjVR$Eln#GareNEAj0`j4})-jPT zC;3nloQ`|d($L)$rIjaCb$0jv0GQK}Qf-nSl#$?y(`&O_Tki&K$`>1ZVAi7M;jQK@ zncCke=B#=D06%6x$(y$9_g}46c{Qy*O5?%XnwN$a_s?x^pvYxc83)d5KEh_Xl={t< zBRfc~JOIcHL}8cou&w5pe=yPP0c2l)e`@B6a&&q*8j5a=yM<<rnQ~c1Q~||m?k^rA z71CtLza{|S{p)FCcDEOA9Lg2JA(eRJ=~eX&j4@ichF|G7A%Nr>iNVS`kz8|1N)lX~ zZ9h?6PfU#pEUrd&jy93w=xY+z^7}=(Wk()^awCv`YUjFRsYaD`DqODRZ|61DYS(sJ z&6}iciGU2Ga8|CrxeZCwk<Td0r^?{taAw~3Rnoejr*1TjTFhP8T&ap!2*`Ez`{%J7 zf&s7355#}KIsP4MY+w&{qTS`}?uh3eV}EB~KKvgzA8zUQznNI@Suyg+2R!@I^?sG7 z^{-9e^zOC>$-u)ea7XEpf8qz9t}u8tw@=ke6un#=-CsbYy$f^lA6C<z%4XvNwZ!JV z+v7jNo9>0`E4?=UAD%P0Bwx`qXO(q6#D5%);=D%g(V&(<aG`m@KVR=(U5_H1Z66~z zI67uEK{OLiE*))}3@4_}9FmG#AP_Lv$Q+!2aCpyQjS5kAoiU1hcwvg_wENs+o-2z4 zsRVZwy0N?Y1816~m6}D-)eFF~0!Xanze4H(`AHq?oD+J00h3uU%nk!7;<D0fsOqKx z;mVKwRnK0?8OS3U?^|1I?^e%gILBfuQF4K|WSnN9B3lzp)l*5HS&gz>IgG4q%*tVY z3dp2+1bO=X#y#&*Y8SG!vs^PdJo8k_m;`f=tzt6d;8uw%vpr2MDePuj>i2TU5XxCW z$;jYWWzxE3#<sH<<&Fz$bu(PuTsNl_G3S<M{=g1NAOJ}MwEZojLwBfIN37ld0ETHU z=hdB1GoS$Y$n%fpr?2|r9a15o=&IJeJ83B~pd>ZZE<(*OyAVzXWd{Q!voxtIq9qw= z)c7|xy>HU`JL(Gdm-^$0;}(*a7RX`+y-myW9I)Ar_~*_Ibn<+0(RF=3JvFqbtZbL$ z5Kj`!nD@&Aitu44HI=7oHyTc%YkO%cL3eE`%?zvE9Zpq;*nwKzaAM=B+h@Gk{5H`q zbXU67lEbGoGQvTr-EI?+{{X>^5Yl8R9F4ipDtWFS;s&wOmwKhFJtJj3u9;~Nh3+l~ z^=_Dd?_F|0B;??Gf#SHWW2p5$qMc>b@8`dD8&uCBZKJ>g!1`4dvXJUAkUUg0q|^2g z)f-Lnc2=63*lD*Dvk<W%U{Cwlz!l}vIg&<m-HuIlol|;j^vEnSJz0ONC>|I5qPZ*_ zX9pd#Qp*JtRkA{gA@t=FGp<UikWl1}rz4+!Gv1&TQAS7vd-tgf0x;SMIQfopC<vm8 zC<2Nopb99bhF?ZhaH{)GcDTqtaX?zvnbXqO(S&W4rS*b;P~-u$xBH_R1KgTbvYG6p zy9t|k<XD;VIA#n~W4|F=_^N227<Q=#6suEGim|dP$0$N<3=ZF$an4WE>DsBM6ruu( zC<8S-0@*Y2<(0ls`g{*#P&?Ao0a6K~q<81FNm3Vf4$^Q90JKp>3ei40^HQ66-=4=9 z_wh?cXiWt(J3coOa4<4^3LTW~+PiQtdCpJ0A!-s$NTd3Gi94Uw-NNnpaqpb>`eL9Q ziU6XW;&{TAQlW<B$nHJqC<poAfAdD;_9CHxR={Ax+)&KYHs&RX?n%u{;jx7afsQIl ziA5At0H#`6k=~^O#Kw8#dBt0m50zh+ISR)-dm5y7r&^F*BMD`92(gAF>`u${A4+(c zt!@LyvDpqrR63v7f(ZMHq;{rPWF)H<BoUBt!QhG&1Vt_LF;VUYYCBOy2nxCgJwh5n zV~030Z6pJXf&Hlfr;lpx+`F<cImQpYNG2F0m`KQlgloOXAEaaqpWKh;fxxRIgYvg6 z9ED%gVyt6u%5ZtZ41zwQs%8X|1_!lR84`;~?&Jv5Tw0C68I(llkCc&?1Ds@J{ruJ` zYJzBrUrIpxRq_5;83wChmYIwqERBJS0fF4(JpRI#S3@HdQix9qPDuUgvT1iz6Gp~U zSkjuc-!+cUh6jU>2CT}YE$T%b%!vf1cta49GHfh79Ax9|n#M&8>KKru@<7P~oy}}C z+Yim$Wm9UC!sj>@oSho2%&iwjGtctnmpl?MNUu}r{Te$9aWtR|4gvF6y*Z{N8hQz% zjbn+-o}fXz<N<<wkC%btC*HGeZ%xdfFA=Y0_7&#jo~KCd#}6jQ^Fn9ICy(Nr*7mJz zQs5K)>mein40kn&)|DGLSQRH7yM5|o_Rq|DHH?xol^LP#Ha0;0>a5Z{o2y%smHF@% zvc%}&*^W*)u6EvVt&-ivWLw-eSOfc-g)aoSZf&ES-~<@YlUCen4(ef1fNN4OK2|yT zz~kvj72#&XVEuDhx3Ri~??!mJWPg>3_a?7bjk#y}yOUa}B{E0?kESbNkc5d?jGSbO z%8red8KPa=O8$^Mc{R1tu8eY`u)^|9Xya!xXB$@;A4<-%Y_WG>LXS0QZ0y4&HB9YO zQbmMqKR?uVt$Ru9N`71}eqt+Fmw{m14&r#nwPjn~5f`V9N$*<9TA7wOXjYAqDuTNX zN1AU}^6j}7ocXFPpXR^^1G%eip?M?q0_|1j?^LH&Xyv^~wn^kvbywW(IU}(8Rvw#R zi9EL#4tN*?6rY_j$FPhu9RAgu@|3f~Mi3qsJ?lNB@7SAgT33@AYou8HFa!5HhjCha zNPTODZd7l`08xJ<!lZ-AI0MaFrpGg6Y}<jrt~uPKwmm#+w+7SX*u#6e{4qw6K*lQQ zZe4DVrWkKMhx%4tpQ4#iJCKUN@M+_-hDh@fG07R_iQ^Tb$2v`%&nzva^f$VC&Mme` z<U1Unp!TaGhA85Zcb5e6N$ptreA2~{EALUx?<7;-C|gMyG%WIRf9qXTs?p|K*G=+j zCq_)m>Jmd7u=c39duNSX)D0th_Z3tZo}3ay`EukQdsYnXiT?nmqvqg~;=IYV!aeM1 zx4(m<(hM<Km2x*0`D7Ki5z3l*WMz>F{Umc-{e%e&P?-tz$-<G%bizirWkC2+nF`g{ z2>NN9S*Po`@42Yc=0kLh&zA@EuW)P7ppj#YNw;Yul1DYii(4yefpEkzJ{Gr!Rf<be zB0HGy7d+MQuHD-(>ZRUs(wRo3tW6Eu?Udkr%Z|j>x%H8$zGibAnBj5Wwc5_1`Jw{1 zrpxm8=l86A6&<E`m=^<$r?~ppB&6jwtn{&BjB#penl^%8&By5f0JK#($9mS>4?=%U zm4lIz7}nn2SS?^=Pn2#yH=NetwA*b`#7!a}UvC)mimz$Qhq<JBm4)uMX17SyCexv0 zi~<}D%bv&SO{r_LtiGnwnE(|$cC0BwqcBinD08^^t0GBtYvozcnA;dQ$*grslx)*X z6#J52Q>oBzMdqC(u3y#z13rG0z0q#tW0~J-?Z7q5SwkhI>ZB}WMIek;eeKj{@mT>x z?m+jhxVIFWIAsMnILC*-(CT#?F?_L04%I{-sClhgTnkO!s%{y;rqb`Emfhe&c|R#W zYa&Sh05bvgHmr%<ANkgjikGS-G0o!iPlFHqNg-89Bix&_x~zn?s17hvOq?&ZVfvA@ zO&pB7x!66(@mNq?NX#Tx*mI4f@GFtFsk6|nNwjQ##3L8Gk$0ithHGc1U8<QLC)`d> z4{==GtiRP^DIYU!VU9Cd6FIV!zC!IimyG@EEmi6_lw&2N-TwfIm(<X>5vvR-t*r3N zs93y+8JaW7`TN&1`J}mpPT&}Pz!S)=_JghZB=2A%an66zrVUBdpC(x~LMl*Oqo37m zPVq3#4<uu;^H=HDvP3;seagdz9mYIXZn}u_G?v5_4Ux|bKDEyY+9xPbs_x};^{#x8 zaeE#3=ibM;*}!dYB3Uimti)l$@yPR8Gh3#yXzLjXaDZ)9JfHsnU2=BU@1@<X$}k~# z!+HDHOBOF97jPU)5#52nD^^Z2RC7u$4K()f{{UujpBVlY#qnFA#0rXfg2h?vravih z+`!*M<JrE1*XJKwbemYU(Ukf(5l9pA?u;Dw&x{OU0tpA7y?(XXLbuA&MB{UES3XCY z`Csva@M$E^S!uSyIIdVGr3wxsnEsi5&*%Ox{%ZC5Ed92i`0#i?XmsjVk}H2F=i!Zm z?7=OZVw8fUf-pw}8r^jUnD!S%fGdOCR+OBap7r#p+Oy`~#g73<BA0p*AtPe8SYsW> zjOUNFTV*gu*vAUqeT`#LQbEwgE5<N;d)3iOSoi1Jwc2I!I;;|bk@{M*cZ8FHkNMVZ z7fjgOUl<15oO@ILbxRC^fm*k?!RNR4s}@vt+yMJhiL*BHWZD@{GvL-ej-a+OJieHc zPBvuxqn>gr7UkEFwS?|L_N5VY?AhwN#m=ic%QNzS%Df8HIW3wuGagCBJ^%xnl$2E= zDLJ<(a?EltY7Ce(&H+4CFIOmBlH}IoN_ef+uC4%RV&`f905R|@p5L;XYp0eqk2po= zA76Ts3BJ9PP`i?OPF>D^-%6t=7|F+cQAI%{$mT~Z*va+)58zbCBf}y|8?s1*GP0Zy zM`8^vJg~_F1(6yyDy##H91sXUVhAJXXbDprM=MCqW;iK;2q!$`)pS&<77R!EMoj?~ zVn^yB*sjtHu>fQR1Hby!U}WR{XrKy^K+Z;Z_n}y;0yrS#Rz=>WJ?^1%dm%-*xOb9I zMkS3h`Q5b?1yRZ2Km)J>fX1nC6^K^cJ@JgxQ9u+?QmS2os4T?b0o>3Mj8`}~$ibql zpZ&A{07yggAL)$zzfO7nYPytEDe4xcjDV?Hijm%=4z+E*49FRg6r2{25~>f904I;7 zRK4nIKv7QHHYVNM`Q4u1Oj7rt8Mb<9v^M^C5_tMzlA1``DH$9UbtOnX4MuZ7B^2&d zs(`p7IXUlB=_lokp0fI(_WC3ok&o04F^?Vd$nVYouBDi}Wr-);)KNeb!?i-GV}J=b z&lMxJ08_lJA<JN#1<pJHf&P?6f<i%cj|xs(Jm7ZVcQlkmSoBfq5}`@p9Evkm0-p6} z<)k*od=E%I%<dV^GuyxKO%<fq5l<vhG?Bo9Nm)ihDPW+VVY!rj$f`jg5DCHVJ?bbH zTh48yE}${m%ZB=?8UFQH6v8IiziHW=ZpRt+sRJ|wv}}X}jzDe*JQ}eOva!ekMtL~T zK0WCI)q!zRK+F&ku(22!+%e8M{`Ctk)p4~>;f#^U<Q$LV`_Vyi5T_eS&taVX=s>wO zAm*dTf7+d*c$}-FBMt~*f(-*KL@gUWR|-HNu+KT{JD$|aDx|ActcV6QhrL+og;vPt zz&zDQy%-4DR#06xsP4JOe|oI;tDurNr7bL8vTb3rab8Fu9FJpz^~F-%s-Pltj%eCQ z{aC{S(dQe6c^UDVTZcxO%t}%+NmV@U9@Py<d(gSEeLC~}DC&QfkZJz6<%PjqA(L+H zfcYSDK<$y9<2vVhhLDkroOi`4g6sh9XxK=})2&6_)M)HdGQcx|oukRen)E)G>Gf%b z-<bj7h{i{~bNw@;nO#X-=DiMS6GpLES01#3pKvROEbnA=<ByQkuTfN6#4#Fo`kKL4 zBZ20zOx*4#G|<bu;2!<!nyBoK$j(VKfu7zgE)$+l6@?lHZaa9awC4bG;-ZZ#$wJxS zoMxU@C4k+)q}&jVyyB;MKp2kx^;V0;+p^RIoP*D9X_|8hAx}PQ7&*xyxjf>j6E;fr z&w9n|bVSdM@>sdxRgt+XgMsHYj;C-{1TpW5y-8-mfuDNKnyDKLG65yL@Hnjf{HhGr zvfdYsH$NovTL{4`pEYWJN;xL%$VFAiCxSVsGBA+=I6`|=H~T(#C%?T^z<?N%PqkH| z@prki7eJD$k%8P+eYD0fo4#iAp8~YdeCxQ3H*f`K!!wss!x{1KP=$CpDDD72tHI7m zHJsC!U;v*}An-iair!N4g~7&ot<=~{T&V-wHPIc`#}xKy7*N|uc<jf5Ip^A_y19~h z9aYZN;d5IiyW4CqM&|GI6@q3-m6ZYA<dIzSimFWN$0pshS!Zt*)JAD@xi|;jskXUv zoq!nUgIRW@t-FE&2Z2~ls;z}PTRgF>q?$>tPK<8clQt0R6GG?JCAN$K?ki&YmA$+Q z(IDP<C)%{j2rsxcMmf!7HIiGw9_J3MdC91Yl`^u9Hqp7YxRPXP3-EXVR@rV4s}13} z#?|Jz*mtlVfoDbJ{VQ{20gWC~fn+O;f6lrf--C}VW67-5VMy*aMw@an2=Q2!+FXva z7bKE9{VQj6Ah@_6CSSHkYR(Ym%PhN89!_~RgT|#M&dBDLI3@0DBDk6p9lIeZ9uDDK zS#N(b%KM2uuzXgwS%xzb^W}|QVVURJ7XuhSdZM)Sr)IR8)YVaK#o7tR)NFy^*4o}- zh@^xLNCi)P*08}ENwlye3bG5~4*1jN&#>)Kt}fX!^+hO`Ycazt-l9k8$QTAtd;MvW zRz)bSg)7cG)ejRF61Of-D_P8uAlR$(Fy*sUan$V_#*||hxkNGJDy&L_md|?XI(GIi zL`NfGR1l#3Yo38WFEZo;yaH=Fd)Tcc+=L(pjMT%>?5=O_tp<JFlus;?82|^iYQv{4 zn*z75wq)fPb~U89)ZX$;u*-MK`*^5*QsU%<aq}qPHC4(M=@A%ZX(smaZS?E(HyLSu zNe<Q^*3#M(npsdt2~&k9&2e!OCs3zjw{S-T?OSLy7J^SqaQFvt<26{$wwC6ZF+O0< zf;~g>G99cL5wlGFP?Ji_>46;MX(PC<dh+#^$Sc2j&mL<I;^{YTkL34ShdC{gXkd)# z$!>34tR*Fli2(DSaaJUuq#(zXY_ftdYf3KJ?UO7*f(XTKEj3umY;@fr&eMv|o=%6V zD~l+(m_+2pr(rlF&)ihpUdeQSsK;>_?c9IXuO=Ip-k_%57d0(v9Pd?Tk%tGsHJgik z8b>5;THLjLC%2OvJYaL?vNWAZ3{M*=G417zYi}&BnM<b%IB#miO-&ZrW7)Kq<o&BP z>7!^RIMR3f9XOakFt6$<9f|Yws$x>$f(a~6ax#Bv#j(1E>PKChasU;ZAd&=*L}BVH zV3I$IP;%1i$cqe27&$MzWOL@Xoj-UilWs-=o*4J{A9~f@Nfpd6$fP9k5*q`!tPo{( zc7$caWP|3SyKOXQ{{Wga_B)=N)zaHj5y%GJ#2j|5-&yE3daa$+joNzpW}yL!R3nTL z-{?nt_&nDO1dSJ=FvBZ>xD(2^wRcwfu+(&qLP7mR7zCWw@q0D-9Qju2ULMT+%J}E- z#^<6spHb587jcEprzjjr8Rb}g#y_~P3b};AD=8SS(r?7S!}aRklYLGobE#=S@7g(d z4<vu-KR4TTetq?3k@;JPI3pdfE9m+x{n&5G@||x^`h1n+!OT-es>JSXoDw+BPk~EP z%e0O`$OnU(^v^U9fWWZaJdEQtlW(XzKr4L8J9~=7qJY}0$uK)wodz;@U{;(G4nf6a zM|&iE960th6iu0~$2rY9#t83`SYuEa{{Ut)^r`;<5jemYp$0=lopo4KfBg1GBO)LI zl7cWgq`P}`H%g4oF*=kG)Hw!>5C+oS-6^n*mXrobK>=w&(P!V^^Lwu6|6S*gvvZyE z{@m~Te%;Z;d_`6#p;KE`Vj@Sw+M@K)a_8pQd}W}eYKRYSUM3`Gd*yGQCvLsJFK&b< z^Z)cI9AFQKgdjYHAvyk$)BlYqqoXe!R5;|}MpT+SRX#PvT_0wXe2=Pe)`)m|bAd1* zMgQr3{a$508Bbv}Phl25)$hz$l2O2<ffh-w^u&Kk6JTj=WM@{!g2VG@lVCC+>acgN ze=pt!$ZX1MnzP?VUtCy*B&m3QNq}-(E+#K5jae>+ny7I~?i^1FLy}(6(wCz7)Eu?@ z2d<gtrtzJs!#KP`QylNJXFL<y(v5?Y2h$Jy^~C#>5;K)2{@RF6dm6--u0|7Tq%4O} z2IQ^oY=1Y$8b8-a5@b-~mmYMMkE-M&oltQwA;8zv3}ZlhVjhl(mnwb3oQv|eZjr=K z$|%)Wewy2gj(1f{=QJS2D&Bpw`Pgx!OrOsZOQ>3{n{#Z^POw5U!AWBTW0WB`;jfkA z{om?Z7mwwjg`P?^H_vyR^W)jZx&mIQ#~`wzLv26=*<h)EF*0ifb11(T`__eTh#Ac6 z4>f6S9$PMsNq}6G5=ijn?+ZLm5<ks%39z?xpJe*88WVH5uhdY%Mq^5G5xs?aE%J6N z1CHWRVe-gYI_-Gx9FvObTpsmmZg)=&ja9NR%*21p2Bws+KicN5m(ak2bfR=aH$Ck0 z-(?|1hbnRHG9<D7VplW-#&|~B;I(!Kmxj0#(>9VP>+|4yzH^Bd(#s@lJ8rO=M0z$_ zyf&f#?PR@sg{rAQ6>5+zk~1C3nS)eCl}f?O!EU}kAbFnUITpSj%4<JFeh~EVi7c?! z?^&ix9K`*#Q~_eiDuh&|@)iUk1werH{~dMrT4fbd+B2!eCcKXHVz(p{vw#-3W<MhB zA1Zaq-zM`iPA7-f8gyH==?VJ62Z7B+?p1E4vvhQ*;5<lBu)U5Z`BO8hsdoSMK_(nC z)6@3^br_O92~tROyENaixF#@c`REo)RZ^_M$MBYc`S%kg&;2Mn)YgmNVige_{Br^u zI+HQ}MY;N3MZQqjl5I*-IHzmkj95McACas7n<xAfB!h6MWz{k8$))F0Q0wzXXNG>7 zsi*$L(#-RrBP{j2T$y~=!{F)W=exOO<D$v{bJ4L}vu)P(3Z%o}VT4Ia+D#I7EC0uj z#@DWPOt~624PE^;!ySuwOCNjU-F;S@K6X397ZyJ4_GoTPvrMpad#{up7+m!Of~&Kg zh{GnlaxGFFScO8jCtT3KIhOOu!>YVp+eLHD$QE~UMLnzEd|<h|sg!!qjkH}xahpiS zBoOqtPQLvCA9D+IhEvHiPQ{wC5g|UyF)oOhDR(6t->V|5CVqTj9?B^v)q0&EeJ!LL zyw}Jvr>kzBu$?PY0}PSdHoAvFH8Nba-9?(MY^D?5NLBcQ?GVBuu-19X(vlG(R7-H< z*M><-XX{ftDc?gP)%^hE3fRY$my8d6pLjpWaotwvC!UP_UR}Lnw|Z9;;MUxx8b%E2 ze=f`q0&crX?`<paWEz9t-N=Q?o7iWOF6Sr9F}@G+NO4ZuCF!`XzH9`W2g_U7Fxj&3 z3uXE0=cE?XAEjVx-|OZ9)bFN{Ugr02k@%|HZ4!~GX&=Z{*c_bdXp5lt=B|ps#%%nr z@fu$`Tj&dY593&6l8@!LCc;AA-NUcZE%A3YQaC@cXkHiso<MOg$=$_RMLj62ci9Y~ zx}J}jsjoeTJSD{MofjD`bNx((@b^Mi<i6pDO6*bFGZ=Ez)26$>@0(lMh%IekgJWr~ z_pfTwNR7@&o_JUsA$KycjaHVk&u%qwlDX<9mLzY7C_ZY7oe(md8$r(CIR3mJ7ZvJJ z;{^S5)#B6jRto^_%**BkpL+8Rd?E0!=IY>*TBZ<-C2~s3Ji~K=F8*H7KhpdeHy@%I zRkK_?qXhmzdTXJs<LhVqIaf<@zP#ffV3huo2vMI&?)*nw$(t13FOz;A{D!Z^6E-Vm zk@3T`e*=#gc(K(}<MOr4o3zY3r#9#Adf6D3G3H-YGv@B2M0U&%FDu3DjXjK$>9JjH z)Jy%wTRAKB>96~$+6k7cVQT|x_xkz7@@TzNNLxp5xapm9=RL1jMmM7xR)^^m8Fc=Z z;xO`?D0T*l4ZUkcsJTZY={D2qk@q$(ggz$rs2=3+jJ|hOVLbP3GVHlRH~l;Qkyr|b zF!sjnE_}(60eNP|;)5EWKpm`tXgh(z_j@%@mUe)<K)D2^nTisOLyp^^)cI;^;Wm_k z=Ga$SV$@Dnd{~XD<LXYLw`FdKu2F+;mqjveU-7ae!on0g22R#6eU=ayc+$F-m0{_$ zs2QMJ<b%8I!ZqlcS4>B%!F`0~H7qsFa01z+S#-p8*++O|vcH{$mrr7)kjDmbfStbB zQ_Tn6J_7O!_8k!l8)9O=UoEsKPxuyU5IH|ce&APqZKn!kiqiYq$V=bWK#hd`5*1(V zq?+uTo8lg*Sx70*0lN8G?mFgrWp#jI_#n2OxFiYl?h+vtwJ`@@TG{4im_=1^@NGXr zFT*_aW3X(>U>usXZfa_Xaxme5qP(}sjB6>W#&`eEv=slJ0LnSW-^=%l6k^oQ$r(@k zhVnG(OakR?s*U$f3^>7`uHWXc_EJzTqK*#Ef}6O%=K9&c>3ZYq^fK_d%1uOqG4{7p zMeX}4@ma3`4E;cD2s~^e%Fez@QKpiNX8;{(Y{8a>_fbu9n472}hcY}X#VKnVY;A58 z!|vuq2}!1)oFmZDyhme1O<(J;jUB+CVAvGoJ&qOPFB-U3d0z|PFaiwycCN~Gf9(=G zGRS(zy-=xQUh3g*uo3OF3CSq#v)_gyOV0l#PWWG#MB|Y@pJw~&4;o)K^1Ogh)*ijj zBR=BEqlq*P4O&|CF1SiQc}5-FZj5iKKyrLqn`UHSwU*wQ;WRk{hF^^YOc4yrX5Mj= zSA((3Dw`D06hx$nu`;bUJGW@~nRcdHlN0e+C<)LZDh$(5S)Naid5bF(Th(5s<e=ZV zllhCzKpE4&I%pEz4EP?G1KqiqP-_SlfFJ|>MU?g49bm^gUU;B)f4nTrfbrb_;e$Ne zRKFHp=C$y%h+5I4Nmev6Fc6kL*7TRD<g;#bj+VyY7e-5d>=?j!PwRgNeY>??nOW;{ zeQ9?!sc_&<{4O0xG&}c#KgYF%o(R&+yq&rlRl!?mf0Q^7$}PCkLh7hNV^rZO`_@_q ze>jFRHJLa-p#%F@@#2|hG@MFuwi!v_9IN%}l6B&~1yNj!>{GsR5`rF!2jIo^kutmL zQvriR2IXAN^e_|3(SG>(0EUDxfKS`S%w(?+2O-92!lMt(l1T}jV?`-XbqqpF1I}u% zqlW$Cpx|w0=WhVasgX`)f&BmmnyZ*u0(;a#7_)+68HZcHbL9r6j)=DqC6I_e(zWn= zzhH5(GTT4uw{toreZAiT;2cCF+2#1KlARU(j>-N}H-Nu$9W*Tr`GRg62tYrEfTlS- z@!&PCMDps~h%vgsg~P$`Bh?PaqolfX9RrXwI+6^=LvQ-b6Q9mRc{3!RoGh0NLE$;# z91%t)JxH-Fp|9<kY`tF0_}T&*Z-OD*B>ySH1*1~bu$EV)GvWjVR4%Fdi;BgitOiSH z$XinS_00xk&lUNcQ9@LC#5A}xgvDvVOZFfa*)b~BP0=J1iR1uMd0`IupAm7f!pah4 ze8m!;xE%<=+zM0eyj;WiL+o!8UA7QTBTc0>yY1g|?F7CQq5Ft=$urwRyYQbQ{LP_n zi(~K=wZ;cY{Lx<#GHm;}Aa`+&iAa84c$aZ|?BP|IPmRXMt_G2nst*@m`d7o0UR{(6 zSo}l_EI9fnm_AkWYiZ+)CiUL-rO-afrDUn1%l$xK4qS4V<nl1v>QNoS<a-DP3{pRA z6*-<Czeq^!nT0K0W)#qTsPggtarN^-!P}LLbF?AqeD87;(O$zhq4$AUQ(!2=8R=2v zvvF3<8HH{vWW!Cw;BS*{GHe!~2;$aTriG0jxL|~ar%=XHt^=IQq4K|~Gwrroe)c}* zYAkek3_SBtFLjYWe7}+BIe-c<e0f&^PMw3s^;e6cL6iIpEYjm8oY`>$$NEYYJ@piT zC!YCltB|X|jUW$ABG0YF1JR%?e{F5~YiZh^dZq%O?mo|b$J8(*+#utdVK~ND8G_*| zRNM`aywaRtCK?MNfTaXLNBVCqdTQ?z8LrOPi)NrBps=RkZyF}6i?@S*QyatTCeF+b zkC0w8+GHskxtHDn)Y*{qqrC9RL`z~_-M$AgQ>->4mKSqw(h{%RHH3QQqWHx{(@gxK z#-)SlO!pni^P@#0C`)YEf#KD{z3Z26{6t%S8nQW9xQsZ#@;99s6utC$t2Awv(;sX2 z8$AQyo!IINSMBsl=FU%iSn7Au>T$DP?WGMHsMK=y5Hw!=wtnNx=}2(@$ZG!|KuYnN z-b990qlmx5OlB%_K4u1>83476ICG{cJP2^%c#B<ZXZWk(`$$S;Uyg%zvbynvgyBVr zp8(gT&r;;va9M-hdmR&v$^=fwTs*AeCF0t9S4Eot?y&3qJN8f4w<c@?@$F8A;l({E z-O{aK75?!2$5_V+SK9>DacPnmn~5=+XdW5j96Oy5AHU_sZK!KqoY2l||Iv_$+2t<r zm>dA<`Br`5#^K9aF}MY|DV>8C4oTT-s#<NWyeJY{*i2aIew0hn<Kw;W)I}eB?4^aL z;XjyE<9&ZWW&<$~w$RsJ?Dj0z{PAe7fx`l3!jz^^>B|CFfwUt)wDFM8#(E>|l}S&| zV;j2ZY;rFkVpQimH^M^olY;Oh>2d(S;e+2G_IwAx&Yc&;D?C+lMB2_-Lc`G2Lq>-m zO0*^Q2nmqCIW_Cxr-{$W%Y2B~YGjBV9VTAOdfK$!K=`uZ^4`~XgrT)z9F^3)p^Vl& zp;XoIM#}Vt720%W?-Zaz`0LY@t$Ihzb&*(+=A`iPw$A00G|UK^FQ;dMsV1eh*~3CU z6&KT#NN)fp&F_S=*H_n`4f<mzy=2)OQi1rd&lJ;Zvlk~?v+TUSC5tl1F9fyBAkAO8 zu$hb5zAYKesavRmSH8v&D2Y_Ie<5rwu3qWRKVg}I99ipq{;PNFz}imS;w5TVU?vy* zXgSYqFaICF;~fnoPa@D{o<GpM(mS3C{X^wWvw>PIz!~1l%0OKBS4*2Vo3J(%SJQ5h zA$Rfz2bYfa*Me>5ar=s|zI<a#JF3>~CeSS`p*~Y2jO1b!;UF1yM_%-7g{=Bh&K`O> zlD?gQP}7m`h~l`+k|@SNor%{I(r-0?rD$)GP0SFWS%n}m&HKMCI+s=6j=x5hAwp?> zYX1PLB@-)ij7-tkirO}B`8{*6OoYX%L4PlF8k&B#P&NM?IUIGcHHYHII{8zaq&J*c z2d}Wt0aaHDf&{cWD*R1@5ifTdDqoqp6+Ex5r$x^YZ<4&Kc~QB7L+Gqpuj1W!wLd4J z-zRkH9#i>MRI(hDJR_vn8ZyQUFCN8bb?j2q;=nBQ=$g3iwf-5>V<2$R$$@>iSH7CU z544-H6FYC)Cea$3UEV8j=KFJX*62pRinsZ9o8-EV<$|R+NZpn1K2?!4K}TKdE7~<+ z<Aigq+7s$C4w_=hyRBgE<KkC05~mq0-af~R&dgtCF0$k@XWg)uR$bP1`cX8-_859& zi_!SO$&|NWg_Ml~9#6{rr%AY_7hBCE)V*3le~231rs`?{j5Vxmo{hynY^&=|efsKy zuQVQHLuOxbm6FX;gH}KVjfV>)=DOqjI`>BM$tYjqOeFwg6SH`76~LAB32qfKjh?}l z#|aDp6q}o8!bA0AVph(5uSbq9&^$zCuQkWlt2%{dR5ZwSonu!04MZa&p3J2g5&g#( z`tM2_wzA6a?<H8KWQnnXR~i~$8joPn6rm?82<1v#={5kOpEEPP{My(7+RV%CE<+$s z@DJc%HV1@P_A?XHJDyCw+EsQU78|JGQ#_P18i&876&{q!R;v1&)*^YIIwaBF{4IrK z{WSv#E0%Rw=qso*8mep)-9=<%{jT45gsUW1yKP9fOgd18ISF#HS7pgFke2QcKq}2P z)AYd!1WZL5LX{;wc>~-h@I0l>{f$H%qla?p6fjC{ikE5EpCywX<Tu3oB16*a;*`q2 znd751gX2xA`(tuCM*8vVt$Z-!6q=z$L3rXu<6Vf=qucB9Ri1k!H2r%Y#3yNkk9?LG z<ef>Dqk9+Dj<e>;9SX(Y%%vDPY8u>sn2G@eL;&;!Jb@`@o#=ajF1P!a(tDNOLc%MJ zqM=6@6%S1AlalS=(-R~PHr&qFs|b$g=7@CH#jY5Gqyb2Og=p@pN?xR<S~`%`A7WyL zikiL!`uJIf9Ii@kvos)ZFt)$*#u79{i(rkYYFuSyH|wj*s%3Bl&lc4XuFOy*lkO5o zPE2#&cE33#nsPG0kbZC#l0?%#7uzqE3qgmKmpsr{rUgUf(8t8)BJKOF5hm3L*kQG~ zony58!<_TqbG5?enVaO%GW#$pSyYR@QBUQ3g$4*>{?M|;Y^^JR&x(&7F{+6-mAY!a zCn171zs3&5(VM2MutwrebyY3{Z19I$mHO|VTHbs6$;Xb_ZBKEZ`;ONv8cr5#AdHc* zsmC=1nof{mn!GE7cFBVY&hng-Wqw0cy)U{v9&5}n8vmU~&;0|`1W1|E1%O7lI)!Qu zZ4vpM(Ua3ZTG|i`oG_Yqs_gN0uK|x@keCb^AQDuo6DB&pKM*Dd{PS4-#&Zm2;AQ{F zvNKZrkGTGqnc@%ymx91C2^>7!^Z4MNjM1a;xDj$gUrCe!B+fW>?Wb1~g<eBi0h96C zpC_jG_fc5OiSQ4&Qw@pv1-Yo5?dm~~sXL6N?6aE`0QVqM5Jb2oN$i>o3Kp00zb#HM zKSuJJC~ht!8mHN3o%Z^Bj&SwkzGaaVhDU~dP$CB4Y0O@e>wDy>bRzw=>)(09suq>A z$oV!|{kx9$Um7~z>d|0&>6ixYI79apMfAB+IXh1}ls=df@bteK`AOuowc9B59<~N6 zDRFG3Q=}nJwdHI{ke-PcN!RIBAR3hUjpz@qiR{c6vN|fT4J5gwu)CZ3Q(0g06nk`J z41#tP`W&~)9dT^g566GlVV|v0VjDHzLJF_uaj>^YEf*d>!mza^EEfLYdKmrd$51fI zkqv*4%hT=%OXul3ZW1Hzng|Hz$J)DCb^RsZov4VU9ZUHf;ZN(>BiPtHc&07&R_O5^ zBXORWkj!-z^=1_v0qxRvTxn53{iBxB9ydsHYy+0T)={ad?d2b5iP6JliG}!bCfgQc zE`p~x7<I*uegtWS*^GPD`#1=4&F5Dkd-`tV6C&@UfklQl1Ti+5F_&60Q`a@)^rUKe z478!>aZ!T|w7MG4A~RXS1I9S`Z&R&v_Ttd}jItgGjR#trz9LOh41Q;(2j;_v!r@2E z*R>~A;k*|U6d3E7p%2S1$?ULw!fmNaJboN(h(B&q(si=1FnSjQk;SC&BU#q1+>V(L zV#1Eck|x5U%LbL$zm&LwdbF3sHS%Ym1j~befRUHx`#W<OQsk0-L)-{0yMA@0yk#*9 zL1`}!R_W)^sXOC$)@<#rsQU56e}FY9$E&}CJz;-a@(-hA%12x(tqmQ67zPk7=rFX( z53M<fjRCD5hUQZCRZ6NCAG){>x>rq2vWjVQw4y@elx4)wRwMg^mPX;C>q=h_&mWTs ze)?u1+&y5<5qM+{v9v^$<q~{nzr_aGfW6w8dt5w`>K8nzOX)qU!BNhEi}K?dcU=P$ zMQ1ub)%AMpJ@W-8yts;~*Nk8+x)#Wb6U_8xkjq|2O35Ke>CbKUyZFz2&^XXk41Ce< zXv#0VXe*`hqGr6^Pdfl_4S}v^%=q=vA%qUim*woEC^(KskM^QoH+-%N58lq}F<-t& zeasNv%{0_)?8%@tZXoO>yS#lH@#at-YUA3%0~<id9aSCe{uI>=@U#I73VQnyEo#@V zO?Ytz3K$qD49g$1<Cg`8Nl>YSN94;l%|KEE?h>@+(O=mpipb00H?)zKzR3>ocBKi^ z5V~EwmnJeYrLe%Dn-2{H><y(^B^CV7v^?IkU<j|Ss+_r>inOzyB@vfCIzL%?jbU#T zWN~Td6eU)XxhkRi0!q6d=IodJ=(lM<QiY8?E_JyR2)mb+ZXkjia7*XaGiIt`O1I`E zh&KB3>nEtyiDt4CY6DIJLW`*42hikDZ}20#A7UA4)H?f9J$^sRR_H_6@X$mSOsR6Z zzPi*<0&S)}wwrTPpf%g38>XRJw?h4gtz4wTf<}=D+##yt#8};4P-BFl8631HHZlS3 z<OP=D<SHsH8IhdKtDjMoiTR5Cq{P)<H8@gD`E{R${$^?A`SG$Pe?IV>_A^p~V{ZVz z)9MQ(=mDG=s*-d*=;<68_Tch%{<(r@H|mwKKtLafT-m4Drk%~QJc;hln+O+v{;%`( z1~S85?1?`6$-OGxv5GB?pXtYxN;j%=9{x?AK=7MLjit1=CRTZXzWGrm_HR0O5Z4P( zaaDZ74pp7PR&~P;D&_J?lX8_}`Ex~&dpLnl-(M;6+oiy5ikKheq@`tj7J|FIpPP&l z+c<n+9<GtT*={)7mK+h~72*gvK#y^ca@xTI9(YQuBr567>K=wHEbue<<?gw_A0|t$ zYDOgOwPpzRFy~B9uJ%MSzc9b>#U0~opB>SDWDU?~SQ2nsvx8zzZ4ue=f1d=Vqy4xS z1lZs0gmRj2F2ihj;k(f+?Q7dCRn`&ZT11soH<>1MGM9|Xv>)2pt|aVUF@$TWwJGI( z`H@x^^zP1;8~cgCZZW8}NWadxOY-f&h<IMdaHPri9;vN2pe5S_e?#%<=iw!-<#>7~ zLQ0muhs7@HlEe3PBjh>=3N$U)aJTQUk2>f$xBKw>yUr)~vjv4THrN59xpg-v@#FU^ zNxcBtBc%toV_#i6rdhv6L1y~4WrvW%j2dp<V;mxmuMH20nBMNB<oZi*^?snBC2Y>) z%oaG4;x#SW666NW%jRCK7|aUPw&;G6RP*w<ultmN8_$e7jQggp<R$rEN?FwL6+fbW z+PmgrEc*V6y}{POqu$+%0l@jKp**w=b*r>>vXxZ!5)i166LtTmFU5yHt5e<@<LqnH z>gGTenEIRC5NTg6=Dl8p?@4ICVTzW^B6}BcIV~9~4-wMfB%h7)E0&4iQCWLM15(4z z8px&GtE5+PT@-I)E)_Gsh%kwX%v^Gr2c5S{P41n;YlUST(Jw6*gCAr2rO}$NV3Tta zcZTL&$is7^j*gDhrzIH{u58x~6Al&;D_64AI=p^3DakCE#_U)w_!Pe1rZk}e_^Ed_ zt0k^yVznq+e@rSvoV9;h``p7!CRkuV*$8w+5OYOqu5F^iMhzIj!0>qraR%;K=r)ZJ z`Ak<~ksPhwkx3}7EYt=U@=SMvlcKfSCzJ_DxJmjeoJj42NSV*qqhYuepEC#jCrC53 z))NDq!MW6`hk=aFf`hpLdvpTQzza;#$*AvQdnhM~?jJ#mU1I#oBEu>@ic@xdCdZGW zMKRm}hgY69_}X|XB*1I>BV&X5uAwu$G(BILqujYPJPXpamFC<=5(*ksHD3pnn>Y0R z{meOM!j#grFhGTaXfRT%gT6dp1Q4xF(vGt@rT<T*7$Ml1V?Ap})MrxBxY+@3*uucQ zfJPC5E0`w!$c!FH+r+haDs!!U?~@aoT>n*mdRbnWp^OtJ9T>U*fd13HxAVI}>L$lS zMCJ^@-(>ZpjRGQ#m3*mLv;jkp_H!XynM?L+ZnVD=%$mb&c#eaU6WQZkjn>)#-?UHL zBj)J%Gx<QTH=>Ev;8cq@Z_ZKrb7GI3#&tzxa49_Z#>|!O6*J9z5rIwWdpIV1DpsMK ztj+v=$5(Hk#W&o^l=4v%=K6F~M&a45VJqvgaWWMn#rcd#p7Fkp*u9+*KTkpS#kd(u zAt@p}grR)0fxO!jzQXDlkO={K1xNd&(i~$s^W3pK7)gTvnl8>#0|LCyj^3p+U)*G! z>mVVyCiqqD;4x5zSBV=4bA#WA+{&HS`(29Mc5gr7%bz2hHYp}1O4y|2ke;w+u43LY z)Br0(4`ie{7IBTR-_$tg1A-MV0n9kr_4K-ggoc)nSbN7U4h3r<*k^-Ss!)9-<NF6N z57<R1S2dHT#O+`2xAL=ieh*!+CJnP-qs@lm;~qE?z?tEb)$^DMz%x&5>+4m0W*u|u zMINC6GwR6Hepb_A=Nh|QK4VS!s)nTiCPH`xR>`yaXk|b6mA7e&Jha+qGJ$?awA|?A z<Mh{<(aq~^=Ka{n&36vk>YtA~-p?h?1PzU(H2j7?T>Pp1+Z?KMu5+e+1VreHb&NPg zAR|%rcH^xlzMK$(_LM^M;3j~wlN^}XfpYHm{0ku_8Qfd1GT+8c^)>!~yZGF#jOsR# z>eFWIj4J(Ob)V|7TxB3LbJ)OdMgpjK$J9bSr~H?1G<ooi_>V4A7Lnr9rLIpuPNW48 zUo-Dq?|;w3?>^r))QNobE3bg5H<s$6%v8XAp><FDYg6YM`!R@yGP&|(d2Dr4cwqGW z0A%BOTl!H);zn$TK99CRU-KaOX-8&0{r&Duvh~AP#J6MG+B;Dc5m?{|!bc(FfSF>4 zehd<=4;3WOXc9NoJk>5EN0eAa4OBL}7k?pNv$-TL^Ql*P1*KT^xxlgSaKMcBTW)3# z8OLhY>@W<Yn1J=hxL8Wcl*PQAFAPklwmxE7opT-PORQ06{z`t~iGLow+}HA&AB(D} z+BgU|OBu&DZhe^nP>2$`_(L}6M^2vqcB}nE{;k1{(AbOd?sZ12SJ5}YmQSk$pZxej zp2_t4`pZ0#=PW#!L_Gv?S5!U68wW<iAOniq?U<Ps1<?HUnzAz}LoU?mj_OmkGVZkE zbFbQBS^rPYIr_Xg)gw~2Z{rWE@<rK}v(dM)8oh(?@QuYau!c`XeX`9^ghTE`k~6{( zIuYh)67=NbLXXxfoaky1ajYbq@fD_l3>c+3ZOdCh^QX+Z%*n>iS?PuZ+(y9-h2g|0 z&V*Nh;ME~Y@4Vd~vSHhtC%#Ti=Jpd0y+@;>jZdt)Q9B1LIzqyrUZtEYDMtD83KSDQ z?PBMxUssX~L5|td0VSJOCrXZ#sW}yrSAXpJbb)TT^v`P%v^F1SJKoM+H`#D|)+O(< zR|U!=J0n=(yA<h-P^0cZtG?!}VT#kN&x&b>OFz<Ta^CNk<=^JP=U<R}wpqT<?*VTZ z$N<7}0FjvWva;L4^HO_olZ%D^3X|V>F&zBlvSB`a!}JTIWmopRXaR^kUxptInDW(x zUZF*-I@5=TEhqQE;M0Q~uGwG9eUGe5S3yKmdq8%7%5SOCN&~c6<(^`P$dmLKym#ux z@o-i^1n$%$nqZtaYf)6rHeIz^+cuEx)2vg(z}^`5kju`&wccmv2L3U+Dk?aQ&iM?+ z+3>MTGWcA;C-1}*s(3>xzfj|=S4;Er@dQ{@H2P-3yvvqO=VKG^t0D$@icnffGMBE^ z!I<^>e}Mj@pa)9lkng6;FXXdb-^^Unkb{XBm`^vP_YZU_vTqn<7M=+KWSE@IEdK7y z@t}20A{g~2AJetD&TFp3ZYDzqNC+(}FUrtf5w<*mLamMOD=z>BY$)Xq!J9V;&o}QV zRM66O4DZz%a!t-hB7*p#J!OkU2Q6BE4p06_m5iTB?Mj|_S7q5}X#!S|hEHCFFGx@@ z!0R}<+7+_(f5}ZR_cqZqTnbn$`y9Tj`3FD)t!e@NocD{f?~^BYlSkiM!LpFECge8N zi#D5hTrcyGZEPVo3Wup1`pFlW5K&Fft(|LZ4x$*!Jy<t`rJ$>8S`fM-UiMH9^Fh^K z$sB^b$m?yU=Via%Lm&!&KTa<o-R)(9$oa(YG`~%jY8dxqGrQozTl}rMkKzwss7=}* zZzD23^iG8S3AsbyN;AF&=M`r#=GlG|PP+qn@V0YY(N8#pvk(-&c~JerOE4zY&vzOD zS>ec2e06){xPa%9aJ6{wh2#RmeekL>cPOfgYjQLEYu7&9GMiVEdbaq|kztf>|HeeY z@E?FNFwfAAp!C3WRklDKW#`h9xpR8SIt-D0W69ML#ysVA!%J-5uT-6M?u_=r-(ue_ z0TMC8<_%W*l$p+RzDRn2nm0(?3|D66egEA2_4TejVc5a5P2FlkdEO$`L4(dc5SP`} z+^=u9I(zVa2ECZmY4u)K_<H0`CmJ_(yLM#H%18SX83Q$W%~gitI&g`WPeKGj6vSTk zP!5JKdnW@m+_WPIhIQ<!ip+;StWVW)23OShTFlN?gAdr;V(Dtw-b2K=ORn4Js@!0@ z@M4YOyX4|%uU6i2Qw>;~BiLdsZ-(ENqY6-by_c`k@SsD^kiF_!<Iv3Q#p(wm!_lao z!#)rN+O#<$VnLv>d9<21#C-dftnTBe^ssrAYeTq+_LOR&49A0g{9Fc`Mf^VDbJ%c; zBc%ON8&~TCp98?7^y}+#V;`%9!h>wNh`xi(T4~I3Wkhss<*(#NF{pPX<{FOuE<5Hr zU#<M?QYNg+j$P<-w+-(-=(<DIU6o5E(tuOWa{tV?eW};?T>OgNM=cnDFDMj)fDf(p zIr+6Td1W}umtK14cQRkg-wgXUcQ}gxY;i6@72i2F-*IBf78j4sd|cC?IR~to(?k&Y zVEE(WaPjw+zQVgh3DP1wNoIU8)Mv$fa#eLFXTMkB*w*=13hua?fZ;&Nk3|R(WCh}f zSu03oXRmt3mr7UcWVzpgV6UNW|NcE-<XV-bS7^!ECNFnVi(@z#F(R}l=_BzBsoH%k zkMyYaWn4eg3fe>qYxRI%R9jkn=c`|h>+74TuK`*EIk8VIfl<6X9_d8_VKfc1lCtTu z;wqv6qx7f&>MD`UP2sEG#ue+D)A@HoajXz4a;@r~zb=ZQwUK$DhlAHqh{7qeV8pX* zksZhne#iMJ!Q#tF<ZW;npuN?Vns<^>k|2TPFQzBpq34S<_ZUDGvt$H;J!e8Wox<;? zMd916PXDRZtwWI(lXvF^Rlj`t+79KgM!f}&{=ZtgVq#l5CLw%zfH0QR&1C+T&o2QN z^F4)sJtvM?mXf<u=^W%EgdqFwCm8Q94MXZ7XE}cmv>#4vbib#!&EY?L7}vc&?2)Qy zh4C=9lWqWilwR4pmb^983f$0FB}z#3a<^7?dl<X;T<cYxblpcKn()BR7dEe8*4m$2 zF1syNt^S&@ytGTIHC5_~;+G#?Xuv9lE=l4b)^!Yj^cFS3W^Or0_vhY@+!K&ti`Kej z-K#+aSU>U}6yR81Vr36{JoL9>;b=o|p{47{O@Ecy=7qrwyD<G~*5oWgl)q~FC58J2 zm8_#*xUG5UrInMYoz*%Px8xMPr%tR?z*d31c_XpcqefB^x*Ap;#bn!eu%^SJrq_)^ zUp>m)j~VUEE1*5`gs$uToHLVz6|NT?)R-1u%81`RAGN*TJRmYE7q^s#pl2nde&E%{ z?2LxO)`DJGd-ixmu&mcfx3_C>gesRb;tW3{T+QiZ-=QFnK0~4!*}i;oo<fP`6;bk> zR3vmG2eL_4gLQK%@=WcYgcunpZ^4iSh&f{|`g0VZg1ldtFj|xJi6?I%#U!2Z@X9V; zM9wJ;pChHv|JyKqu!}T%A?$<CzMSe$Vw{u=<EeffYdhbWGx~Zcw<^m8FHfNAEEb0) zp&&NEQ-~2K0x_gda}a%O$9y>enItyM*E5kwD<o>*dB05a;z?)J1pmzf0mzm}xN)Fw zIAr7mO*X-stNMc$gdr`^@^c*Kq`^aoY0$XAov(2oM-0u+O8H7oC^Pua_+tpc(H>6A z#+=WLH|WxMF&hf`a#~#zF|dKpFrN^Hj#kaa%JQDCMS}SU`i+r>Ny|uF3b;6)tXL9s zEV7I{fR$!gpj3~VMNV2&lQto^?Szp5q@VtY{Imw(ljZ$3d7qWvZc5*EHD~}2?6`)A z1>eOEy!W|jCxQQB&ijpDcxd)tgZhP8%o(xZq>KzsF21g8f<4qgsL9dV=jX&vLUc_k zkE;hjW(-d9P?%BJe#}Jyjmn9JuCu&b8>;R6F+O(Suy0o{`%Vz!`o~%Ndf-HL7`T%P zkNW=`3mO9TSS>PpXL@d^8%uAR8J?w5p@MSPMqUcyaZ>qy?)obft&77JDqqMfCnj!X zq(Y@VrvG%pDrWk*YH)=&z5%hnJc^IP*$HnyC0*fA3x-$4ZmSu4Kg@c|<)bbSh&IcA z*uS5-V!HuD_ypbXo$L54O7+k-JL`GGyUcDRHeeEi1fA(y(}IC#f?c1O9?J1IZU!i` zvHk-{{{x6;(wq{B@&uUQV}ikx<u@Uy`bKu_+k&}zzGLBj@;iUA+=#i@9M?=T_c^i3 zCPEWs0WE^>29oTZN9%?Mk=v{-q)#<$nqho<=_M;&tbFnG0DnGg1(<jPyanNPX1#!e z9be$WpEC5T(i})i=>ZIfIHpBVn=T(d`w-=ho^8($Vi}lJYO#ohKf2#c1vv-rb06GQ zjs+%`;JdkR?+d(dM@VmT+<7PuYco{jgHti#Y8XXNvz=Kf&uuMTG4aXL`DPHoqNk+= zoI(X=(yn-UxIxjmT`9X@(J&Xp&orS(w>oIqpgYXe4H$%<MI@DkHa&q_s09f&IMdY8 zeN9*#0!J{utB`IX(fdN)fcDr$lpF;{`9=L~Qg04pGC4@31BKoTEBbyP&v_%H>=49w zr}$!JI1m-`Rm!sGv&-R1wj8$exj<$S3T(}UC0{fN5L?o^u0sTReKgp+dg}FkO>r|y z*t#uBSnkVo>qm{#DuZ9IDn1E4^Xb$)_;4=y__2Q_S{cg+lq-6&>knc+hy55_={x3r zL6S8WlIBN)48iTu%UT1kzxwWN6mlMRMTwOPHh+2tydR*pD%!a7C?^?;zQ?@RY%jOK zLR;gd>+_|PD>b6@7P+Dob))e&W7Qcda<p-=xxe1)_-5;6GkZHssGvQDYZ)mJ&`6>| zZ_efbNln6;2zxZ6O!ezq8oE*TV%qJ*TT3o_nl}I>h3Fba$w=Ome;Ak{Jso*poNGO- zERwITZtiL-$>ZmD&Cf@agBh^lw(N*8NB7g)gPh{`ybMlqRG^cAy1ZwBUGKYM7_sl< zQ-CR$oUAwsf31`i5>&QtezWd9^X98Qyt(cZ4*ZTEcq}oP6PR1qXFuS0h-^k0J{Tuf z^<D8P|Hajx?Jid|b($Dh(s<^Mdhr$~1iF{PkHEg0cOLUrR9c>EWxB)V*zaXQMDfS| zD(xxx!HSCzgvbUCW_%Fkw9LRM8#tr(=to$=3u77_9$QGmrMS1$ftt`+;c&;_X)^4p zT5dp`yaH&*z`-V)j^p;agY92S(Q1tZ1-fDcZLE$76us<fb@!E?!LSMTQ>j>5gJ21q zn*cFdV#o$RHD$iB@3>6?k<r=@?QFveCGkeiJUGqN?Uz0r9+^a#C$EWV(xfIysHsL# zEggG&HG0evQ{CMAAT3ohC@tPPOAEg`OdIep2UWW9uz>BSQjhdlE9iW=<Wxl>*xNix zdM<7-q-r}8AMdN@2L^LC|H71e(3sVERAEYbmE`-U>Hd@iu(AagpZ=fnMh($LND7&1 zuaSNibNQGF3$GD(gEbDWWAXOJ)VcfY+GJ@jwKkrR{)~VPzA?xE)RgLW47~U|+2PB| zO){H&b+UAqG#+7wUN0b;qzb9-F!$wxC(TG>3sVIS#2OD@=Gb$nwDrF3`m{fj?!qQ3 z!vHAXft>6xZoLk$bJ?cvKe3I8JbD<D7wAoFcEizdS4BO5<ow`|W+FMV9+Bypr-N_5 z3upQ1Jf8Q#YAl4K$0yM3BI}(`$s6MOlPjHyuIQdB$D+b!Z=7%ZQu|X`g9o@fcw$^C zR`bCMX1#$+vAE>)6RO3@=n$|?`!I60SwFGo)Mixh$M{pu$L`pcc+|?=3z-v*>fB`e z!3_C%FX#bx=^r87ONp!_fI=0ac*$rP=OWdUCEEHO9!!90$mgTYmOkburc<n-h(Imr zOQW7Xk%M$r=~%kt<k)DJkCq?G04#J2830jGt5qLv1PQ8!>!P9EwQ<JpvHK!$Dd#p4 z@@HYhH;?SzjfSUV(4rLt1(6$#4wgAGX?=Q|)f<*!X)&-Yx1c$kHAp$5rKB^ztn=OX z24{J0j30fgR<KMt23x4!^}&FhR2aO_iG7;-A}I>SRKW6m7+T<ScVo`^l=u}k)+~Kw zknB@83*x2sq-<ykGdbnC^LmA?M5=y3w{}^*M7n4Omv&|XhX2H9{bP54wxW?`ibd#$ zhRLSUj6HE`>=iia${B~(w86v~BWHr%=J3e;UQq7RwRPX}`^cP=vehgb6Wk&C^d>UN zb00O6^35TmbuI&AEm_H>Y!F6UmHfDUo&M+_Ah>1Oes)sbJB);(re&>ecPN5M0Fz?f zyjPPEU{#X8YBg!IGD{NM;h1+~m0{WC#}qP-pS&m$^I#`4__=~e%iA-aIAV(cZ5*v% zs>QmiQY^N{a%b`Rc$=Pogn!YI^?mQrl<>sVCWzuzb1E;{%zpGR6tx6AlIW0YBmEL{ z%um+(<De5}>=@n!RDGRn$T93KK9=5|zU{gn?H_@fUuCuQEchfm8C5xEr4yf`1nO5( zTP_H9aqm8~x{mlLSX<87uH6`r#e75iyven36~i`i`B);!J66FIxcLyC6z=1DISpba zl)vlwUUG*iKhM9|LnJvnmaValjuGs(FB3<7&-gXyanl!ntv%`z>H_j7`L^$;q$h<t zgng_Ov(r?eXFKz9oa5^(Q};>^cYff)0e=Dlq}=tFO8GpR8$kaxoHA&Po9%Q?l9;gc z`%>@Sj(ax@S$^IQd9?2`g8Ve~M~19<>mOiFZm#lHQ=Xg(n!Geck5NF&<PU8<<<)$c z!N<x>Es~P=QtOP@<8>{&JC4VkDWdmNdy+x{ZFuITS~zVw@WM%{N6*o^KX|dM3oplr zYCze(I9CRXexQO5om6ZQif2jNO!oIzVa1+k&NjDFe1@R#`OIO$E}T|4_a6)v<`_yZ z05gUq0DeDAe&w$LNarcYCW=jh;zloga}^8VHUUBo)hP9jE$cb;S?h*@{;zve@?jI^ zh5|EsX4s>r=44$6e-a19G<;)|#ns?_-_WUW7$6@RG2celWFuSsbL76L0IZSM#5|gB zP>~eZI?h-b?Xy3-6px1H;J{Vl&8Fki&6Cql5uz{6U00QVZ@NY&*jK*smrL-c^o2|a zioZ3kw-RAvu9$+LZ19T6z(zlCgG`pD_7i{Ui3be#y9453u@N~@O8?D<IK>*zM7e)p z<V>LwC({2<+y;iP^OXP`pp8Lw+)10PeNE}{_6<-Ph2mHzoa4mH!SI}P%&kdrHSAFG zE1JUW(drXQ#5xaY%CaS{H^~X4xK{|n<5OXNs7zzl)m%Hd(<Cy&JyM=8puN1MxlF@M zhgOB-rEVN8SJl4J{q<^&vHD7+G)tjzL*x63esC%2UJ_?Wks<+ktf&}PBC9-7@bH1; zWlc#e`=S4MDz8z5Lta6p%y7U22Ljq7;)~G9Blb{M2ebmrr^3HmeoZLVCGMFuPKO<N z<r-d9we-D~J$^Hbv2knaZZg6l)_FyOHMp7HvO#hw<jh8m6FnnSn4upJENz?K6=tnj zJL5~JR>h7Q*zMrL?tix7!A}A_Jcs|I>~QMq3kKUf`;=Su<qPOfZDLTO!O@Z6e$bHK zD4uTw9K5qVfYugohV?;{N^P!R|DlW2NYivmGR)Ktt{vf&k^cwy`8U0NRtfMA@aXbh z&DKS=lh{syYi1lg_;23aEB5A@J9q?cLw%(+$*GO;)eMy>W%+(UQSgW45*OV*7>TN0 zJ6NFewdTk$wDpoEJer&;*y|0OYd*M0)Y(Jts@mCNr?v60SZQfRF9k?|f6sJpmb5@c z`11whu3a=SH0P$mbXX9)v|NL2pBOUghJ4USPC0zIrgD@6K8J4;Ac@@Ji*f5^L3~|4 zOQ~%#=+Fs$5P@qZ^Ss06UQNp2=r}_jH)p0p>Rxn%=a;0JVEfU<voiK77<cGe|0tIf z^VO86|B`Y$Gc^?9iLH3rO65->|76}?%w#SW9Y{p(X%;Qko<R8f>gZxGxxoo^9JW=M zWfeUtl5mo&^uvgX*rPIvOWl$Whv*){slv20L=_tn6!S|`pYC25CYRP%l{o)Fd;&4$ zkrm^b0r??rrQc8-#SJTR-R|nCc3<lkk7;le34U3ZgPrZ>m+$6Vl39EvS{im%?eO0v zNpEvsO@!DyR{18&6EOk$2T*K?iBY=PCJbBIVRg1q@Tzl+C!}-!!sZO&cFMz97j8@2 zBC2}7SG`H57->IZb+lY!xW(b8OV|Eb&vyE1%04Ir1%`??0^<4!0L*wtc3f=>D2!>} zKY;rSU2)}OE2)Q!d4*TCAq-`z8frfAgl%VTs+Squ(hR`um?_UM=vDK>B?43lBU1!_ zowiP<tF@`4(XM6yDfb4U8!cY{jiGck&jtaimFvmfVKXR;b$l~v*7~QX6rJ^-mZ5s# zeUXkA7d77Av>r1m9Wm#|8mUb^GcScM6&l~}_v-6VOi&b1Pu5(o@<YomUm18k$)D?l z8Z`B-GV&gMF7}R0P!5jSZwd2Hoh4v7)E^XK)xiLR2GEn$3{T`?fvD#U$2NU-fmtn9 zuYO*o2cJ0!FI;)hz?3fF`{=M*spoGr-War6anLO%2k1d|5|X%?Zu%WDo?AYvA08md zZfMi0^}aSC!(=L*hF9RuA)0GirW-l?C4#m`jW5)kW|M=HJgY@aRQ`IlLE8SZ^Dezh z)IPNtVNMQoW4MpbCHDMOs@Db}!;nNq-VIw*IZ}<>RWjb2Mk1o9<Xus#Y0$n@s}X-o zKrnIk0yi|ptk^qyb6ra|b4uPq1}M#TRn4;_&@n}ea(0R%PMU*N#^llL8DHSiueW-m zv|8Lf)-<s^{oI5zT!io)>cNHzWQ&H=9C+zrdxnx8>qo<LTRF-5RAd4CAU_^!EDj9; zw3vT&_AOg;@5mbC=Gq~(xL8OY2MbDco?;MyP8fUt7_~ahg;?|5HSIMQk4xE-FMMaG zk#|E)wcrz(vou(-ecVP>dZ7`HBlpdNSjL8Bj$D_$(DC)ep%YrQjI7q$)D)MM5wYjw z%yG-rb~)$$$9#!Pov|PWODI!KNhrTHo$q%1{>YO{b*OrSkGxd);z}hKy}!O{Vi)yD zsjR#5xLdHqM=z<TRzE!}2*K1^+{^VP?H3&+a#0m!20Li`iIgS*0#@&Hy<ICn)URO& z8@rc2F(f%>{6ZjF=D4l$?T8``zM!2j&WkVZ+Z?dQ1=Ru-9PIxEFtMX)<+}wkN?GMt zWY|qhb<KFX<g=HFe0_hFz^4(`@lX=>7Q9S$@+;;AGs~wwJBH_Mr+Y2u9V48&&d&a^ z5s}>7GPH)JZcI`>{EHS6iAx{Y+W5Mis+;n%0$%!ul{a5MwKIK=9%0sa0{S89nc8}` zDjDhkF~8Rnnn~`kV?ky&;?XGdI|jdoD?NhYli*r_srSx{Ti->tpOl`7YF<-pllP`Z z`RTqc^KT7NDHAeckxR!taxv)b3IFD0_e)b_PyJsaKDAF4b}5vnq_1jvODf(Wc^@U1 z-&AZUCq!BG3pqAve-{5BH6vgQ77A6|2)DLacPChBwtU!_E~+~488-@OksR|)V<aB` zP;c5Ak!~3YMvZV5_bJXkC6GvW@tqR;v{9h)_kp`!qK_<xe|!LcVPpG0fXh?N<zv=i zw$z<kTbNH6e=pN>t2ACaasqu;Y$Xk|&mbA5JNrwwDBo$c$Yu2sr%1iGgX}Teyq#IL zOA%KQ%@6?ecJ9w;z{|Ox_Vhlr#_d03aEQ5c0+C1u+<FV_xln@}5`<az{Cb=U4@SAp zu&waK=#OP0()3a5N-fqAa(15v(n!}u@10C|3LNi=<jQH|DK6H`57y4o-vCUOBswqE zx3Z^!?&1{<%{~kx09M*L8|(|Mo_DEp8w3R}fV2{0R?l|pG*A1T3~3j>ao;v}&@bbX zCDoAOQp98_s5FxElyS9WJReS_&4!rYyZqiq=S+3D99tPpjt~yROgz_DXm0&Y-XQO! zv7SAbIEEBez>wsXL{YGIMvmX`7s4_CL<1unxpEeJ4k^twuTAy|ziNgO0CBy2h0((X zDr|SSZ|?unk<a*WgNdI}8d(@Y+e`-RgVPVIO=@U=|6d}!|8c;oph+Tc3ZrvsKM9Ap z^+rZj;(q@BLdQHs@F;hZ#3HL*N+!8<JbFp|9E3JT9_);`IR$CZ5UU5r>MKSd#}#;~ zg$pz(HZi>cN`?)0?UM>LXf7iVGd>>Me(FEKVLn?08HFb07Zj|KXGSBfxr)JjwID`r zQh8ZedNXy3**uUibII7Mf@}%Ld)61%{LjN6O~SLp48N|jxHTMY>Dsb7ZBfw6)7@JQ zjsRa*hXZi;z@&_vR1qER8st8$?r07DK)Bnti<$XP(u46%r#Q=)fTtLggoGm_uJWmI zLkJm9RrOJy43t^1+UP|3lFO&O&%~YZwWg~Izvz&aAJ~4J1c}GNeNFR*L3$_3y)gRd z>#wSsl0E|!NZZ-1xQn1YO;zK`>OC2pFO=M=Eu@e3kzH(PA7AXA6*#>ZWdvhTaT1L{ zK8}_FM$1U-4!<+H2c#HOsN}!*6(c*>F53`fUC%2JP@|KzvUHiAQ{7wbb018AJO0dc z*>i7+x84GmgDRItTtr2+8`bL$`B}599xfu}8Bb-GtPeiizCON^MDi#}n{neoG2A^i zR$p-!<xf)73n@5nlJik3x?l<HT9k8_0-n;qU0a>=NG|h_HlhL;_q9EH6bo&;oP0o^ z)>PG#+gh0+c?AQvmTvihlWo8lzb7jmti04!!q?oahAiJbHPG+<PgkCk*yV)lCx6F| zJ@bCJx!=pL(U9tK9LBlxaAndNwk21LXIO;`;3VIpcc3KPZbOL5RC>+%ia#JISId5{ zR$~9;3*$?|S0QgtF^#l!4!`(+S$*4h%g9wgyWYV$e_Q0!g=@BshyB6r0jY{z61EH- z+j>B$K{#yt;qBnh`Jd-?1xf1j5`reb)eepnp-@K$V$OEg$pF$DXSsKSAtU_^YtK`D zp8WjsS&3+1{`QUW`|=>Qr+=E8G-)0Cf21H)BG22ew&~G^#X;O#OeNulCA;0T0znU+ zZ$0J_80Qum+N8o{{KhjuMy6#WmpBwpOy<gn_va;#D6uP62zhGg6cjV}B{^tgOIP(* zaASX4nfd%S*4E2{)9km%kDfPQ4qnJan*Y(i3>oqOwt$G^OZSNvuxYOQe~vb;=x_JB zqp3l_ojeJ_6nf541t>0^PaS2xAf+oOZjmzl)O}kmXD9XiRnO}ArQ+r8_4T>uhbH^g zz6$wq`<2zX!{CLCrue4rKNlLbJ!%h=2+KMBqu%jfuH;F)EiEsg6$JSP$~UE%F)fAu zv^J6ZTocbhkSk|q@pd>#Q{O7J*?)xUUaOujCrz%LLrTi;kaCsg*s*phf6CkWw}d`8 zs0?LzdO0x1d|_H(cKDG_GJm}(j*c6hyAQ~r_=E3`7cpPy*<#!syNQ>V&1{xXp>f}x zl$6__{%7P2ap{@`UTYa3xO`1-7{ml!$@%>m=U389X=+d(G3Sn-iX<~4rZ7<GZJ{w@ z)mr;2P%U)KXnm`z69%LY)+IHgENAip&9T0FJCRZ3SA=UYr)j$~UDOR&aBoLgjr*!b zD|(9zTo2R{t)UX<r+8%q1v0<BeqQ`$(o>5)U0-Kje*_<vq5&BFRW<#cW|sddUu?#( z#-8aM)en-cdfm=y6RVHZwczE%WpLP2-A5#nQ(ZiZYv9<8J=%aexo*^8e6Q!|Mlv5y z%dyMQ3NE)8z-oC_6QO_pe|UPYa5&rVe|v-=B18>Q!{{Y?iQbLgMen_v=$()!h%$oc zy^hZ4Jz?~TE_#g4=)L7X-`{&2@0J}K<DSEHuj{kcI!}y^>gJ3D@UixZW}J}Y{Tj-M zLAsoO5q7|g3tc&DEiV);;y?dL&$>Q4DYmmMZt(1xSHxnY79<F)ZD7>Y$}P{7fP@D& zwT!}T77Q4MRFjX5h$4m7eJiK9Jcex*8gTNQ|H$#<#`p_HyiHB_daT$=rw41LeOD`i z+VF3syEwE6P^A(81iz7%N=qX#$!tBL019vM8F25?lR}}m>5)Zib!ulv;qx{L=!-1t zH`MDkq*uguak;-p24s!R$h~K&u7rt%*fzg2xSw$l*-?cy3G^0lp^W)S?FWR*`9uWe zs(x_p(EC<a7c)@jxw9`tj#Ioaf;ld}S1VWEV*e>|z^Zwcg4Rq6y2iNACR1=0??jAS zrzn{?PBbN{3*in|7ldg`n&WO7!~j2G%blIZj>6jMpHF}ns|kd8bMI@hr`Z0?c!-Er zQI`JN#7tf-RNoqY)kjhCm5h;dt^8CQxZ3}UYiv|h-l~43wYrJ7IH4rr??&6r=IcS3 z&Fkvu_@xYuApDKwEBlnSPi{5UbILywaLJ-6FP7b}s{UhkdTEPfsjo7LS&^b?1eJ_1 zl4f2sewbQ+t$P2qkS%*S$)Bs~(TQ|RnL3~(`lOOKD)%x^z^(akfmKNN*uEbruCI9d zXQgPEn@_q*U1PU<%u@+VpZsm)0FS+E1x00Zt#WLjPuHK$hn+1QG8SCJl*=NmHeG6s zH(VW&9A@RgBMtA_m8+V<+v0*8Fkz}K^_gD!LU!f_xsIl%gUY4TAqubHBbuXm&XD!Q ztXy8x7Nbkb`i3A5O5YV~cY3sUQWs78GyHg|YQvJQ#+E~yI+JOkZQlGvAX)Cyh(gs7 zrh@0@`XD%*!k6a!LS)#AEbk}cl%hT2h*NulF_HqmZ03;X%^;u4K+Dl$)|&m19`)^J zqK*0O$xc=NKI`Ep+FFB#oMw2*K{`=lUjG1xP5e3lzLW>vt<uh6^B2GMq2$^3yl>bF z-JM&eGb37D-S}gRHQ1unq@k!hF$ahpZ){^;eBJqd_z@~r?L#`reRP3RoZ}CA`GYY4 z_%4arTM?U3JjgT5%?)B%^xoH$=(?6{?`VX_PifLA&zh1?5$4{LC%n1KRb6;7Cx^=H zovBg&Ob(!c(;5F+s)Q4>w2+nTlk9UZ3}GdkXb4h?oQxW%|1$E*4b_e02ypVb{ZHmN z9y5<@>}L$GQg-qate!~IR!{Md;SmQeyYDCa3ihs%FcAhrJWp>p&YEB+<N`#jOyGxp zvc_9v$nKdEeUX<DiM5#%37dlk_(;dzyBU^IW^J8X@P`ez#MY=JP3N`UmUjYwG7>0w z5OZz!UxoMXXT8#-_DpDa&r9GF0a!cZvfq?7jPtw8hStajeN2%500Uu{g2?f^#MQ1Z z8^iwqC^5u80L9wA3w&g=erWxw)&XZl+%(VM7-LZendp_tZiep`6~9hP5EMTzlWey# zy1Jt7MK1%anc|5R{D#58f|viCP8vYVCeLl<B%AWKPG=QEls?SnK~Jf1#_X0Yxx*tk zU_dUsruP}P|Etw-iVAcKOL~VzRO+l8YT1;#$Lwzf(T|n@84GYSnntwMFW5$;9I;md zN<q&!!wc)J!c9cYWrprf6PKr^#~4XOqB9B+BWBXplKCWw+u<tBTlr2HEg;53>qwv{ z+;_5d6Qj>HN&FeM2A}gF8lH!){Te7jY*Skbe@utYiMP<B_qY)YZ)?JTJ;PvR#G!T8 z9&@h(0I45(i5}iQW1EuLWAxBp;E(y=Z-$9JR<Ik2#b6_IS1+)%H5qM_wDntvy^<44 zm@sS^XcFuk@OkUW6sdA$M3IRFB`NM#3wQ;hfb1m6izf6&)=Bdpy+dN^ld^h&2BB;F zUQ>uCHXxrN7BGw{a+k^atm<P)tC20<6mG8*Rrh2PUuXt>=mr$cxT#QL%eG05N_wS? zX35=xu;POpo|>=@E##%jg^%E6QQ1NFI_O<5-9sG`0zPn(l5>MX`k7WE>4jf=K7L2T zv$Ub(1jCfR9hz_GrLbX2&+(}GFkKAvr@Gcw!e6q^aVK%k*)Iv8C*}!`RF*{d4l^sm z9Y>&%`)0x6URqV!QEYuU^oeAoTI6P?!vSEfUt@2LOgriTwkavS>-wLxr7b-CPfQhr zhPHTpL0Wvthf10I>D4HQaA4*qg9%brM`25w1@eD@RBxX4(vwNflJDiq=y8}q+@0eA zSGV@ijlH6Us^o@9bN!{L9qMy!>9qxymz)*10c$gKd^O7AL37(&4iB<#Y;#(US^w9x zM_dh9OwzsZ2~mGEaS;V#g<WUp6<gFx9Q;1&rn@+nE=fh`IVYo1jk>7(^r<3{fNx{s zv^b%jq7@8@ADWw!mu#IUydafr>meK=)5ZubW7PoKFI=0tB>U=Qg}n5b@kuyVOPN3S zrGCAXaT5HZ<kWK$O1$tUyteWns)HjELyB7ziL;g{wU_r_j?9$swE13ozgR`WPMSYs z&exqJTZ6vMtMcMou1c*{>fBoBCD&?WQGG{i?$aX>OLHH!kiDgHyZJPGOo!b=L?bn| z$er$19xRbqA;r|}1SH=Dy-TF3^W(9=fhdaBeic}S&RkS$t^M?B|C^D~rGQ_h8C<c^ zWm6I=*yrc$dW=+MvBzS^mxnLk8*1P;^d8|O_fO!J3E8ppKWk5v;2z)1#7^m;mmMo& z`JC-uEN#9CKq?ySu(5mq#kG`<%K$};wRA5_3oL}JdT~!eCrgm(Q@eGmQLNRM<~izw z$Hv5xfbEWx#BGQ=@%fwrqtLZ2$FpFZznaI*h2S(LZx#X(>x3VnwND69?CQ)n&1eU@ z4n%RlNHdZE7Ychtd#&95(1ogMp7sZk#y={x-o*}7sgG)*EIkQC`w9z!W@i7hnJuw( z{P}v|%@%$TA-czIT<#CerDRMHvU>-l`hFt<3@aD3#vt)-v{PL6)MP=aGcvj^MDyee znTSgM18Bm4^*?M`2SBv?QG}a1+n)&t*!}ba1jWi<%IO_dMwISQ!ZG82$G(SbZGD{0 zVh~*X|KvbAE(|i5A7xLAs{a15v(HfW;DGVq!bO2e{-}>sl`<6#FZ;&%0f3*F$tj6s zI@!}vnRUE<`I)9O<Tdu6$T2lu>6K>FI=+-CFUN_^K34=xr9>zNMzUfn4H(x#IH=Jr z*wkP>OcALO7Y3Go!fHz;CR2z5nK2WUa`tQV46|<Re9>%oODv|q5EWTSXU^$I+&>Hr z+TrV7`h_TzqT~0Dh)n~BDr}5%<C|<&s9IKf)VOq`og4Fu;d3Kq-4?)H<qJH(3uvxQ zVkz5t0DpDf_U5@!95(?D*f=Pl?q}vm!j7Lrr8x1ekYqhx@YHBd)Y`aB)gMag?HpTl zX*T<LcQi&C$5p@3n)|gwB*$CfAt<)+=5>iH$r{)asD+oZTKGHKn;+rzap^o3qSBtc zu5Y~DX+GmRXC3k5erEigm`e8gRTV#*e}`peDMx>moNrmL%C)d6>)<Ps{m3$$Bo5<- z9UMVN@1I?&3fdW%9&z~W2i^==B$;#7bNz29nQNUg%%oA<70Qz>eLuLR_~I+QqEty0 zES|TWxR8@H*uY0zfXalDvt+jW(+&JZ*+-&}l}$MxJ4lj5s_e3Py^y5IgDSb6Z78kb zXm0gi+Qs#WErBp5D$;ki>1|aXnQ*gYkYv2r|6wMmv!=cWdwKYl@_c;g1sm`h$}pTB zaTv#a?M0{lWIiho2E5ow;e=Mlx+5G;65onBSz2;Hw6OLUq?Y*bq-w+i=kTQ%;vS}( z6JJK{>5`jX#o2TZHOcxf>�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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 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 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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⪼*xTXgYX^VdW7w7LhmAe#XS_b%9dT!I9@TT$ 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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 &, < and > in XML strings + * + * @param string + * the string to escape + * + * @return the escaped string + */ + private String xmlString( String string ) + { + return string.replace( "&" , "&" ).replace( "<" , "<" ).replace( ">" , ">" ); + } + + + /** + * Escape &, < and >, ' and " in XML strings + * + * @param string + * the string to escape + * + * @return the escaped string + */ + private String quoteString( String string ) + { + return "\"" + this.xmlString( string ).replace( "\"" , """ ).replace( "'" , "'" ) + "\""; + } + + + /** + * 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> </@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> </@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> </@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> </@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 </@lv_column> + <@lv_column width=100> 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")} </@lv_column> + <@lv_column>/ ${resProv.capacity?string(",##0")}</@lv_column> + <@lv_column centered=true>${resProv.difficulty} %</@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> </@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> </@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> </@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> </@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é </@lv_column> + <@lv_column width=100> 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")} </@lv_column> + <@lv_column>/ ${resProv.capacity?string(",##0")}</@lv_column> + <@lv_column centered=true>${resProv.difficulty} %</@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> </@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> </@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> </@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> </@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> </@lv_column> + </@lv_line> + <@lv_line> + <@lv_column colspan=4> </@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> </@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> </@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> </@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> </@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> </@lv_column> + </@lv_line> + <@lv_line> + <@lv_column colspan=4> </@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 </@lv_column> - <@lv_column width=100> 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")} </@lv_column> - <@lv_column>/ ${resProv.capacity?string(",##0")}</@lv_column> - <@lv_column centered=true>${resProv.difficulty} %</@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 </@lv_column> + <@lv_column width=100> 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")} </@lv_column> + <@lv_column>/ ${resProv.capacity?string(",##0")}</@lv_column> + <@lv_column centered=true>${resProv.difficulty} %</@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> </@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é </@lv_column> - <@lv_column width=100> 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")} </@lv_column> - <@lv_column>/ ${resProv.capacity?string(",##0")}</@lv_column> - <@lv_column centered=true>${resProv.difficulty} %</@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é </@lv_column> + <@lv_column width=100> 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")} </@lv_column> + <@lv_column>/ ${resProv.capacity?string(",##0")}</@lv_column> + <@lv_column centered=true>${resProv.difficulty} %</@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> </@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> + + </#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> </@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> + + </#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> </@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 &, < and > 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 &, < and > in XML strings + * + * @param string + * the string to escape + * + * @return the escaped string + */ + private String xmlString( String string ) + { + return string.replace( "&" , "&" ).replace( "<" , "<" ).replace( ">" , ">" ); + } + + + /** + * Escape &, < and >, ' and " in XML strings + * + * @param string + * the string to escape + * + * @return the escaped string + */ + private String quoteString( String string ) + { + return "\"" + this.xmlString( string ).replace( "\"" , """ ).replace( "'" , "'" ) + "\""; + } + + + /** + * 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 + * <technology> 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 & 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> </@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> </@lv_column> + </@lv_line> + <@lv_line> + <@lv_column colspan=3> </@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 >= 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> + + </#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> </@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> </@lv_column> + </@lv_line> + <@lv_line> + <@lv_column colspan=3> </@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 >= 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> + + </#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\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]"; + 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> </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 >= 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 > 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 >= 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 > 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"