diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
index 26a6d01..9d15e1e 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireDAOBean.java
@@ -27,7 +27,6 @@ public class EmpireDAOBean
implements EmpireDAO
{
private JdbcTemplate dTemplate;
- private StoredProc fImplementTech;
private StoredProc fAddEmpEnemy;
private StoredProc fAddAllEnemy;
private StoredProc fRemoveEmpEnemy;
@@ -42,10 +41,6 @@ public class EmpireDAOBean
{
this.dTemplate = new JdbcTemplate( dataSource );
- this.fImplementTech = new StoredProc( dataSource , "emp" , "implement_tech" );
- this.fImplementTech.addParameter( "empire_id" , Types.INTEGER );
- this.fImplementTech.addParameter( "line_id" , Types.INTEGER );
-
this.fAddEmpEnemy = new StoredProc( dataSource , "emp" , "add_enemy_empire" );
this.fAddEmpEnemy.addParameter( "empire_id" , Types.INTEGER );
this.fAddEmpEnemy.addParameter( "enemy_name" , Types.VARCHAR );
@@ -141,13 +136,6 @@ public class EmpireDAOBean
}
- @Override
- public void implementTechnology( int empireId , int lineId )
- {
- this.fImplementTech.execute( empireId , lineId );
- }
-
-
@Override
public List< PlanetListData > getPlanetList( int empireId )
{
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
index 4602ce0..2e3838b 100644
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
+++ b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/empire/EmpireManagementBean.java
@@ -146,14 +146,6 @@ public class EmpireManagementBean
}
- @Override
- public EmpireResponse implementTechnology( int empireId , int techId )
- {
- this.empireDao.implementTechnology( empireId , techId );
- return this.getOverview( empireId );
- }
-
-
@Override
public ListPlanetsResponse getPlanetList( int empireId )
{
diff --git a/legacyworlds-server-beans-technologies/pom.xml b/legacyworlds-server-beans-technologies/pom.xml
new file mode 100644
index 0000000..f3ddea4
--- /dev/null
+++ b/legacyworlds-server-beans-technologies/pom.xml
@@ -0,0 +1,26 @@
+
+ * This class implements database queries and calls to stored procedures which allow players to + * access and manage their empires' research. + * + * @author E. Benoît + */ +class PlayerTechnologiesDAOBean + implements PlayerTechnologiesDAO +{ + + /** SQL query that fetches an empire's research state */ + private static final String Q_EMPIRE_RESEARCH = "SELECT * FROM emp.technologies_v2_view WHERE empire_id = ?"; + + /** Row mapper for research state entries */ + private final ResearchRowMapper mResearch; + + /** Stored procedure that initiates a research priority update */ + private StoredProc fResprioUpdateStart; + + /** Stored procedure that uploads a single research priority */ + private StoredProc fResprioUpdateSet; + + /** Stored procedure that applies a research priority update */ + private StoredProc fResprioUpdateApply; + + /** Stored procedure that implements a technology */ + private StoredProc fTechnologyImplement; + + /** Spring JDBC interface */ + private JdbcTemplate dTemplate; + + + /** Initialise the necessary row mappers */ + public PlayerTechnologiesDAOBean( ) + { + this.mResearch = new ResearchRowMapper( ); + } + + + /** + * Dependency injector that sets the data source and initialises stored procedures + * + * @param dataSource + * the data source + */ + @Autowired( required = true ) + public void setDataSource( DataSource dataSource ) + { + this.dTemplate = new JdbcTemplate( dataSource ); + + this.fResprioUpdateStart = new StoredProc( dataSource , "emp" , "resprio_update_start" ) + .addParameter( "_empire" , Types.INTEGER ) + .addOutput( "_result" , Types.BOOLEAN ); + + this.fResprioUpdateSet = new StoredProc( dataSource , "emp" , "resprio_update_set" ) + .addParameter( "_technology" , Types.VARCHAR ) + .addParameter( "_priority" , Types.INTEGER ) + .addOutput( "_result" , Types.BOOLEAN ); + + this.fResprioUpdateApply = new StoredProc( dataSource , "emp" , "resprio_update_apply" ) + .addOutput( "_result" , Types.BOOLEAN ); + + this.fTechnologyImplement = new StoredProc( dataSource , "emp" , "technology_implement" ) + .addParameter( "_empire" , Types.INTEGER ) + .addParameter( "_technology" , Types.VARCHAR ) + .addOutput( "_result" , Types.BOOLEAN ); + } + + + /** + * Query and map research entries for an empire + * + *
+ * Run the query against emp.technologies_view
then map all rows using a
+ * {@link ResearchRowMapper}.
+ */
+ @Override
+ public List< ResearchData > getResearchData( int empire )
+ {
+ return this.dTemplate.query( Q_EMPIRE_RESEARCH , this.mResearch , empire );
+ }
+
+
+ /**
+ * Call emp.resprio_update_start
+ */
+ @Override
+ public boolean startPriorityUpdate( int empire )
+ {
+ return (Boolean) this.fResprioUpdateStart.execute( empire ).get( "_result" );
+ }
+
+
+ /**
+ * Call emp.resprio_update_set
+ */
+ @Override
+ public boolean uploadResearchPriority( String identifier , int priority )
+ {
+ return (Boolean) this.fResprioUpdateSet.execute( identifier , priority ).get( "_result" );
+ }
+
+
+ /**
+ * Call emp.resprio_update_apply
+ */
+ @Override
+ public boolean applyPriorityUpdate( )
+ {
+ return (Boolean) this.fResprioUpdateApply.execute( ).get( "_result" );
+ }
+
+
+ /**
+ * Call emp.technology_implement
+ */
+ @Override
+ public boolean implement( int empire , String technology )
+ {
+ return (Boolean) this.fTechnologyImplement.execute( empire , technology ).get( "_result" );
+ }
+
+}
diff --git a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java
new file mode 100644
index 0000000..c1c9388
--- /dev/null
+++ b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchControllerBean.java
@@ -0,0 +1,197 @@
+package com.deepclone.lw.beans.game.technologies;
+
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+import com.deepclone.lw.cmd.player.tech.GetResearchResponse;
+import com.deepclone.lw.interfaces.game.EmpireManagement;
+import com.deepclone.lw.interfaces.game.technologies.PlayerTechnologiesDAO;
+import com.deepclone.lw.interfaces.game.technologies.ResearchController;
+import com.deepclone.lw.interfaces.i18n.TranslationException;
+import com.deepclone.lw.interfaces.i18n.Translator;
+
+
+
+/**
+ * Research control component
+ *
+ *
+ * This component implements all actions allowing a player to view and update its empire's research + * state. + * + * @author E. Benoît + */ +@Transactional +class ResearchControllerBean + implements ResearchController +{ + /** Main empire management component */ + private EmpireManagement empireManagement; + + /** String translator */ + private Translator translator; + + /** Data access object for technologies */ + private PlayerTechnologiesDAO playerTechnologiesDAO; + + + /** + * Dependency injector that sets the empire management component + * + * @param empireManagement + * the empire management component + */ + @Autowired( required = true ) + public void setEmpireManagement( EmpireManagement empireManagement ) + { + this.empireManagement = empireManagement; + } + + + /** + * Dependency injector that sets the translation component + * + * @param translator + * the translation component + */ + @Autowired( required = true ) + public void setTranslator( Translator translator ) + { + this.translator = translator; + } + + + /** + * Dependency injector that sets the data access object for technologies + * + * @param playerTechnologiesDAO + * the data access object for technologies + */ + @Autowired( required = true ) + public void setPlayerTechnologiesDAO( PlayerTechnologiesDAO playerTechnologiesDAO ) + { + this.playerTechnologiesDAO = playerTechnologiesDAO; + } + + + /** + * Get the raw research data from the database, then handle translation and dependencies + * + *
+ * In order to generate the response, this method fetches the raw research state information + * from the database. It then proceeds with translating all categories, names and descriptions, + * then adds reverse dependency information to all loaded technologies. + */ + @Override + public GetResearchResponse getResearchData( int empire ) + { + GamePageData pageData = this.empireManagement.getGeneralInformation( empire ); + List< ResearchData > technologies = this.playerTechnologiesDAO.getResearchData( empire ); + + // Translate categories, names and descriptions + Map< String , String > translations = this.getTranslationsFor( technologies , pageData.getLanguage( ) ); + Map< String , ResearchData > byId = new HashMap< String , ResearchData >( ); + for ( ResearchData tech : technologies ) { + byId.put( tech.getIdentifier( ) , tech ); + tech.setCategory( translations.get( tech.getCategory( ) ) ); + if ( tech.getName( ) != null ) { + tech.setName( translations.get( tech.getName( ) ) ); + tech.setDescription( translations.get( tech.getDescription( ) ) ); + } + } + + // Add reverse dependency identifiers + for ( ResearchData tech : technologies ) { + for ( String dependency : tech.getDependencies( ) ) { + byId.get( dependency ).addReverseDependency( tech.getIdentifier( ) ); + } + } + + return new GetResearchResponse( pageData , technologies ); + } + + + /** + * Get translations used by a list of raw research entries + * + *
+ * This method is fed the raw research state data from the player technologies data access + * object. It generates a set of string identifiers which contains all categories, names and + * descriptions, then fetches these strings' translations. + * + * @param technologies + * the research state data from the DAO + * @param language + * the player's selected language + * + * @return the map of string identifiers to string contents + */ + private Map< String , String > getTranslationsFor( List< ResearchData > technologies , String language ) + { + Set< String > identifiers = new HashSet< String >( ); + for ( ResearchData tech : technologies ) { + identifiers.add( tech.getCategory( ) ); + if ( tech.getName( ) != null ) { + identifiers.add( tech.getName( ) ); + identifiers.add( tech.getDescription( ) ); + } + } + + try { + return this.translator.translate( language , identifiers ); + } catch ( TranslationException e ) { + throw new RuntimeException( "error while translating technology-related data" , e ); + } + } + + + /** + * Update research priorities + * + *
+ * This method will start a research priority update for the specified empire, then upload each + * value in the map to the database. Finally, it will try to apply the update. + * + *
+ * If any of the above steps fail, the method will abort. + */ + @Override + public boolean updatePriorities( int empire , Map< String , Integer > priorities ) + { + if ( !this.playerTechnologiesDAO.startPriorityUpdate( empire ) ) { + return false; + } + + for ( Map.Entry< String , Integer > entry : priorities.entrySet( ) ) { + if ( !this.playerTechnologiesDAO.uploadResearchPriority( entry.getKey( ) , entry.getValue( ) ) ) { + return false; + } + } + + return this.playerTechnologiesDAO.applyPriorityUpdate( ); + } + + + /** + * Implement a technology + * + *
+ * Call the DAO's technology implementation method in order to access the corresponding stored + * procedure. + */ + @Override + public boolean implement( int empire , String technology ) + { + return this.playerTechnologiesDAO.implement( empire , technology ); + } + +} diff --git a/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java new file mode 100644 index 0000000..e69c979 --- /dev/null +++ b/legacyworlds-server-beans-technologies/src/main/java/com/deepclone/lw/beans/game/technologies/ResearchRowMapper.java @@ -0,0 +1,69 @@ +package com.deepclone.lw.beans.game.technologies; + + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.postgresql.util.PGobject; + +import org.springframework.jdbc.core.RowMapper; + +import com.deepclone.lw.cmd.player.gdata.empire.ResearchData; + + + +/** + * Row mapper for empire research state entries + * + *
+ * This class is responsible for converting empire research state information rows into instances of
+ * the corresponding class, {@link ResearchData}. The instances it produces are "raw": they contain
+ * string identifiers instead of translations, and their reverse dependencies are not set.
+ *
+ * @author E. Benoît
+ *
+ */
+class ResearchRowMapper
+ implements RowMapper< ResearchData >
+{
+
+ /**
+ * Map a row from emp.technologies_view
+ *
+ *
+ * This method maps a single row from the emp.technologies_view
view into a
+ * {@link ResearchData} instance.
+ */
+ @Override
+ public ResearchData mapRow( ResultSet rs , int rowNum )
+ throws SQLException
+ {
+ ResearchData output = new ResearchData( );
+
+ output.setIdentifier( rs.getString( "emptech_id" ) );
+ output.setCategory( rs.getString( "technology_category" ) );
+
+ if ( rs.getBoolean( "emptech_visible" ) ) {
+ output.setName( rs.getString( "technology_name" ) );
+ output.setDescription( rs.getString( "technology_description" ) );
+ output.setPrice( rs.getLong( "technology_price" ) );
+ }
+
+ int ratio = rs.getInt( "emptech_ratio" );
+ if ( rs.wasNull( ) ) {
+ String state = ( (PGobject) rs.getObject( "emptech_state" ) ).getValue( );
+ output.setImplemented( "KNOWN".equals( state ) );
+ } else {
+ output.setCompletion( ratio );
+ output.setPriority( rs.getInt( "emptech_priority" ) );
+ }
+
+ String dependencies = rs.getString( "technology_dependencies" );
+ if ( ! "".equals( dependencies ) ) {
+ output.setDependencies( dependencies.split( "," ) );
+ }
+
+ return output;
+ }
+
+}
diff --git a/legacyworlds-server-beans-technologies/src/main/resources/.empty b/legacyworlds-server-beans-technologies/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-technologies/src/main/resources/configuration/game/technologies.xml b/legacyworlds-server-beans-technologies/src/main/resources/configuration/game/technologies.xml
new file mode 100644
index 0000000..e0b7bdc
--- /dev/null
+++ b/legacyworlds-server-beans-technologies/src/main/resources/configuration/game/technologies.xml
@@ -0,0 +1,9 @@
+
+
+ * This command delegate uses the {@link ResearchController} to obtain all information pertaining to + * an empire's research. + * + * @author E. Benoît + */ +class GetResearchCommandDelegateBean + implements AutowiredCommandDelegate +{ + /** The research controller */ + private ResearchController researchController; + + + /** + * Dependency injector that sets the research controller + * + * @param researchController + * the research controller + */ + @Autowired + public void setResearchController( ResearchController researchController ) + { + this.researchController = researchController; + } + + + /** This delegate handles {@link GetResearchCommand} instances */ + @Override + public Class< ? extends Command > getType( ) + { + return GetResearchCommand.class; + } + + + /** This delegate is part of the game session sub-type */ + @Override + public Class< ? extends SessionCommandHandler > getCommandHandler( ) + { + return GameSubTypeBean.class; + } + + + /** When the command is executed, fetch relevant data from the research controller */ + @Override + public CommandResponse execute( ServerSession session , Command command ) + { + int empire = session.get( "empireId" , Integer.class ); + return this.researchController.getResearchData( empire ); + } + +} diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/ImplementTechCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/ImplementTechCommandDelegateBean.java new file mode 100644 index 0000000..5b13636 --- /dev/null +++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/ImplementTechCommandDelegateBean.java @@ -0,0 +1,72 @@ +package com.deepclone.lw.beans.user.player.game.tech; + + +import org.springframework.beans.factory.annotation.Autowired; + +import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate; +import com.deepclone.lw.beans.user.abst.SessionCommandHandler; +import com.deepclone.lw.beans.user.player.GameSubTypeBean; +import com.deepclone.lw.cmd.player.tech.ImplementTechCommand; +import com.deepclone.lw.interfaces.game.technologies.ResearchController; +import com.deepclone.lw.interfaces.session.ServerSession; +import com.deepclone.lw.session.Command; +import com.deepclone.lw.session.CommandResponse; +import com.deepclone.lw.session.NullResponse; + + + +public class ImplementTechCommandDelegateBean + implements AutowiredCommandDelegate + +{ + /** The research controller */ + private ResearchController researchController; + + + /** + * Dependency injector that sets the research controller + * + * @param researchController + * the research controller + */ + @Autowired( required = true ) + public void setResearchController( ResearchController researchController ) + { + this.researchController = researchController; + } + + + /** This class handles {@link ImplementTechCommand} instances */ + @Override + public Class< ? extends Command > getType( ) + { + return ImplementTechCommand.class; + } + + + /** This class is enabled for the {@link GameSubTypeBean} session type */ + @Override + public Class< ? extends SessionCommandHandler > getCommandHandler( ) + { + return GameSubTypeBean.class; + } + + + /** + * Implement a technology + * + *
+ * If the empire is not in vacation mode, try to implement the technology. Always return a + * {@link NullResponse}. + */ + @Override + public CommandResponse execute( ServerSession session , Command cParam ) + { + ImplementTechCommand command = (ImplementTechCommand) cParam; + int empire = session.get( "empireId" , Integer.class ); + if ( !session.get( "vacation" , Boolean.class ) ) { + this.researchController.implement( empire , command.getTech( ) ); + } + return new NullResponse( ); + } +} diff --git a/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/UpdateResearchPrioritiesCommandDelegateBean.java b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/UpdateResearchPrioritiesCommandDelegateBean.java new file mode 100644 index 0000000..c3dc103 --- /dev/null +++ b/legacyworlds-server-beans-user/src/main/java/com/deepclone/lw/beans/user/player/game/tech/UpdateResearchPrioritiesCommandDelegateBean.java @@ -0,0 +1,77 @@ +package com.deepclone.lw.beans.user.player.game.tech; + + +import org.springframework.beans.factory.annotation.Autowired; + +import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate; +import com.deepclone.lw.beans.user.abst.SessionCommandHandler; +import com.deepclone.lw.beans.user.player.GameSubTypeBean; +import com.deepclone.lw.cmd.player.tech.UpdateResearchPrioritiesCommand; +import com.deepclone.lw.interfaces.game.technologies.ResearchController; +import com.deepclone.lw.interfaces.session.ServerSession; +import com.deepclone.lw.session.Command; +import com.deepclone.lw.session.CommandResponse; +import com.deepclone.lw.session.NullResponse; + + + +/** + * Command handler for research priority updates + * + * @author E. Benoît + */ +class UpdateResearchPrioritiesCommandDelegateBean + implements AutowiredCommandDelegate +{ + /** The research controller */ + private ResearchController researchController; + + + /** + * Dependency injector that sets the research controller + * + * @param researchController + * the research controller + */ + @Autowired( required = true ) + public void setResearchController( ResearchController researchController ) + { + this.researchController = researchController; + } + + + /** This class handles {@link UpdateResearchPrioritiesCommand} instances */ + @Override + public Class< ? extends Command > getType( ) + { + return UpdateResearchPrioritiesCommand.class; + } + + + /** This class is enabled for the {@link GameSubTypeBean} session type */ + @Override + public Class< ? extends SessionCommandHandler > getCommandHandler( ) + { + return GameSubTypeBean.class; + } + + + /** + * Update research priorities + * + *
+ * If the empire is not in vacation mode, access the research controller and update mining
+ * priorities using the specified values. Return a {@link NullResponse} in all cases.
+ */
+ @Override
+ public CommandResponse execute( ServerSession session , Command command )
+ {
+ if ( !session.get( "vacation" , Boolean.class ) ) {
+ int empireId = session.get( "empireId" , Integer.class );
+ this.researchController.updatePriorities( empireId ,
+ ( (UpdateResearchPrioritiesCommand) command ).getPriorities( ) );
+ }
+ return new NullResponse( );
+ }
+
+}
diff --git a/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
index ff9a382..8398256 100644
--- a/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
+++ b/legacyworlds-server-beans-user/src/main/resources/configuration/session-types/player.xml
@@ -44,9 +44,13 @@
+ * Query the database for technology entries as seen from an empire's perspective. The + * technologies will be returned with I18N string identifiers for categories, names and + * descriptions (unless the details are not available). + * + * @param empire + * the empire's identifier + * + * @return the list of technology entries + */ + public List< ResearchData > getResearchData( int empire ); + + + /** + * Start a research priority update + * + *
+ * This method initialises a research priorities update by calling the appropriate stored
+ * procedure.
+ *
+ * @param empire
+ * the empire for which an update is being performed
+ *
+ * @return true
on success, false
on failure
+ */
+ public boolean startPriorityUpdate( int empire );
+
+
+ /**
+ * Upload a single research priority
+ *
+ *
+ * This method uploads the priority for one of the in-progress research items. It must be called
+ * only after a research priority update has been initiated.
+ *
+ * @param identifier
+ * the technology's identifier
+ * @param priority
+ * the research priority
+ *
+ * @return true
on success, false
on failure
+ */
+ public boolean uploadResearchPriority( String identifier , int priority );
+
+
+ /**
+ * Apply an in-progress research priority update
+ *
+ *
+ * This method applies a previously uploaded set of research priority updates.
+ *
+ * @return true
on success, false
on failure
+ */
+ public boolean applyPriorityUpdate( );
+
+
+ /**
+ * Implement a technology
+ *
+ *
+ * This method calls the stored procedure which causes an empire to implement a technology.
+ *
+ * @param empire
+ * the empire's identifier
+ * @param technology
+ * the technology's identifier
+ *
+ * @return true
on success, false
on failure
+ */
+ public boolean implement( int empire , String technology );
+
+}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/ResearchController.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/ResearchController.java
new file mode 100644
index 0000000..e32d840
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/technologies/ResearchController.java
@@ -0,0 +1,69 @@
+package com.deepclone.lw.interfaces.game.technologies;
+
+
+import java.util.Map;
+
+import com.deepclone.lw.cmd.player.tech.GetResearchCommand;
+import com.deepclone.lw.cmd.player.tech.GetResearchResponse;
+
+
+
+/**
+ * Research control interface
+ *
+ *
+ * This interface is implemented by the component which allows empires (and therefore players) to + * view the research state and control it. + * + * @author E. Benoît + */ +public interface ResearchController +{ + + /** + * Obtain the state of an empire's research + * + *
+ * This method must be overridden to generate the response to a {@link GetResearchCommand}. + * + * @param empire + * the empire's identifier + * + * @return the appropriate {@link GetResearchResponse} instance + */ + public GetResearchResponse getResearchData( int empire ); + + + /** + * Update research priorities + * + *
+ * This method must be implemented so it updates research priorities for an empire.
+ *
+ * @param empire
+ * the empire to update
+ * @param priorities
+ * the new research priorities, as a map associating technology identifiers to
+ * priority values
+ *
+ * @return true
on success, false
otherwise
+ */
+ public boolean updatePriorities( int empire , Map< String , Integer > priorities );
+
+
+ /**
+ * Implement a technology
+ *
+ *
+ * This method implements a technology which has been fully researched.
+ *
+ * @param empire
+ * the empire trying to implement a technology
+ * @param technology
+ * the technology's identifier
+ *
+ * @return
+ * This class represents entries from the research page. It is capable of representing in-progress
+ * research (with or without details), as well as pending and implemented technologies.
+ *
+ * @author E. Benoît
+ */
+public class ResearchData
+ implements Serializable , Comparable< ResearchData >
+{
+
+ /**
+ * The serialisation version identifier
+ *
+ *
+ * This method compares research entries based on:
+ *
+ * Copy and sort the list of research entries
+ *
+ * @param page
+ * the common page information
+ * @param technologies
+ * the list of research entries
+ */
+ public GetResearchResponse( GamePageData page , List< ResearchData > technologies )
+ {
+ super( page );
+ this.researchData = new LinkedList< ResearchData >( technologies );
+ Collections.sort( this.researchData );
+ }
+
+
+ /**
+ * Get the list of research entries
+ *
+ * @return the sorted list of research entries
+ */
+ public List< ResearchData > getResearchData( )
+ {
+ return this.researchData;
+ }
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/ImplementTechCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/ImplementTechCommand.java
new file mode 100644
index 0000000..b3dbc93
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/ImplementTechCommand.java
@@ -0,0 +1,53 @@
+package com.deepclone.lw.cmd.player.tech;
+
+
+import com.deepclone.lw.session.Command;
+
+
+
+/**
+ * Technology implementation command
+ *
+ * @author E. Benoît
+ */
+public class ImplementTechCommand
+ extends Command
+{
+
+ /**
+ * The serialisation version identifier
+ *
+ *
- * This method is mapped to the technology implementation command URL.
+ * This method is called when a command to update empire-wide mining settings is received.
*
* @param request
* the HTTP request
- * @param language
- * the language from the session
- * @param model
- * the model
- * @param tech
- * the technology identifier
*
- * @return the overview page rendering order
+ * @return a redirection to the overview page's "economy" tab
*
* @throws SessionException
* if some error occurs on the server
@@ -96,26 +89,8 @@ public class OverviewPage
* @throws SessionMaintenanceException
* if the game is under maintenance
*/
- @RequestMapping( value = "/implement-{tech}.action" , method = RequestMethod.POST )
- public String implement( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model ,
- @PathVariable String tech )
- throws SessionException , SessionServerException , SessionMaintenanceException
- {
- int techId;
- try {
- techId = Integer.parseInt( tech );
- } catch ( NumberFormatException e ) {
- return this.redirect( "overview" );
- }
-
- PlayerSession pSession = this.getSession( PlayerSession.class , request );
- return this.render( model , "game" , language , "overview" , pSession.implementTechnology( techId ) );
- }
-
-
@RequestMapping( value = "/update-mining-settings.action" , method = RequestMethod.POST )
- public String updateMiningSettings( HttpServletRequest request , @ModelAttribute( "language" ) String language ,
- Model model )
+ public String updateMiningSettings( HttpServletRequest request )
throws SessionException , SessionServerException , SessionMaintenanceException
{
Map< String , Integer > miningSettings = this.getMiningSettings( request );
diff --git a/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ResearchPage.java b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ResearchPage.java
new file mode 100644
index 0000000..79c9beb
--- /dev/null
+++ b/legacyworlds-web-main/src/main/java/com/deepclone/lw/web/main/game/ResearchPage.java
@@ -0,0 +1,186 @@
+package com.deepclone.lw.web.main.game;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.SessionAttributes;
+
+import com.deepclone.lw.session.SessionException;
+import com.deepclone.lw.web.beans.intercept.SessionRequirement;
+import com.deepclone.lw.web.beans.session.SessionMaintenanceException;
+import com.deepclone.lw.web.beans.session.SessionServerException;
+import com.deepclone.lw.web.beans.view.PageControllerBase;
+import com.deepclone.lw.web.csess.PlayerSession;
+
+
+
+/**
+ * Controller for the Research page
+ *
+ *
+ * This controller contains all URL handlers associated with the research page: viewing research
+ * state, setting research priorities, and implementing technologies.
+ *
+ * @author E. Benoît
+ */
+@Controller
+@SessionRequirement( value = true , redirectTo = "player-session" , subType = "game" )
+@SessionAttributes( "language" )
+public class ResearchPage
+ extends PageControllerBase
+{
+
+ /**
+ * Research page view
+ *
+ *
+ * This method fetches all research information from the game server then requests the page
+ * rendering.
+ *
+ * @param request
+ * the HTTP request
+ * @param language
+ * the language from the session
+ * @param model
+ * the model
+ *
+ * @return the research page rendering information
+ *
+ * @throws SessionException
+ * if some error occurs on the server
+ * @throws SessionServerException
+ * if the server is unreachable
+ * @throws SessionMaintenanceException
+ * if the game is under maintenance
+ */
+ @RequestMapping( "/research" )
+ public String view( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model )
+ throws SessionException , SessionServerException , SessionMaintenanceException
+ {
+ PlayerSession pSession = this.getSession( PlayerSession.class , request );
+ return this.render( model , "game" , language , "research" , pSession.getResearch( ) );
+ }
+
+
+ /**
+ * Research priorities update command
+ *
+ *
+ * This method handles the research priorities update command, which is triggered when the
+ * "Update" button is clicked on the "In progress" tab of the research page.
+ *
+ * @param request
+ * the HTTP request
+ *
+ * @return a redirection to the research page's "in progress" tab
+ *
+ * @throws SessionException
+ * if some error occurs on the server
+ * @throws SessionServerException
+ * if the server is unreachable
+ * @throws SessionMaintenanceException
+ * if the game is under maintenance
+ */
+ @RequestMapping( value = "/research-set-priorities.action" , method = RequestMethod.POST )
+ public String setPriorities( HttpServletRequest request )
+ throws SessionException , SessionServerException , SessionMaintenanceException
+ {
+ Map< String , Integer > priorities = this.getPriorities( request );
+ if ( priorities != null ) {
+ this.getSession( PlayerSession.class , request ).updateResearchPriorities( priorities );
+ }
+ return this.redirect( "research#research" );
+ }
+
+
+ /**
+ * Extract research priorities from the HTTP request
+ *
+ *
+ * Look for all submitted fields that begin with "rp-" then try to extract their values into a
+ * map that associates technology identifiers to priorities.
+ *
+ * @param request
+ * the HTTP request
+ *
+ * @return the map containing the submitted priorities, or
+ * This method handles the technology implementation command, which is triggered when the
+ * "Implement" button is clicked on the "Pending" tab of the research page.
+ *
+ * @param request
+ * the HTTP request
+ * @param tech
+ * the identifier of the technology
+ *
+ * @return a redirection to the research page's "Implemented" tab
+ *
+ * @throws SessionException
+ * if some error occurs on the server
+ * @throws SessionServerException
+ * if the server is unreachable
+ * @throws SessionMaintenanceException
+ * if the game is under maintenance
+ */
+ @RequestMapping( value = "/research-implement.action" , method = RequestMethod.POST )
+ public String implement( HttpServletRequest request , @RequestParam( "technology" ) String technology )
+ throws SessionException , SessionServerException , SessionMaintenanceException
+ {
+ this.getSession( PlayerSession.class , request ).implementTechnology( technology );
+ return this.redirect( "research#implemented" );
+ }
+
+}
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index bd3479b..606ac67 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -168,6 +168,11 @@
true
on success, false
otherwise
+ */
+ public boolean implement( int empire , String technology );
+
+}
diff --git a/legacyworlds-server-main/pom.xml b/legacyworlds-server-main/pom.xml
index 1e4af1b..86b8a84 100644
--- a/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server-main/pom.xml
@@ -50,6 +50,10 @@
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /** The identifier of the technology */
+ private String identifier;
+
+ /** The category the technology belongs to */
+ private String category;
+
+ /**
+ * The translated name of the technology, or null
if the technology is being
+ * researched and progress is insufficient to display details.
+ */
+ private String name;
+
+ /**
+ * The description of the technology, or null
if the technology is being researched
+ * and progress is insufficient to display details.
+ */
+ private String description;
+
+ /**
+ * The implementation price of the technology, or null
if the technology is being
+ * researched and progress is insufficient to display details.
+ */
+ private Long price;
+
+ /**
+ * The completion percentage of the technology, or null
if the technology has been
+ * fully researched.
+ */
+ private Integer completion;
+
+ /**
+ * Priority of the research on this technology, or null
if the technology is not
+ * being researched.
+ */
+ private Integer priority;
+
+ /**
+ * Whether the technology is being researched (null
), pending implementation (
+ * false
) or implemented (true
).
+ */
+ private Boolean implemented;
+
+ /** List of identifiers of technologies the current technology depends on. */
+ private List< String > dependencies = new ArrayList< String >( );
+
+ /** List of identifiers of technologies that depend on the current technology. */
+ private List< String > revDependencies = new LinkedList< String >( );
+
+
+ /**
+ * Gets the identifier of the technology.
+ *
+ * @return the identifier of the technology
+ */
+ public String getIdentifier( )
+ {
+ return this.identifier;
+ }
+
+
+ /**
+ * Sets the identifier of the technology.
+ *
+ * @param identifier
+ * the new identifier of the technology
+ */
+ public void setIdentifier( String identifier )
+ {
+ this.identifier = identifier;
+ }
+
+
+ /**
+ * Gets the category the technology belongs to.
+ *
+ * @return the category the technology belongs to
+ */
+ public String getCategory( )
+ {
+ return this.category;
+ }
+
+
+ /**
+ * Sets the category the technology belongs to.
+ *
+ * @param category
+ * the new category the technology belongs to
+ */
+ public void setCategory( String category )
+ {
+ this.category = category;
+ }
+
+
+ /**
+ * Gets the translated name of the technology
+ *
+ * @return the translated name of the technology, or null
if the technology is
+ * being researched and progress is insufficient to display details
+ */
+ public String getName( )
+ {
+ return this.name;
+ }
+
+
+ /**
+ * Sets the translated name of the technology
+ *
+ * @param name
+ * the new translated name of the technology
+ */
+ public void setName( String name )
+ {
+ this.name = name;
+ }
+
+
+ /**
+ * Gets the description of the technology
+ *
+ * @return the description of the technology, or null
if the technology is being
+ * researched and progress is insufficient to display details
+ */
+ public String getDescription( )
+ {
+ return this.description;
+ }
+
+
+ /**
+ * Sets the description of the technology
+ *
+ * @param description
+ * the new description of the technology
+ */
+ public void setDescription( String description )
+ {
+ this.description = description;
+ }
+
+
+ /**
+ * Gets the implementation price of the technology
+ *
+ * @return the implementation price of the technology, or null
if the technology is
+ * being researched and progress is insufficient to display details
+ */
+ public Long getPrice( )
+ {
+ return this.price;
+ }
+
+
+ /**
+ * Sets the implementation price of the technology
+ *
+ * @param price
+ * the new implementation price of the technology
+ */
+ public void setPrice( Long price )
+ {
+ this.price = price;
+ }
+
+
+ /**
+ * Gets the completion percentage of the technology.
+ *
+ * @return the completion percentage of the technology, or null
if the technology
+ * has been fully researched
+ */
+ public Integer getCompletion( )
+ {
+ return this.completion;
+ }
+
+
+ /**
+ * Sets the completion percentage of the technology.
+ *
+ * @param completion
+ * the new completion percentage of the technology
+ */
+ public void setCompletion( Integer completion )
+ {
+ this.completion = completion;
+ }
+
+
+ /**
+ * Gets the priority of the research on this technology
+ *
+ * @return the priority of the research on this technology, or null
if the
+ * technology is not being researched
+ */
+ public Integer getPriority( )
+ {
+ return this.priority;
+ }
+
+
+ /**
+ * Sets the priority of the research on this technology.
+ *
+ * @param priority
+ * the new priority of the research on this technology
+ */
+ public void setPriority( Integer priority )
+ {
+ this.priority = priority;
+ }
+
+
+ /**
+ * Gets whether the technology is being researched (null
), pending implementation (
+ * false
) or implemented (true
).
+ *
+ * @return whether the technology is being researched (null
), pending
+ * implementation ( false
) or implemented (true
)
+ */
+ public Boolean getImplemented( )
+ {
+ return this.implemented;
+ }
+
+
+ /**
+ * Sets whether the technology is being researched (null
), pending implementation (
+ * false
) or implemented (true
).
+ *
+ * @param implemented
+ * whether the technology is being researched (null
), pending
+ * implementation ( false
) or implemented (true
)
+ */
+ public void setImplemented( Boolean implemented )
+ {
+ this.implemented = implemented;
+ }
+
+
+ /**
+ * Get the list of dependencies
+ *
+ * @return the list of dependencies
+ */
+ public List< String > getDependencies( )
+ {
+ return Collections.unmodifiableList( this.dependencies );
+ }
+
+
+ /**
+ * Update the list of dependencies
+ *
+ * @param dependencies
+ * the new list of dependencies
+ */
+ public void setDependencies( String[] dependencies )
+ {
+ this.dependencies = Arrays.asList( dependencies );
+ }
+
+
+ /**
+ * Get the list of reverse dependencies
+ *
+ * @return the list of reverse dependencies
+ */
+ public List< String > getReverseDependencies( )
+ {
+ return this.revDependencies;
+ }
+
+
+ /**
+ * Add a reverse dependency
+ *
+ * @param identifier
+ * the identifier of the reverse dependency
+ */
+ public void addReverseDependency( String identifier )
+ {
+ this.revDependencies.add( identifier );
+ }
+
+
+ /**
+ * Research page entry comparison
+ *
+ *
+ *
+ *
+ * @param other
+ * the entry to compare to
+ *
+ * @return 1 if the current entry is "bigger", -1 if it is "smaller", and 0 if both entries are
+ * equal
+ */
+ @Override
+ public int compareTo( ResearchData other )
+ {
+ if ( other == null ) {
+ return 1;
+ }
+
+ if ( this.implemented == null && other.implemented != null ) {
+ return -1;
+ }
+ if ( other.implemented == null && this.implemented != null ) {
+ return 1;
+ }
+
+ if ( this.implemented != null ) {
+ if ( this.implemented && !other.implemented ) {
+ return 1;
+ }
+ if ( other.implemented && !this.implemented ) {
+ return -1;
+ }
+ return this.name.compareTo( other.name );
+ }
+
+ if ( this.completion != other.completion ) {
+ return other.completion - this.completion;
+ }
+ if ( this.name == null ) {
+ return other.name == null ? 0 : -1;
+ }
+ return this.name.compareTo( other.name );
+ }
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchCommand.java
new file mode 100644
index 0000000..f5961c3
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchCommand.java
@@ -0,0 +1,26 @@
+package com.deepclone.lw.cmd.player.tech;
+
+
+import com.deepclone.lw.session.Command;
+
+
+
+/**
+ * Command that obtains the current research & technology data
+ *
+ * @author E. Benoît
+ */
+public class GetResearchCommand
+ extends Command
+{
+
+ /**
+ * The serialisation version identifier
+ *
+ *
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchResponse.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchResponse.java
new file mode 100644
index 0000000..22fd824
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/GetResearchResponse.java
@@ -0,0 +1,66 @@
+package com.deepclone.lw.cmd.player.tech;
+
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.deepclone.lw.cmd.player.gdata.GamePageData;
+import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
+import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
+
+
+
+/**
+ * Response that lists all research entries (sent by the server in response to
+ * {@link GetResearchCommand})
+ *
+ * @author E. Benoît
+ */
+public class GetResearchResponse
+ extends GameResponseBase
+{
+
+ /**
+ * The serialisation version identifier
+ *
+ *
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /** Sorted list of research entries */
+ private final List< ResearchData > researchData;
+
+
+ /**
+ * Initialise the response
+ *
+ *
+ *
+ */
+ private static final long serialVersionUID = 2L;
+
+ /** Identifier of the technology to implement */
+ private final String tech;
+
+
+ /**
+ * Initialise the command
+ *
+ * @param tech
+ * the identifier of the technology to implement
+ */
+ public ImplementTechCommand( String tech )
+ {
+ this.tech = tech;
+ }
+
+
+ /**
+ * Get the identifier of the technology to implement
+ *
+ * @return the identifier of the technology to implement
+ */
+ public String getTech( )
+ {
+ return this.tech;
+ }
+
+}
diff --git a/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/UpdateResearchPrioritiesCommand.java b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/UpdateResearchPrioritiesCommand.java
new file mode 100644
index 0000000..4dd5b08
--- /dev/null
+++ b/legacyworlds-session/src/main/java/com/deepclone/lw/cmd/player/tech/UpdateResearchPrioritiesCommand.java
@@ -0,0 +1,50 @@
+package com.deepclone.lw.cmd.player.tech;
+
+
+import java.util.Map;
+
+import com.deepclone.lw.session.Command;
+
+
+
+/**
+ * Command that updates research priorities
+ *
+ * @author E. Benoît
+ */
+public class UpdateResearchPrioritiesCommand
+ extends Command
+{
+
+ /**
+ * Serialisation version identifier
+ *
+ *
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /** The new research priorities */
+ private final Map< String , Integer > priorities;
+
+
+ /**
+ * Initialise the command using research priority values
+ *
+ * @param priorities
+ * a map that associates technology identifiers to priorities
+ */
+ public UpdateResearchPrioritiesCommand( Map< String , Integer > priorities )
+ {
+ this.priorities = priorities;
+ }
+
+
+ /** @return the new research priorities */
+ public Map< String , Integer > getPriorities( )
+ {
+ return this.priorities;
+ }
+
+}
diff --git a/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
index 6d132af..f47b989 100644
--- a/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
+++ b/legacyworlds-web-beans/src/main/java/com/deepclone/lw/web/csess/PlayerSession.java
@@ -17,6 +17,10 @@ import com.deepclone.lw.cmd.player.elist.*;
import com.deepclone.lw.cmd.player.fleets.*;
import com.deepclone.lw.cmd.player.gdata.*;
import com.deepclone.lw.cmd.player.planets.*;
+import com.deepclone.lw.cmd.player.tech.GetResearchCommand;
+import com.deepclone.lw.cmd.player.tech.GetResearchResponse;
+import com.deepclone.lw.cmd.player.tech.ImplementTechCommand;
+import com.deepclone.lw.cmd.player.tech.UpdateResearchPrioritiesCommand;
import com.deepclone.lw.cmd.player.msgs.*;
import com.deepclone.lw.session.Command;
import com.deepclone.lw.session.SessionException;
@@ -78,10 +82,41 @@ public class PlayerSession
}
- public EmpireResponse implementTechnology( int technology )
+ /**
+ * Request research information
+ *
+ * @return the research information response
+ */
+ public GetResearchResponse getResearch( )
throws SessionException , SessionServerException , SessionMaintenanceException
{
- return (EmpireResponse) this.execute( new ImplementTechCommand( technology ) );
+ return (GetResearchResponse) this.execute( new GetResearchCommand( ) );
+ }
+
+
+ /**
+ * Update research priorities
+ *
+ * @param priorities
+ * the map of research identifiers to priority values
+ */
+ public void updateResearchPriorities( Map< String , Integer > priorities )
+ throws SessionException , SessionServerException , SessionMaintenanceException
+ {
+ this.execute( new UpdateResearchPrioritiesCommand( priorities ) );
+ }
+
+
+ /**
+ * Implement a technology
+ *
+ * @param technology
+ * the technology's identifier
+ */
+ public void implementTechnology( String technology )
+ throws SessionException , SessionServerException , SessionMaintenanceException
+ {
+ this.execute( new ImplementTechCommand( technology ) );
}
@@ -618,4 +653,5 @@ public class PlayerSession
{
return (PostCommentResponse) this.execute( new PostCommentCommand( bugId , comment ) );
}
+
}
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
index e74fbd5..ce53339 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/containers/game.ftl
@@ -63,6 +63,9 @@
+
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
index 4eae28b..b0a711d 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/overview.ftl
@@ -2,7 +2,6 @@
<@page title="Empire">
<#assign ov = data.overview >
- <#assign rs = data.research >
<@tabs>
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl
new file mode 100644
index 0000000..7e84965
--- /dev/null
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/en/types/research.ftl
@@ -0,0 +1,255 @@
+<#--
+ Macro that starts a research tab for one of the 3 existing modes
+
+ Parameters:
+ curMode The tab to start: R for "in progress", P for pending, K for implemented
+ -->
+<#macro startResearchTab curMode>
+ <#switch curMode>
+
+ <#case 'R'>
+ <@tabStart "research" "In progress" />
+ <@rawFormStart "research-set-priorities" "form-priorities" "research" />
+ <@listviewStart />
+ <@lv_line headers=true>
+ <@lv_column width=150>Category@lv_column>
+ <@lv_column width="x">Name@lv_column>
+ <@lv_column width=100 centered=true>Progress@lv_column>
+ <@lv_column width=150 centered=true>Priority@lv_column>
+ @lv_line>
+ <#break>
+
+ <#case 'P'>
+ <@tabStart "pending" "Pending implementation" />
+ <@listviewStart />
+ <@lv_line headers=true>
+ <@lv_column width=150>Category@lv_column>
+ <@lv_column width="x">Name@lv_column>
+ <@lv_column width=150 right=true>Cost@lv_column>
+ <@lv_column width=100 centered=true> @lv_column>
+ @lv_line>
+ <#break>
+
+ <#case 'K'>
+ <@tabStart "implemented" "Implemented" />
+ <@listviewStart />
+ <@lv_line headers=true>
+ <@lv_column width=150>Category@lv_column>
+ <@lv_column width="x">Name@lv_column>
+ @lv_line>
+ <#break>
+
+ #switch>
+#macro>
+<#--
+ Macro that finishes a research tab depending on its type
+
+ Parameters:
+ mode The type of tab that is being closed
+ -->
+<#macro endResearchTab mode>
+ <#switch mode>
+
+ <#case 'R'>
+ <@lv_line headers=true>
+ <@lv_column width="x" colspan=4> @lv_column>
+ @lv_line>
+ <@lv_line>
+ <@lv_column colspan=3> @lv_column>
+ <@lv_column centered=true>
+
+ @lv_column>
+ @lv_line>
+ <@listviewEnd />
+
+ <#break>
+
+ <#case 'P'>
+ <#case 'K'>
+ <@listviewEnd />
+ <#break>
+
+ #switch>
+ <@tabEnd />
+#macro>
+<#--
+ Macro that renders an in-progress research entry
+
+ Parameters:
+ entry the entry
+-->
+<#macro drawInProgressResearch entry>
+ <@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+ <@lv_column>${entry.category?xhtml}@lv_column>
+ <@lv_column>
+ <#if entry.name?has_content>
+ ${entry.name?xhtml}
+ <#else>
+ Unknown technology
+ #if>
+ @lv_column>
+ <@lv_column centered=true>${entry.completion} %@lv_column>
+ <@lv_column centered=true>
+
+ @lv_column>
+ @lv_line>
+#macro>
+<#--
+ Macro that renders a pending technology
+
+ Parameters:
+ entry the technology
+-->
+<#macro drawPendingTechnology entry>
+ <@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+ <@lv_column>${entry.category?xhtml}@lv_column>
+ <@lv_column>
+ ${entry.name?xhtml}
+ @lv_column>
+ <@lv_column right=true>
+ ${entry.price?string(",##0")} <@abbr_bgc/>
+ @lv_column>
+ <@lv_column right=true>
+ <#if data.page.cash >= entry.price>
+ <@rawFormStart "research-implement" "form-implement-${entry.identifier}" entry.identifier />
+
+
+
+ <#else>
+
+ #if>
+ @lv_column>
+ @lv_line>
+#macro>
+<#--
+ Macro that renders an implemented technology
+
+ Parameters:
+ entry the technology
+-->
+<#macro drawImplementedTechnology entry>
+ <@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
+ <@lv_column>${entry.category?xhtml}@lv_column>
+ <@lv_column>
+ ${entry.name?xhtml}
+ @lv_column>
+ @lv_line>
+#macro>
+<#--
+ Macro that renders a single entry
+
+ Parameters:
+ mode the current tab (see startResearchTab for details)
+ entry the entry
+-->
+<#macro drawResearchEntry mode entry>
+ <#switch mode>
+
+ <#case 'R'>
+ <@drawInProgressResearch entry />
+ <#break>
+
+ <#case 'P'>
+ <@drawPendingTechnology entry />
+ <#break>
+
+ <#case 'K'>
+ <@drawImplementedTechnology entry />
+ <#break>
+
+ #switch>
+#macro>
+<#--
+ Render the visible part of the research page
+
+ Parameters:
+ entries the sorted list of research entries
+ -->
+<#macro renderResearchTabs entries>
+ <#local prevMode = ''>
+
+ <@tabs>
+ <#list entries as entry>
+
+ <#-- Determine type of entry to display -->
+ <#if entry.implemented?has_content>
+ <#if entry.implemented>
+ <#local curMode = 'K'>
+ <#else>
+ <#local curMode = 'P'>
+ #if>
+ <#else>
+ <#local curMode = 'R'>
+ #if>
+
+ <#-- Start/end tabs -->
+ <#if curMode != prevMode>
+ <#if prevMode != ''>
+ <@endResearchTab prevMode />
+ #if>
+ <@startResearchTab curMode />
+ <#local prevMode = curMode>
+ #if>
+
+
+ <@drawResearchEntry curMode entry />
+
+ #list>
+ <@endResearchTab prevMode />
+ @tabs>
+#macro>
+<#--
+ Render all research information in a hidden layer which will be used by
+ the client-side code to display more information.
+ -->
+<#macro renderFullResearchData entries>
+
+
+#macro>
+<#--
+ Main research page
+ -->
+<#macro render>
+<@page title="Research">
+
+ <#local entries = data.researchData>
+ <@renderResearchTabs entries />
+ <@renderFullResearchData entries />
+
+@page>
+#macro>
\ No newline at end of file
diff --git a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
index c82690e..3568606 100644
--- a/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
+++ b/legacyworlds-web-main/Content/Raw/WEB-INF/fm/fr/containers/game.ftl
@@ -63,11 +63,14 @@
+
#macro>
-<#macro tab id title>
+<#macro tabStart id title>
${title?xhtml}
null
if one of the
+ * values was incorrect.
+ */
+ private Map< String , Integer > getPriorities( HttpServletRequest request )
+ {
+ Map< String , Object > input = this.getInput( request );
+ Map< String , Integer > priorities = new HashMap< String , Integer >( );
+ for ( Entry< String , Object > entry : input.entrySet( ) ) {
+ // Ignore items which are not priorities
+ String name = entry.getKey( );
+ if ( !name.startsWith( "rp-" ) ) {
+ continue;
+ }
+ name = name.substring( 3 );
+
+ // Get values
+ if ( ! ( entry.getValue( ) instanceof String[] ) ) {
+ continue;
+ }
+ String[] values = (String[]) entry.getValue( );
+ if ( values.length < 1 ) {
+ continue;
+ }
+
+ // Pre-validate them
+ int value;
+ try {
+ value = Integer.parseInt( values[ 0 ] );
+ } catch ( NumberFormatException e ) {
+ value = -1;
+ }
+ if ( value < 0 || value > 4 ) {
+ return null;
+ }
+
+ priorities.put( name , value );
+ }
+ return priorities;
+ }
+
+
+ /**
+ * Technology implementation command
+ *
+ *