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
+ *
+ *
+ * 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 E. Benoît
+ *
+ * @param
+ * 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
+ *
+ *
+ * This method will extract the descriptive fields from a resource information row and store
+ * them in a resource information instance. It extracts:
+ *
+ * - the resource's text identifier,
+ *
- the resource's internationalised name,
+ *
- the resource's internationalised description,
+ *
- the resource's internationalised category name, if there is one.
+ *
+ *
+ * @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
+ *
+ *
+ * This class is responsible for converting empire resource information rows into instances of the
+ * corresponding class, {@link EmpireResourceRecord}.
+ *
+ * @author E. Benoît
+ */
+class EmpireResourceMapper
+ extends AbstractResourceMapper< EmpireResourceRecord >
+{
+
+ /**
+ * Map a row from emp.resources_view
+ *
+ *
+ * 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
+ *
+ *
+ * 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
+ *
+ *
+ * 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 E. Benoît
+ */
+class PlanetResourceMapper
+ extends AbstractResourceMapper< PlanetResourceRecord >
+{
+
+ /**
+ * Map a row from emp.get_planet_resources( )
+ *
+ *
+ * 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
+ *
+ *
+ * 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.
+ *
+ *
+ * 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
+ *
+ *
+ * This component's goal is to read information about resources from the database and return the
+ * records in some usable format.
+ *
+ *
+ * It does not contain any method that actually change the database.
+ *
+ * @author E. Benoît
+ *
+ */
+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
+ *
+ *
+ * This implementation simply executes a query using the emp.get_planet_resources()
+ * 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
+ *
+ *
+ * This implementation executes a query on emp.resources_view
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 @@
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
\ 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
+ *
+ *
+ * This interface defines the methods which execute the queries required to extract information
+ * about resources from the database.
+ *
+ * @author E. Benoît
+ */
+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 @@
-
+
-
@@ -29,5 +28,4 @@
-
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 E. Benoît
+ */
+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 E. Benoît
+ */
+ @SuppressWarnings( "serial" )
+ private static class EmptyResourceRecord
+ extends AbstractResourceRecord
+ {
+ // EMPTY
+ }
+
+ /**
+ * Resource row mapper that calls
+ * {@link AbstractResourceMapper#getResourceDescription(AbstractResourceRecord, ResultSet)}
+ *
+ * @author E. Benoît
+ *
+ */
+ 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 E. Benoît
+ */
+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 E. Benoît
+ */
+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
+ *
+ *
+ * - Introduced in B6M2 with ID 1
+ *
+ */
+ 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
+ *
+ *
+ * This record describes a resource. It is used in the empire overview's economic information and
+ * for mining settings.
+ *
+ * @author E. Benoît
+ *
+ */
+public class EmpireResourceRecord
+ extends AbstractResourceRecord
+{
+
+ /**
+ * The serialisation version identifier
+ *
+ *
+ * - Introduced in B6M2 with ID 1
+ *
+ */
+ 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 null
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 null
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
+ *
+ *
+ * - Introduced in B6M1 with ID 1
+ *
- Modified in B6M2, ID set to 2
+ *
+ */
+ 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
+ *
+ *
+ * This class carries the part of the planet view which is only available to empires owning the
+ * planet being viewed.
+ *
+ * @author E. Benoît
+ */
public class PlanetOwnView
implements Serializable
{
- private static final long serialVersionUID = 1L;
+ /**
+ * Serialisation version identifier
+ *
+ *
+ * - Introduced in B6M1 with ID 1
+ *
- Modified in B6M2, ID set to 2
+ *
+ */
+ private static final long serialVersionUID = 2L;
+ /** Happiness of the planet's population as a percentage */
private int happiness;
+
+ /**
+ * Happiness change indicator
+ *
+ *
+ * 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
+ *
+ *
+ * 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
+ *
+ *
+ * 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.
+ *
+ *
+ * The class is used by {@link PlanetOwnView} to represent all of a planet's resources.
+ *
+ * @author E. Benoît
+ */
+public class PlanetResourceRecord
+ extends AbstractResourceRecord
+{
+
+ /**
+ * The serialisation version identifier
+ *
+ *
+ * - Introduced in B6M2 with ID 1
+ *
+ */
+ 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
+ * null
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
+ *
+ *
+ * This class carries the information about a resource provider and associated mining priorities.
+ *
+ * @author E. Benoît
+ */
+public class ResourceProviderRecord
+ implements Serializable
+{
+
+ /**
+ * The serialisation version identifier
+ *
+ *
+ * - Introduced in B6M2 with ID 1
+ *
+ */
+ 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 E. Benoît
+ */
+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 E. Benoît
+ */
+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 E. Benoît
+ */
+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 E. Benoît
+ *
+ */
+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>bgc#macro>
<#macro abbr_st>ST#macro>
-<#macro abbr_gt>GT#macro>
\ No newline at end of file
+<#macro abbr_gt>GT#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>
+ ${resource.category?xhtml} |
+ <#else>
+
|
+ #if>
+ @lv_line>
+ <#local previousCategory=resource.category>
+ #if>
+
+ <@lv_line>
+ <@lv_column> @lv_column>
+ <@lv_column>${resource.title?xhtml}
+ ${resource.description?xhtml}
+ @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>
Our scientists are still settling in.
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>
+ ${resource.category?xhtml} |
+ <#else>
+
|
+ #if>
+ @lv_line>
+ <#local previousCategory=resource.category>
+ #if>
+
+ <@lv_line>
+ <@lv_column> @lv_column>
+ <@lv_column>${resource.title?xhtml}
+ ${resource.description?xhtml}
+ @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
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}
+ ${resource.description?xhtml}
+ @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>mcg#macro>
<#macro abbr_st>TS #macro>
-<#macro abbr_gt>TJ #macro>
\ No newline at end of file
+<#macro abbr_gt>TJ #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>
+ ${resource.category?xhtml} |
+ <#else>
+
|
+ #if>
+ @lv_line>
+ <#local previousCategory=resource.category>
+ #if>
+
+ <@lv_line>
+ <@lv_column> @lv_column>
+ <@lv_column>${resource.title?xhtml}
+ ${resource.description?xhtml}
+ @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>
Nos scientifiques sont encore en train de s'installer.
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>
+ ${resource.category?xhtml} |
+ <#else>
+
|
+ #if>
+ @lv_line>
+ <#local previousCategory=resource.category>
+ #if>
+
+ <@lv_line>
+ <@lv_column> @lv_column>
+ <@lv_column>${resource.title?xhtml}
+ ${resource.description?xhtml}
+ @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é
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}
+ ${resource.description?xhtml}
+ @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>
#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>
-
+ | colspan="${colspan}"#if>>
<#nested>
|
<#elseif width gt 0>
-
+ | colspan="${colspan}"#if>>
<#nested>
|
<#else>
-
+ | colspan="${colspan}"#if>>
<#nested>
|
#if>