New research and technology page

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

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

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

* Added controller, templates and JavaScript for research page
This commit is contained in:
Emmanuel BENOîT 2012-04-07 13:06:03 +02:00
parent 154f215e24
commit 6dcd59d7bc
45 changed files with 2314 additions and 178 deletions

View file

@ -27,7 +27,6 @@ public class EmpireDAOBean
implements EmpireDAO implements EmpireDAO
{ {
private JdbcTemplate dTemplate; private JdbcTemplate dTemplate;
private StoredProc fImplementTech;
private StoredProc fAddEmpEnemy; private StoredProc fAddEmpEnemy;
private StoredProc fAddAllEnemy; private StoredProc fAddAllEnemy;
private StoredProc fRemoveEmpEnemy; private StoredProc fRemoveEmpEnemy;
@ -42,10 +41,6 @@ public class EmpireDAOBean
{ {
this.dTemplate = new JdbcTemplate( dataSource ); 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 = new StoredProc( dataSource , "emp" , "add_enemy_empire" );
this.fAddEmpEnemy.addParameter( "empire_id" , Types.INTEGER ); this.fAddEmpEnemy.addParameter( "empire_id" , Types.INTEGER );
this.fAddEmpEnemy.addParameter( "enemy_name" , Types.VARCHAR ); 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 @Override
public List< PlanetListData > getPlanetList( int empireId ) public List< PlanetListData > getPlanetList( int empireId )
{ {

View file

@ -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 @Override
public ListPlanetsResponse getPlanetList( int empireId ) public ListPlanetsResponse getPlanetList( int empireId )
{ {

View file

@ -0,0 +1,26 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>legacyworlds-server-beans</artifactId>
<groupId>com.deepclone.lw</groupId>
<version>1.0.0</version>
<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
</parent>
<artifactId>legacyworlds-server-beans-technologies</artifactId>
<name>Legacy Worlds - Server - Components - Technologies</name>
<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
<description>This module contains the components which manage and access in-universe technologies.</description>
<dependencies>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,143 @@
package com.deepclone.lw.beans.game.technologies;
import java.sql.Types;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
import com.deepclone.lw.interfaces.game.technologies.PlayerTechnologiesDAO;
import com.deepclone.lw.utils.StoredProc;
/**
* Data access component for player technologies
*
* <p>
* This class implements database queries and calls to stored procedures which allow players to
* access and manage their empires' research.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
class PlayerTechnologiesDAOBean
implements PlayerTechnologiesDAO
{
/** SQL query that fetches an empire's research state */
private static final String Q_EMPIRE_RESEARCH = "SELECT * FROM emp.technologies_v2_view WHERE empire_id = ?";
/** Row mapper for research state entries */
private final ResearchRowMapper mResearch;
/** Stored procedure that initiates a research priority update */
private StoredProc fResprioUpdateStart;
/** Stored procedure that uploads a single research priority */
private StoredProc fResprioUpdateSet;
/** Stored procedure that applies a research priority update */
private StoredProc fResprioUpdateApply;
/** Stored procedure that implements a technology */
private StoredProc fTechnologyImplement;
/** Spring JDBC interface */
private JdbcTemplate dTemplate;
/** Initialise the necessary row mappers */
public PlayerTechnologiesDAOBean( )
{
this.mResearch = new ResearchRowMapper( );
}
/**
* Dependency injector that sets the data source and initialises stored procedures
*
* @param dataSource
* the data source
*/
@Autowired( required = true )
public void setDataSource( DataSource dataSource )
{
this.dTemplate = new JdbcTemplate( dataSource );
this.fResprioUpdateStart = new StoredProc( dataSource , "emp" , "resprio_update_start" )
.addParameter( "_empire" , Types.INTEGER )
.addOutput( "_result" , Types.BOOLEAN );
this.fResprioUpdateSet = new StoredProc( dataSource , "emp" , "resprio_update_set" )
.addParameter( "_technology" , Types.VARCHAR )
.addParameter( "_priority" , Types.INTEGER )
.addOutput( "_result" , Types.BOOLEAN );
this.fResprioUpdateApply = new StoredProc( dataSource , "emp" , "resprio_update_apply" )
.addOutput( "_result" , Types.BOOLEAN );
this.fTechnologyImplement = new StoredProc( dataSource , "emp" , "technology_implement" )
.addParameter( "_empire" , Types.INTEGER )
.addParameter( "_technology" , Types.VARCHAR )
.addOutput( "_result" , Types.BOOLEAN );
}
/**
* Query and map research entries for an empire
*
* <p>
* Run the query against <code>emp.technologies_view</code> then map all rows using a
* {@link ResearchRowMapper}.
*/
@Override
public List< ResearchData > getResearchData( int empire )
{
return this.dTemplate.query( Q_EMPIRE_RESEARCH , this.mResearch , empire );
}
/**
* Call <code>emp.resprio_update_start</code>
*/
@Override
public boolean startPriorityUpdate( int empire )
{
return (Boolean) this.fResprioUpdateStart.execute( empire ).get( "_result" );
}
/**
* Call <code>emp.resprio_update_set</code>
*/
@Override
public boolean uploadResearchPriority( String identifier , int priority )
{
return (Boolean) this.fResprioUpdateSet.execute( identifier , priority ).get( "_result" );
}
/**
* Call <code>emp.resprio_update_apply</code>
*/
@Override
public boolean applyPriorityUpdate( )
{
return (Boolean) this.fResprioUpdateApply.execute( ).get( "_result" );
}
/**
* Call <code>emp.technology_implement</code>
*/
@Override
public boolean implement( int empire , String technology )
{
return (Boolean) this.fTechnologyImplement.execute( empire , technology ).get( "_result" );
}
}

View file

@ -0,0 +1,197 @@
package com.deepclone.lw.beans.game.technologies;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.deepclone.lw.cmd.player.gdata.GamePageData;
import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
import com.deepclone.lw.cmd.player.tech.GetResearchResponse;
import com.deepclone.lw.interfaces.game.EmpireManagement;
import com.deepclone.lw.interfaces.game.technologies.PlayerTechnologiesDAO;
import com.deepclone.lw.interfaces.game.technologies.ResearchController;
import com.deepclone.lw.interfaces.i18n.TranslationException;
import com.deepclone.lw.interfaces.i18n.Translator;
/**
* Research control component
*
* <p>
* This component implements all actions allowing a player to view and update its empire's research
* state.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@Transactional
class ResearchControllerBean
implements ResearchController
{
/** Main empire management component */
private EmpireManagement empireManagement;
/** String translator */
private Translator translator;
/** Data access object for technologies */
private PlayerTechnologiesDAO playerTechnologiesDAO;
/**
* Dependency injector that sets the empire management component
*
* @param empireManagement
* the empire management component
*/
@Autowired( required = true )
public void setEmpireManagement( EmpireManagement empireManagement )
{
this.empireManagement = empireManagement;
}
/**
* Dependency injector that sets the translation component
*
* @param translator
* the translation component
*/
@Autowired( required = true )
public void setTranslator( Translator translator )
{
this.translator = translator;
}
/**
* Dependency injector that sets the data access object for technologies
*
* @param playerTechnologiesDAO
* the data access object for technologies
*/
@Autowired( required = true )
public void setPlayerTechnologiesDAO( PlayerTechnologiesDAO playerTechnologiesDAO )
{
this.playerTechnologiesDAO = playerTechnologiesDAO;
}
/**
* Get the raw research data from the database, then handle translation and dependencies
*
* <p>
* In order to generate the response, this method fetches the raw research state information
* from the database. It then proceeds with translating all categories, names and descriptions,
* then adds reverse dependency information to all loaded technologies.
*/
@Override
public GetResearchResponse getResearchData( int empire )
{
GamePageData pageData = this.empireManagement.getGeneralInformation( empire );
List< ResearchData > technologies = this.playerTechnologiesDAO.getResearchData( empire );
// Translate categories, names and descriptions
Map< String , String > translations = this.getTranslationsFor( technologies , pageData.getLanguage( ) );
Map< String , ResearchData > byId = new HashMap< String , ResearchData >( );
for ( ResearchData tech : technologies ) {
byId.put( tech.getIdentifier( ) , tech );
tech.setCategory( translations.get( tech.getCategory( ) ) );
if ( tech.getName( ) != null ) {
tech.setName( translations.get( tech.getName( ) ) );
tech.setDescription( translations.get( tech.getDescription( ) ) );
}
}
// Add reverse dependency identifiers
for ( ResearchData tech : technologies ) {
for ( String dependency : tech.getDependencies( ) ) {
byId.get( dependency ).addReverseDependency( tech.getIdentifier( ) );
}
}
return new GetResearchResponse( pageData , technologies );
}
/**
* Get translations used by a list of raw research entries
*
* <p>
* This method is fed the raw research state data from the player technologies data access
* object. It generates a set of string identifiers which contains all categories, names and
* descriptions, then fetches these strings' translations.
*
* @param technologies
* the research state data from the DAO
* @param language
* the player's selected language
*
* @return the map of string identifiers to string contents
*/
private Map< String , String > getTranslationsFor( List< ResearchData > technologies , String language )
{
Set< String > identifiers = new HashSet< String >( );
for ( ResearchData tech : technologies ) {
identifiers.add( tech.getCategory( ) );
if ( tech.getName( ) != null ) {
identifiers.add( tech.getName( ) );
identifiers.add( tech.getDescription( ) );
}
}
try {
return this.translator.translate( language , identifiers );
} catch ( TranslationException e ) {
throw new RuntimeException( "error while translating technology-related data" , e );
}
}
/**
* Update research priorities
*
* <p>
* This method will start a research priority update for the specified empire, then upload each
* value in the map to the database. Finally, it will try to apply the update.
*
* <p>
* If any of the above steps fail, the method will abort.
*/
@Override
public boolean updatePriorities( int empire , Map< String , Integer > priorities )
{
if ( !this.playerTechnologiesDAO.startPriorityUpdate( empire ) ) {
return false;
}
for ( Map.Entry< String , Integer > entry : priorities.entrySet( ) ) {
if ( !this.playerTechnologiesDAO.uploadResearchPriority( entry.getKey( ) , entry.getValue( ) ) ) {
return false;
}
}
return this.playerTechnologiesDAO.applyPriorityUpdate( );
}
/**
* Implement a technology
*
* <p>
* Call the DAO's technology implementation method in order to access the corresponding stored
* procedure.
*/
@Override
public boolean implement( int empire , String technology )
{
return this.playerTechnologiesDAO.implement( empire , technology );
}
}

View file

@ -0,0 +1,69 @@
package com.deepclone.lw.beans.game.technologies;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.postgresql.util.PGobject;
import org.springframework.jdbc.core.RowMapper;
import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
/**
* Row mapper for empire research state entries
*
* <p>
* This class is responsible for converting empire research state information rows into instances of
* the corresponding class, {@link ResearchData}. The instances it produces are "raw": they contain
* string identifiers instead of translations, and their reverse dependencies are not set.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*
*/
class ResearchRowMapper
implements RowMapper< ResearchData >
{
/**
* Map a row from <code>emp.technologies_view</code>
*
* <p>
* This method maps a single row from the <code>emp.technologies_view</code> view into a
* {@link ResearchData} instance.
*/
@Override
public ResearchData mapRow( ResultSet rs , int rowNum )
throws SQLException
{
ResearchData output = new ResearchData( );
output.setIdentifier( rs.getString( "emptech_id" ) );
output.setCategory( rs.getString( "technology_category" ) );
if ( rs.getBoolean( "emptech_visible" ) ) {
output.setName( rs.getString( "technology_name" ) );
output.setDescription( rs.getString( "technology_description" ) );
output.setPrice( rs.getLong( "technology_price" ) );
}
int ratio = rs.getInt( "emptech_ratio" );
if ( rs.wasNull( ) ) {
String state = ( (PGobject) rs.getObject( "emptech_state" ) ).getValue( );
output.setImplemented( "KNOWN".equals( state ) );
} else {
output.setCompletion( ratio );
output.setPriority( rs.getInt( "emptech_priority" ) );
}
String dependencies = rs.getString( "technology_dependencies" );
if ( ! "".equals( dependencies ) ) {
output.setDependencies( dependencies.split( "," ) );
}
return output;
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean id="playerTechnologiesDAO" class="com.deepclone.lw.beans.game.technologies.PlayerTechnologiesDAOBean" />
<bean id="reesearchController" class="com.deepclone.lw.beans.game.technologies.ResearchControllerBean" />
</beans>

View file

@ -1,56 +0,0 @@
package com.deepclone.lw.beans.user.player.game;
import org.springframework.beans.factory.annotation.Autowired;
import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
import com.deepclone.lw.beans.user.player.GameSubTypeBean;
import com.deepclone.lw.cmd.player.ImplementTechCommand;
import com.deepclone.lw.interfaces.game.EmpireManagement;
import com.deepclone.lw.interfaces.session.ServerSession;
import com.deepclone.lw.session.Command;
import com.deepclone.lw.session.CommandResponse;
public class ImplementTechCommandDelegateBean
implements AutowiredCommandDelegate
{
private EmpireManagement empireManagement;
@Autowired( required = true )
public void setEmpireManager( EmpireManagement manager )
{
this.empireManagement = manager;
}
@Override
public Class< ? extends Command > getType( )
{
return ImplementTechCommand.class;
}
@Override
public Class< ? extends SessionCommandHandler > getCommandHandler( )
{
return GameSubTypeBean.class;
}
@Override
public CommandResponse execute( ServerSession session , Command cParam )
{
ImplementTechCommand command = (ImplementTechCommand) cParam;
int empireId = session.get( "empireId" , Integer.class );
if ( session.get( "vacation" , Boolean.class ) ) {
return this.empireManagement.getOverview( empireId );
}
return this.empireManagement.implementTechnology( empireId , command.getTech( ) );
}
}

View file

@ -0,0 +1,70 @@
package com.deepclone.lw.beans.user.player.game.tech;
import org.springframework.beans.factory.annotation.Autowired;
import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
import com.deepclone.lw.beans.user.player.GameSubTypeBean;
import com.deepclone.lw.cmd.player.tech.GetResearchCommand;
import com.deepclone.lw.interfaces.game.technologies.ResearchController;
import com.deepclone.lw.interfaces.session.ServerSession;
import com.deepclone.lw.session.Command;
import com.deepclone.lw.session.CommandResponse;
/**
* Command delegate for {@link GetResearchCommand}
*
* <p>
* This command delegate uses the {@link ResearchController} to obtain all information pertaining to
* an empire's research.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
class GetResearchCommandDelegateBean
implements AutowiredCommandDelegate
{
/** The research controller */
private ResearchController researchController;
/**
* Dependency injector that sets the research controller
*
* @param researchController
* the research controller
*/
@Autowired
public void setResearchController( ResearchController researchController )
{
this.researchController = researchController;
}
/** This delegate handles {@link GetResearchCommand} instances */
@Override
public Class< ? extends Command > getType( )
{
return GetResearchCommand.class;
}
/** This delegate is part of the game session sub-type */
@Override
public Class< ? extends SessionCommandHandler > getCommandHandler( )
{
return GameSubTypeBean.class;
}
/** When the command is executed, fetch relevant data from the research controller */
@Override
public CommandResponse execute( ServerSession session , Command command )
{
int empire = session.get( "empireId" , Integer.class );
return this.researchController.getResearchData( empire );
}
}

View file

@ -0,0 +1,72 @@
package com.deepclone.lw.beans.user.player.game.tech;
import org.springframework.beans.factory.annotation.Autowired;
import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
import com.deepclone.lw.beans.user.player.GameSubTypeBean;
import com.deepclone.lw.cmd.player.tech.ImplementTechCommand;
import com.deepclone.lw.interfaces.game.technologies.ResearchController;
import com.deepclone.lw.interfaces.session.ServerSession;
import com.deepclone.lw.session.Command;
import com.deepclone.lw.session.CommandResponse;
import com.deepclone.lw.session.NullResponse;
public class ImplementTechCommandDelegateBean
implements AutowiredCommandDelegate
{
/** The research controller */
private ResearchController researchController;
/**
* Dependency injector that sets the research controller
*
* @param researchController
* the research controller
*/
@Autowired( required = true )
public void setResearchController( ResearchController researchController )
{
this.researchController = researchController;
}
/** This class handles {@link ImplementTechCommand} instances */
@Override
public Class< ? extends Command > getType( )
{
return ImplementTechCommand.class;
}
/** This class is enabled for the {@link GameSubTypeBean} session type */
@Override
public Class< ? extends SessionCommandHandler > getCommandHandler( )
{
return GameSubTypeBean.class;
}
/**
* Implement a technology
*
* <p>
* If the empire is not in vacation mode, try to implement the technology. Always return a
* {@link NullResponse}.
*/
@Override
public CommandResponse execute( ServerSession session , Command cParam )
{
ImplementTechCommand command = (ImplementTechCommand) cParam;
int empire = session.get( "empireId" , Integer.class );
if ( !session.get( "vacation" , Boolean.class ) ) {
this.researchController.implement( empire , command.getTech( ) );
}
return new NullResponse( );
}
}

View file

@ -0,0 +1,77 @@
package com.deepclone.lw.beans.user.player.game.tech;
import org.springframework.beans.factory.annotation.Autowired;
import com.deepclone.lw.beans.user.abst.AutowiredCommandDelegate;
import com.deepclone.lw.beans.user.abst.SessionCommandHandler;
import com.deepclone.lw.beans.user.player.GameSubTypeBean;
import com.deepclone.lw.cmd.player.tech.UpdateResearchPrioritiesCommand;
import com.deepclone.lw.interfaces.game.technologies.ResearchController;
import com.deepclone.lw.interfaces.session.ServerSession;
import com.deepclone.lw.session.Command;
import com.deepclone.lw.session.CommandResponse;
import com.deepclone.lw.session.NullResponse;
/**
* Command handler for research priority updates
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
class UpdateResearchPrioritiesCommandDelegateBean
implements AutowiredCommandDelegate
{
/** The research controller */
private ResearchController researchController;
/**
* Dependency injector that sets the research controller
*
* @param researchController
* the research controller
*/
@Autowired( required = true )
public void setResearchController( ResearchController researchController )
{
this.researchController = researchController;
}
/** This class handles {@link UpdateResearchPrioritiesCommand} instances */
@Override
public Class< ? extends Command > getType( )
{
return UpdateResearchPrioritiesCommand.class;
}
/** This class is enabled for the {@link GameSubTypeBean} session type */
@Override
public Class< ? extends SessionCommandHandler > getCommandHandler( )
{
return GameSubTypeBean.class;
}
/**
* Update research priorities
*
* <p>
* If the empire is not in vacation mode, access the research controller and update mining
* priorities using the specified values. Return a {@link NullResponse} in all cases.
*/
@Override
public CommandResponse execute( ServerSession session , Command command )
{
if ( !session.get( "vacation" , Boolean.class ) ) {
int empireId = session.get( "empireId" , Integer.class );
this.researchController.updatePriorities( empireId ,
( (UpdateResearchPrioritiesCommand) command ).getPriorities( ) );
}
return new NullResponse( );
}
}

View file

@ -44,9 +44,13 @@
<!-- Game: empire --> <!-- Game: empire -->
<bean class="com.deepclone.lw.beans.user.player.game.OverviewCommandDelegateBean" /> <bean class="com.deepclone.lw.beans.user.player.game.OverviewCommandDelegateBean" />
<bean class="com.deepclone.lw.beans.user.player.game.ImplementTechCommandDelegateBean" /> <bean class="com.deepclone.lw.beans.user.player.game.tech.ImplementTechCommandDelegateBean" />
<bean class="com.deepclone.lw.beans.user.player.game.UpdateEmpireMiningSettingsCommandDelegateBean" /> <bean class="com.deepclone.lw.beans.user.player.game.UpdateEmpireMiningSettingsCommandDelegateBean" />
<bean class="com.deepclone.lw.beans.user.player.game.GetNewPlanetCommandDelegateBean" /> <bean class="com.deepclone.lw.beans.user.player.game.GetNewPlanetCommandDelegateBean" />
<!-- Game: research -->
<bean class="com.deepclone.lw.beans.user.player.game.tech.GetResearchCommandDelegateBean" />
<bean class="com.deepclone.lw.beans.user.player.game.tech.UpdateResearchPrioritiesCommandDelegateBean" />
<!-- Game: planet list --> <!-- Game: planet list -->
<bean class="com.deepclone.lw.beans.user.player.game.ListPlanetsCommandDelegateBean" /> <bean class="com.deepclone.lw.beans.user.player.game.ListPlanetsCommandDelegateBean" />

View file

@ -30,6 +30,7 @@
<module>../legacyworlds-server-beans-resources</module> <module>../legacyworlds-server-beans-resources</module>
<module>../legacyworlds-server-beans-simple</module> <module>../legacyworlds-server-beans-simple</module>
<module>../legacyworlds-server-beans-system</module> <module>../legacyworlds-server-beans-system</module>
<module>../legacyworlds-server-beans-technologies</module>
<module>../legacyworlds-server-beans-updates</module> <module>../legacyworlds-server-beans-updates</module>
<module>../legacyworlds-server-beans-user</module> <module>../legacyworlds-server-beans-user</module>
</modules> </modules>

View file

@ -134,6 +134,10 @@ REVOKE EXECUTE
ON FUNCTION emp.technology_make_identifier( INT , TEXT , BOOLEAN ) ON FUNCTION emp.technology_make_identifier( INT , TEXT , BOOLEAN )
FROM PUBLIC; FROM PUBLIC;
GRANT EXECUTE
ON FUNCTION emp.technology_make_identifier( INT , TEXT , BOOLEAN )
TO :dbuser;
/* /*
* Initialise a research priorities update * Initialise a research priorities update

View file

@ -23,9 +23,6 @@ public interface EmpireDAO
public OverviewData getOverview( int empireId ); public OverviewData getOverview( int empireId );
public void implementTechnology( int empireId , int lineId );
public List< PlanetListData > getPlanetList( int empireId ); public List< PlanetListData > getPlanetList( int empireId );

View file

@ -22,9 +22,6 @@ public interface EmpireManagement
public EmpireResponse getOverview( int empireId ); public EmpireResponse getOverview( int empireId );
public EmpireResponse implementTechnology( int empireId , int techId );
public ListPlanetsResponse getPlanetList( int empireId ); public ListPlanetsResponse getPlanetList( int empireId );

View file

@ -0,0 +1,92 @@
package com.deepclone.lw.interfaces.game.technologies;
import java.util.List;
import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
/**
* Player-oriented data access interface for technologies
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public interface PlayerTechnologiesDAO
{
/**
* Obtain the list of technologies for an empire
*
* <p>
* Query the database for technology entries as seen from an empire's perspective. The
* technologies will be returned with I18N string identifiers for categories, names and
* descriptions (unless the details are not available).
*
* @param empire
* the empire's identifier
*
* @return the list of technology entries
*/
public List< ResearchData > getResearchData( int empire );
/**
* Start a research priority update
*
* <p>
* This method initialises a research priorities update by calling the appropriate stored
* procedure.
*
* @param empire
* the empire for which an update is being performed
*
* @return <code>true</code> on success, <code>false</code> on failure
*/
public boolean startPriorityUpdate( int empire );
/**
* Upload a single research priority
*
* <p>
* This method uploads the priority for one of the in-progress research items. It must be called
* only after a research priority update has been initiated.
*
* @param identifier
* the technology's identifier
* @param priority
* the research priority
*
* @return <code>true</code> on success, <code>false</code> on failure
*/
public boolean uploadResearchPriority( String identifier , int priority );
/**
* Apply an in-progress research priority update
*
* <p>
* This method applies a previously uploaded set of research priority updates.
*
* @return <code>true</code> on success, <code>false</code> on failure
*/
public boolean applyPriorityUpdate( );
/**
* Implement a technology
*
* <p>
* This method calls the stored procedure which causes an empire to implement a technology.
*
* @param empire
* the empire's identifier
* @param technology
* the technology's identifier
*
* @return <code>true</code> on success, <code>false</code> on failure
*/
public boolean implement( int empire , String technology );
}

View file

@ -0,0 +1,69 @@
package com.deepclone.lw.interfaces.game.technologies;
import java.util.Map;
import com.deepclone.lw.cmd.player.tech.GetResearchCommand;
import com.deepclone.lw.cmd.player.tech.GetResearchResponse;
/**
* Research control interface
*
* <p>
* This interface is implemented by the component which allows empires (and therefore players) to
* view the research state and control it.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public interface ResearchController
{
/**
* Obtain the state of an empire's research
*
* <p>
* This method must be overridden to generate the response to a {@link GetResearchCommand}.
*
* @param empire
* the empire's identifier
*
* @return the appropriate {@link GetResearchResponse} instance
*/
public GetResearchResponse getResearchData( int empire );
/**
* Update research priorities
*
* <p>
* This method must be implemented so it updates research priorities for an empire.
*
* @param empire
* the empire to update
* @param priorities
* the new research priorities, as a map associating technology identifiers to
* priority values
*
* @return <code>true</code> on success, <code>false</code> otherwise
*/
public boolean updatePriorities( int empire , Map< String , Integer > priorities );
/**
* Implement a technology
*
* <p>
* This method implements a technology which has been fully researched.
*
* @param empire
* the empire trying to implement a technology
* @param technology
* the technology's identifier
*
* @return <code>true</code> on success, <code>false</code> otherwise
*/
public boolean implement( int empire , String technology );
}

View file

@ -50,6 +50,10 @@
<artifactId>legacyworlds-server-beans-system</artifactId> <artifactId>legacyworlds-server-beans-system</artifactId>
<groupId>com.deepclone.lw</groupId> <groupId>com.deepclone.lw</groupId>
</dependency> </dependency>
<dependency>
<artifactId>legacyworlds-server-beans-technologies</artifactId>
<groupId>com.deepclone.lw</groupId>
</dependency>
<dependency> <dependency>
<artifactId>legacyworlds-server-beans-updates</artifactId> <artifactId>legacyworlds-server-beans-updates</artifactId>
<groupId>com.deepclone.lw</groupId> <groupId>com.deepclone.lw</groupId>

View file

@ -8,6 +8,7 @@
<!-- Spring configuration loader for all "real" game components --> <!-- Spring configuration loader for all "real" game components -->
<!-- ========================================================== --> <!-- ========================================================== -->
<import resource="game/resources.xml" /> <import resource="game/resources.xml" />
<import resource="game/technologies.xml" />
<import resource="game/updates.xml" /> <import resource="game/updates.xml" />
</beans> </beans>

View file

@ -1,28 +0,0 @@
package com.deepclone.lw.cmd.player;
import com.deepclone.lw.session.Command;
public class ImplementTechCommand
extends Command
{
private static final long serialVersionUID = 1L;
private final int tech;
public ImplementTechCommand( int tech )
{
this.tech = tech;
}
public int getTech( )
{
return tech;
}
}

View file

@ -0,0 +1,373 @@
package com.deepclone.lw.cmd.player.gdata.empire;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* An entry from the research page
*
* <p>
* This class represents entries from the research page. It is capable of representing in-progress
* research (with or without details), as well as pending and implemented technologies.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class ResearchData
implements Serializable , Comparable< ResearchData >
{
/**
* The serialisation version identifier
*
* <ul>
* <li>Introduced in B6M2 with ID 1
* </ul>
*/
private static final long serialVersionUID = 1L;
/** The identifier of the technology */
private String identifier;
/** The category the technology belongs to */
private String category;
/**
* The translated name of the technology, or <code>null</code> if the technology is being
* researched and progress is insufficient to display details.
*/
private String name;
/**
* The description of the technology, or <code>null</code> if the technology is being researched
* and progress is insufficient to display details.
*/
private String description;
/**
* The implementation price of the technology, or <code>null</code> if the technology is being
* researched and progress is insufficient to display details.
*/
private Long price;
/**
* The completion percentage of the technology, or <code>null</code> if the technology has been
* fully researched.
*/
private Integer completion;
/**
* Priority of the research on this technology, or <code>null</code> if the technology is not
* being researched.
*/
private Integer priority;
/**
* Whether the technology is being researched (<code>null</code>), pending implementation (
* <code>false</code>) or implemented (<code>true</code>).
*/
private Boolean implemented;
/** List of identifiers of technologies the current technology depends on. */
private List< String > dependencies = new ArrayList< String >( );
/** List of identifiers of technologies that depend on the current technology. */
private List< String > revDependencies = new LinkedList< String >( );
/**
* Gets the identifier of the technology.
*
* @return the identifier of the technology
*/
public String getIdentifier( )
{
return this.identifier;
}
/**
* Sets the identifier of the technology.
*
* @param identifier
* the new identifier of the technology
*/
public void setIdentifier( String identifier )
{
this.identifier = identifier;
}
/**
* Gets the category the technology belongs to.
*
* @return the category the technology belongs to
*/
public String getCategory( )
{
return this.category;
}
/**
* Sets the category the technology belongs to.
*
* @param category
* the new category the technology belongs to
*/
public void setCategory( String category )
{
this.category = category;
}
/**
* Gets the translated name of the technology
*
* @return the translated name of the technology, or <code>null</code> if the technology is
* being researched and progress is insufficient to display details
*/
public String getName( )
{
return this.name;
}
/**
* Sets the translated name of the technology
*
* @param name
* the new translated name of the technology
*/
public void setName( String name )
{
this.name = name;
}
/**
* Gets the description of the technology
*
* @return the description of the technology, or <code>null</code> if the technology is being
* researched and progress is insufficient to display details
*/
public String getDescription( )
{
return this.description;
}
/**
* Sets the description of the technology
*
* @param description
* the new description of the technology
*/
public void setDescription( String description )
{
this.description = description;
}
/**
* Gets the implementation price of the technology
*
* @return the implementation price of the technology, or <code>null</code> if the technology is
* being researched and progress is insufficient to display details
*/
public Long getPrice( )
{
return this.price;
}
/**
* Sets the implementation price of the technology
*
* @param price
* the new implementation price of the technology
*/
public void setPrice( Long price )
{
this.price = price;
}
/**
* Gets the completion percentage of the technology.
*
* @return the completion percentage of the technology, or <code>null</code> if the technology
* has been fully researched
*/
public Integer getCompletion( )
{
return this.completion;
}
/**
* Sets the completion percentage of the technology.
*
* @param completion
* the new completion percentage of the technology
*/
public void setCompletion( Integer completion )
{
this.completion = completion;
}
/**
* Gets the priority of the research on this technology
*
* @return the priority of the research on this technology, or <code>null</code> if the
* technology is not being researched
*/
public Integer getPriority( )
{
return this.priority;
}
/**
* Sets the priority of the research on this technology.
*
* @param priority
* the new priority of the research on this technology
*/
public void setPriority( Integer priority )
{
this.priority = priority;
}
/**
* Gets whether the technology is being researched (<code>null</code>), pending implementation (
* <code>false</code>) or implemented (<code>true</code>).
*
* @return whether the technology is being researched (<code>null</code>), pending
* implementation ( <code>false</code>) or implemented (<code>true</code>)
*/
public Boolean getImplemented( )
{
return this.implemented;
}
/**
* Sets whether the technology is being researched (<code>null</code>), pending implementation (
* <code>false</code>) or implemented (<code>true</code>).
*
* @param implemented
* whether the technology is being researched (<code>null</code>), pending
* implementation ( <code>false</code>) or implemented (<code>true</code>)
*/
public void setImplemented( Boolean implemented )
{
this.implemented = implemented;
}
/**
* Get the list of dependencies
*
* @return the list of dependencies
*/
public List< String > getDependencies( )
{
return Collections.unmodifiableList( this.dependencies );
}
/**
* Update the list of dependencies
*
* @param dependencies
* the new list of dependencies
*/
public void setDependencies( String[] dependencies )
{
this.dependencies = Arrays.asList( dependencies );
}
/**
* Get the list of reverse dependencies
*
* @return the list of reverse dependencies
*/
public List< String > getReverseDependencies( )
{
return this.revDependencies;
}
/**
* Add a reverse dependency
*
* @param identifier
* the identifier of the reverse dependency
*/
public void addReverseDependency( String identifier )
{
this.revDependencies.add( identifier );
}
/**
* Research page entry comparison
*
* <p>
* This method compares research entries based on:
* <ul>
* <li>The entries' states (in progress, pending implementation, implemented),
* <li>For in-progress entries, their current completion ratio,
* <li>The entries' names
* </ul>
*
* @param other
* the entry to compare to
*
* @return 1 if the current entry is "bigger", -1 if it is "smaller", and 0 if both entries are
* equal
*/
@Override
public int compareTo( ResearchData other )
{
if ( other == null ) {
return 1;
}
if ( this.implemented == null && other.implemented != null ) {
return -1;
}
if ( other.implemented == null && this.implemented != null ) {
return 1;
}
if ( this.implemented != null ) {
if ( this.implemented && !other.implemented ) {
return 1;
}
if ( other.implemented && !this.implemented ) {
return -1;
}
return this.name.compareTo( other.name );
}
if ( this.completion != other.completion ) {
return other.completion - this.completion;
}
if ( this.name == null ) {
return other.name == null ? 0 : -1;
}
return this.name.compareTo( other.name );
}
}

View file

@ -0,0 +1,26 @@
package com.deepclone.lw.cmd.player.tech;
import com.deepclone.lw.session.Command;
/**
* Command that obtains the current research &amp; technology data
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class GetResearchCommand
extends Command
{
/**
* The serialisation version identifier
*
* <ul>
* <li>Introduced in B6M2 with ID 1
* </ul>
*/
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,66 @@
package com.deepclone.lw.cmd.player.tech;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import com.deepclone.lw.cmd.player.gdata.GamePageData;
import com.deepclone.lw.cmd.player.gdata.GameResponseBase;
import com.deepclone.lw.cmd.player.gdata.empire.ResearchData;
/**
* Response that lists all research entries (sent by the server in response to
* {@link GetResearchCommand})
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class GetResearchResponse
extends GameResponseBase
{
/**
* The serialisation version identifier
*
* <ul>
* <li>Introduced in B6M2 with ID 1
* </ul>
*/
private static final long serialVersionUID = 1L;
/** Sorted list of research entries */
private final List< ResearchData > researchData;
/**
* Initialise the response
*
* <p>
* Copy and sort the list of research entries
*
* @param page
* the common page information
* @param technologies
* the list of research entries
*/
public GetResearchResponse( GamePageData page , List< ResearchData > technologies )
{
super( page );
this.researchData = new LinkedList< ResearchData >( technologies );
Collections.sort( this.researchData );
}
/**
* Get the list of research entries
*
* @return the sorted list of research entries
*/
public List< ResearchData > getResearchData( )
{
return this.researchData;
}
}

View file

@ -0,0 +1,53 @@
package com.deepclone.lw.cmd.player.tech;
import com.deepclone.lw.session.Command;
/**
* Technology implementation command
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class ImplementTechCommand
extends Command
{
/**
* The serialisation version identifier
*
* <ul>
* <li>Introduced in B6M1 with ID 1
* <li>Modified in B6M2, ID set to 2
* </ul>
*/
private static final long serialVersionUID = 2L;
/** Identifier of the technology to implement */
private final String tech;
/**
* Initialise the command
*
* @param tech
* the identifier of the technology to implement
*/
public ImplementTechCommand( String tech )
{
this.tech = tech;
}
/**
* Get the identifier of the technology to implement
*
* @return the identifier of the technology to implement
*/
public String getTech( )
{
return this.tech;
}
}

View file

@ -0,0 +1,50 @@
package com.deepclone.lw.cmd.player.tech;
import java.util.Map;
import com.deepclone.lw.session.Command;
/**
* Command that updates research priorities
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class UpdateResearchPrioritiesCommand
extends Command
{
/**
* Serialisation version identifier
*
* <ul>
* <li>Introduced in B6M2
* </ul>
*/
private static final long serialVersionUID = 1L;
/** The new research priorities */
private final Map< String , Integer > priorities;
/**
* Initialise the command using research priority values
*
* @param priorities
* a map that associates technology identifiers to priorities
*/
public UpdateResearchPrioritiesCommand( Map< String , Integer > priorities )
{
this.priorities = priorities;
}
/** @return the new research priorities */
public Map< String , Integer > getPriorities( )
{
return this.priorities;
}
}

View file

@ -17,6 +17,10 @@ import com.deepclone.lw.cmd.player.elist.*;
import com.deepclone.lw.cmd.player.fleets.*; import com.deepclone.lw.cmd.player.fleets.*;
import com.deepclone.lw.cmd.player.gdata.*; import com.deepclone.lw.cmd.player.gdata.*;
import com.deepclone.lw.cmd.player.planets.*; 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.cmd.player.msgs.*;
import com.deepclone.lw.session.Command; import com.deepclone.lw.session.Command;
import com.deepclone.lw.session.SessionException; 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 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 ) ); return (PostCommentResponse) this.execute( new PostCommentCommand( bugId , comment ) );
} }
} }

View file

@ -63,6 +63,9 @@
<div class="button"> <div class="button">
<a href="map" title="Map of the universe">Map</a> <a href="map" title="Map of the universe">Map</a>
</div> </div>
<div class="button">
<a href="research" title="Manage research priorities, implement new technologies and browse known technologies">Research</a>
</div>
<div class="button"> <div class="button">
<a href="alliance" title="Alliance">Alliance</a> <a href="alliance" title="Alliance">Alliance</a>
</div> </div>

View file

@ -2,7 +2,6 @@
<@page title="Empire"> <@page title="Empire">
<#assign ov = data.overview > <#assign ov = data.overview >
<#assign rs = data.research >
<@tabs> <@tabs>

View file

@ -0,0 +1,255 @@
<#--
Macro that starts a research tab for one of the 3 existing modes
Parameters:
curMode The tab to start: R for "in progress", P for pending, K for implemented
-->
<#macro startResearchTab curMode>
<#switch curMode>
<#case 'R'>
<@tabStart "research" "In progress" />
<@rawFormStart "research-set-priorities" "form-priorities" "research" />
<@listviewStart />
<@lv_line headers=true>
<@lv_column width=150>Category</@lv_column>
<@lv_column width="x">Name</@lv_column>
<@lv_column width=100 centered=true>Progress</@lv_column>
<@lv_column width=150 centered=true>Priority</@lv_column>
</@lv_line>
<#break>
<#case 'P'>
<@tabStart "pending" "Pending implementation" />
<@listviewStart />
<@lv_line headers=true>
<@lv_column width=150>Category</@lv_column>
<@lv_column width="x">Name</@lv_column>
<@lv_column width=150 right=true>Cost</@lv_column>
<@lv_column width=100 centered=true>&nbsp;</@lv_column>
</@lv_line>
<#break>
<#case 'K'>
<@tabStart "implemented" "Implemented" />
<@listviewStart />
<@lv_line headers=true>
<@lv_column width=150>Category</@lv_column>
<@lv_column width="x">Name</@lv_column>
</@lv_line>
<#break>
</#switch>
</#macro>
<#--
Macro that finishes a research tab depending on its type
Parameters:
mode The type of tab that is being closed
-->
<#macro endResearchTab mode>
<#switch mode>
<#case 'R'>
<@lv_line headers=true>
<@lv_column width="x" colspan=4>&nbsp;</@lv_column>
</@lv_line>
<@lv_line>
<@lv_column colspan=3>&nbsp;</@lv_column>
<@lv_column centered=true>
<input type="submit" value="Update" class="input" style="margin: 15px 0 0 0" />
</@lv_column>
</@lv_line>
<@listviewEnd />
</form>
<#break>
<#case 'P'>
<#case 'K'>
<@listviewEnd />
<#break>
</#switch>
<@tabEnd />
</#macro>
<#--
Macro that renders an in-progress research entry
Parameters:
entry the entry
-->
<#macro drawInProgressResearch entry>
<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
<@lv_column>${entry.category?xhtml}</@lv_column>
<@lv_column>
<#if entry.name?has_content>
<strong>${entry.name?xhtml}</strong>
<#else>
Unknown technology
</#if>
</@lv_column>
<@lv_column centered=true>${entry.completion} %</@lv_column>
<@lv_column centered=true>
<select name="rp-${entry.identifier?xhtml}" class="input">
<option style="padding: 0 5px" value="0" <#if entry.priority = 0>selected="selected"</#if>>lowest</option>
<option style="padding: 0 5px" value="1" <#if entry.priority = 1>selected="selected"</#if>>low</option>
<option style="padding: 0 5px" value="2" <#if entry.priority = 2>selected="selected"</#if>>normal</option>
<option style="padding: 0 5px" value="3" <#if entry.priority = 3>selected="selected"</#if>>high</option>
<option style="padding: 0 5px" value="4" <#if entry.priority = 4>selected="selected"</#if>>highest</option>
</select>
</@lv_column>
</@lv_line>
</#macro>
<#--
Macro that renders a pending technology
Parameters:
entry the technology
-->
<#macro drawPendingTechnology entry>
<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
<@lv_column>${entry.category?xhtml}</@lv_column>
<@lv_column>
<strong>${entry.name?xhtml}</strong>
</@lv_column>
<@lv_column right=true>
<strong>${entry.price?string(",##0")}</strong> <@abbr_bgc/>
</@lv_column>
<@lv_column right=true>
<#if data.page.cash &gt;= entry.price>
<@rawFormStart "research-implement" "form-implement-${entry.identifier}" entry.identifier />
<input type="hidden" name="technology" value="${entry.identifier?xhtml}" />
<input type="submit" value="Implement" class="input" />
</form>
<#else>
&nbsp;
</#if>
</@lv_column>
</@lv_line>
</#macro>
<#--
Macro that renders an implemented technology
Parameters:
entry the technology
-->
<#macro drawImplementedTechnology entry>
<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
<@lv_column>${entry.category?xhtml}</@lv_column>
<@lv_column>
<strong>${entry.name?xhtml}</strong>
</@lv_column>
</@lv_line>
</#macro>
<#--
Macro that renders a single entry
Parameters:
mode the current tab (see startResearchTab for details)
entry the entry
-->
<#macro drawResearchEntry mode entry>
<#switch mode>
<#case 'R'>
<@drawInProgressResearch entry />
<#break>
<#case 'P'>
<@drawPendingTechnology entry />
<#break>
<#case 'K'>
<@drawImplementedTechnology entry />
<#break>
</#switch>
</#macro>
<#--
Render the visible part of the research page
Parameters:
entries the sorted list of research entries
-->
<#macro renderResearchTabs entries>
<#local prevMode = ''>
<@tabs>
<#list entries as entry>
<#-- Determine type of entry to display -->
<#if entry.implemented?has_content>
<#if entry.implemented>
<#local curMode = 'K'>
<#else>
<#local curMode = 'P'>
</#if>
<#else>
<#local curMode = 'R'>
</#if>
<#-- Start/end tabs -->
<#if curMode != prevMode>
<#if prevMode != ''>
<@endResearchTab prevMode />
</#if>
<@startResearchTab curMode />
<#local prevMode = curMode>
</#if>
<@drawResearchEntry curMode entry />
</#list>
<@endResearchTab prevMode />
</@tabs>
</#macro>
<#--
Render all research information in a hidden layer which will be used by
the client-side code to display more information.
-->
<#macro renderFullResearchData entries>
<div style="display: none">
<#list entries as tech>
<div class="tech-description" id="tdesc-${tech.identifier?xhtml}">
<h4><#if tech.name?has_content>${tech.name?xhtml}<#else>Unknown technology</#if></h4>
<div class="tech-info"><strong>Category:</strong> ${tech.category?xhtml}</div>
<#if tech.description?has_content><div class="tech-info">${tech.description?xhtml}</div></#if>
<#if tech.price?has_content>
<div class="tech-info"><strong>Implementation cost:</strong> ${tech.price?string(",##0")} <@abbr_bgc /></div>
</#if>
<#if tech.dependencies?has_content>
<div class="tech-info"><strong>Depends on:</strong>
<ul>
<#list tech.dependencies as depId>
<li class="dep">${depId}</li>
</#list>
</ul>
</div>
</#if>
<#if tech.reverseDependencies?has_content>
<div class="tech-info"><strong>Required by:</strong>
<ul>
<#list tech.reverseDependencies as depId>
<li class="dep">${depId}</li>
</#list>
</ul>
</div>
</#if>
</div>
</#list>
</div>
<script type="text/javascript" charset="utf-8" src="js/research.js"></script>
</#macro>
<#--
Main research page
-->
<#macro render>
<@page title="Research">
<#local entries = data.researchData>
<@renderResearchTabs entries />
<@renderFullResearchData entries />
</@page>
</#macro>

View file

@ -63,11 +63,14 @@
<div class="button"> <div class="button">
<a href="map" title="Carte de l'univers">Carte</a> <a href="map" title="Carte de l'univers">Carte</a>
</div> </div>
<div class="button">
<a href="research" title="Gestion des priorités de recherche, implémentation et visualisation de technologies">Recherche</a>
</div>
<div class="button"> <div class="button">
<a href="alliance" title="Alliance">Alliance</a> <a href="alliance" title="Alliance">Alliance</a>
</div> </div>
<div class="button"> <div class="button">
<a href="enemies" title="Gèstion des listes de joueurs et alliances ennemis">Listes d'ennemis</a> <a href="enemies" title="Gestion des listes de joueurs et alliances ennemis">Listes d'ennemis</a>
</div> </div>
<div class="button"> <div class="button">
<a href="messages" title="Messages">Messages</a> <a href="messages" title="Messages">Messages</a>

View file

@ -2,7 +2,6 @@
<@page title="Empire"> <@page title="Empire">
<#assign ov = data.overview > <#assign ov = data.overview >
<#assign rs = data.research >
<@tabs> <@tabs>

View file

@ -0,0 +1,255 @@
<#--
Macro that starts a research tab for one of the 3 existing modes
Parameters:
curMode The tab to start: R for "in progress", P for pending, K for implemented
-->
<#macro startResearchTab curMode>
<#switch curMode>
<#case 'R'>
<@tabStart "research" "En cours" />
<@rawFormStart "research-set-priorities" "form-priorities" "research" />
<@listviewStart />
<@lv_line headers=true>
<@lv_column width=150>Catégorie</@lv_column>
<@lv_column width="x">Nom</@lv_column>
<@lv_column width=100 centered=true>Progrès</@lv_column>
<@lv_column width=150 centered=true>Priorité</@lv_column>
</@lv_line>
<#break>
<#case 'P'>
<@tabStart "pending" "En attente" />
<@listviewStart />
<@lv_line headers=true>
<@lv_column width=150>Catégorie</@lv_column>
<@lv_column width="x">Nom</@lv_column>
<@lv_column width=150 right=true>Coût</@lv_column>
<@lv_column width=100 centered=true>&nbsp;</@lv_column>
</@lv_line>
<#break>
<#case 'K'>
<@tabStart "implemented" "Recherchées" />
<@listviewStart />
<@lv_line headers=true>
<@lv_column width=150>Catégorie</@lv_column>
<@lv_column width="x">Nom</@lv_column>
</@lv_line>
<#break>
</#switch>
</#macro>
<#--
Macro that finishes a research tab depending on its type
Parameters:
mode The type of tab that is being closed
-->
<#macro endResearchTab mode>
<#switch mode>
<#case 'R'>
<@lv_line headers=true>
<@lv_column width="x" colspan=4>&nbsp;</@lv_column>
</@lv_line>
<@lv_line>
<@lv_column colspan=3>&nbsp;</@lv_column>
<@lv_column centered=true>
<input type="submit" value="Modifier" class="input" style="margin: 15px 0 0 0" />
</@lv_column>
</@lv_line>
<@listviewEnd />
</form>
<#break>
<#case 'P'>
<#case 'K'>
<@listviewEnd />
<#break>
</#switch>
<@tabEnd />
</#macro>
<#--
Macro that renders an in-progress research entry
Parameters:
entry the entry
-->
<#macro drawInProgressResearch entry>
<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
<@lv_column>${entry.category?xhtml}</@lv_column>
<@lv_column>
<#if entry.name?has_content>
<strong>${entry.name?xhtml}</strong>
<#else>
Technologie inconnue
</#if>
</@lv_column>
<@lv_column centered=true>${entry.completion} %</@lv_column>
<@lv_column centered=true>
<select name="rp-${entry.identifier?xhtml}" class="input">
<option style="padding: 0 5px" value="0" <#if entry.priority = 0>selected="selected"</#if>>très basse</option>
<option style="padding: 0 5px" value="1" <#if entry.priority = 1>selected="selected"</#if>>basse</option>
<option style="padding: 0 5px" value="2" <#if entry.priority = 2>selected="selected"</#if>>normale</option>
<option style="padding: 0 5px" value="3" <#if entry.priority = 3>selected="selected"</#if>>élevée</option>
<option style="padding: 0 5px" value="4" <#if entry.priority = 4>selected="selected"</#if>>très élevée</option>
</select>
</@lv_column>
</@lv_line>
</#macro>
<#--
Macro that renders a pending technology
Parameters:
entry the technology
-->
<#macro drawPendingTechnology entry>
<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
<@lv_column>${entry.category?xhtml}</@lv_column>
<@lv_column>
<strong>${entry.name?xhtml}</strong>
</@lv_column>
<@lv_column right=true>
<strong>${entry.price?string(",##0")}</strong> <@abbr_bgc/>
</@lv_column>
<@lv_column right=true>
<#if data.page.cash &gt;= entry.price>
<@rawFormStart "research-implement" "form-implement-${entry.identifier}" entry.identifier />
<input type="hidden" name="technology" value="${entry.identifier?xhtml}" />
<input type="submit" value="Implémenter" class="input" />
</form>
<#else>
&nbsp;
</#if>
</@lv_column>
</@lv_line>
</#macro>
<#--
Macro that renders an implemented technology
Parameters:
entry the technology
-->
<#macro drawImplementedTechnology entry>
<@lv_line id="tl-${entry.identifier?xhtml}" class="tech-line">
<@lv_column>${entry.category?xhtml}</@lv_column>
<@lv_column>
<strong>${entry.name?xhtml}</strong>
</@lv_column>
</@lv_line>
</#macro>
<#--
Macro that renders a single entry
Parameters:
mode the current tab (see startResearchTab for details)
entry the entry
-->
<#macro drawResearchEntry mode entry>
<#switch mode>
<#case 'R'>
<@drawInProgressResearch entry />
<#break>
<#case 'P'>
<@drawPendingTechnology entry />
<#break>
<#case 'K'>
<@drawImplementedTechnology entry />
<#break>
</#switch>
</#macro>
<#--
Render the visible part of the research page
Parameters:
entries the sorted list of research entries
-->
<#macro renderResearchTabs entries>
<#local prevMode = ''>
<@tabs>
<#list entries as entry>
<#-- Determine type of entry to display -->
<#if entry.implemented?has_content>
<#if entry.implemented>
<#local curMode = 'K'>
<#else>
<#local curMode = 'P'>
</#if>
<#else>
<#local curMode = 'R'>
</#if>
<#-- Start/end tabs -->
<#if curMode != prevMode>
<#if prevMode != ''>
<@endResearchTab prevMode />
</#if>
<@startResearchTab curMode />
<#local prevMode = curMode>
</#if>
<@drawResearchEntry curMode entry />
</#list>
<@endResearchTab prevMode />
</@tabs>
</#macro>
<#--
Render all research information in a hidden layer which will be used by
the client-side code to display more information.
-->
<#macro renderFullResearchData entries>
<div style="display: none">
<#list entries as tech>
<div class="tech-description" id="tdesc-${tech.identifier?xhtml}">
<h4><#if tech.name?has_content>${tech.name?xhtml}<#else>Technologie inconnue</#if></h4>
<div class="tech-info"><strong>Catégorie:</strong> ${tech.category?xhtml}</div>
<#if tech.description?has_content><div class="tech-info">${tech.description?xhtml}</div></#if>
<#if tech.price?has_content>
<div class="tech-info"><strong>Coût d'implémentation:</strong> ${tech.price?string(",##0")} <@abbr_bgc /></div>
</#if>
<#if tech.dependencies?has_content>
<div class="tech-info"><strong>Requiert:</strong>
<ul>
<#list tech.dependencies as depId>
<li class="dep">${depId}</li>
</#list>
</ul>
</div>
</#if>
<#if tech.reverseDependencies?has_content>
<div class="tech-info"><strong>Requis par:</strong>
<ul>
<#list tech.reverseDependencies as depId>
<li class="dep">${depId}</li>
</#list>
</ul>
</div>
</#if>
</div>
</#list>
</div>
<script type="text/javascript" charset="utf-8" src="js/research.js"></script>
</#macro>
<#--
Main research page
-->
<#macro render>
<@page title="Recherche">
<#local entries = data.researchData>
<@renderResearchTabs entries />
<@renderFullResearchData entries />
</@page>
</#macro>

View file

@ -1,12 +1,21 @@
<#macro form action name="" hash=""> <#macro rawFormStart action name="" hash="">
<form action="${action?url}.action<#if hash != "">#${hash?url}</#if>" method="post">
</#macro>
<#macro formStart action name="" hash="">
<div class="form-container"> <div class="form-container">
<form action="${action?url}.action<#if hash != "">#${hash?url}</#if>" method="post"> <@rawFormStart action name hash />
<table> <table>
<#nested> </#macro>
<#macro formEnd>
</table> </table>
</form> </form>
</div> </div>
</#macro> </#macro>
<#macro form action name="" hash="">
<@formStart action name hash />
<#nested>
<@formEnd />
</#macro>
<#macro form_field_line label id> <#macro form_field_line label id>
<tr class="form-field"> <tr class="form-field">
<th><label for="ff-${id?xhtml}">${label?xhtml}:</label></th> <th><label for="ff-${id?xhtml}">${label?xhtml}:</label></th>

View file

@ -1,24 +1,30 @@
<#macro listview> <#macro listviewStart>
<table class="list-view"> <table class="list-view">
<#nested> </#macro>
<#macro listviewEnd>
</table> </table>
</#macro>
<#macro listview>
<@listviewStart />
<#nested>
<@listviewEnd />
</#macro> </#macro>
<#macro lv_line headers=false class=""> <#macro lv_line headers=false class="" id="">
<tr<#if class != ""> class="${class}<#if headers>headers</#if>"<#elseif headers> class="headers"</#if>> <tr<#if class != ""> class="${class}<#if headers>headers</#if>"<#elseif headers> class="headers"</#if><#if id != ""> id="${id?xhtml}"</#if>>
<#nested> <#nested>
</tr> </tr>
</#macro> </#macro>
<#macro lv_column width=0 centered=false right=false colspan=0> <#macro lv_column width=0 centered=false right=false colspan=0 id="">
<#if width?is_string> <#if width?is_string>
<th style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>> <th style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if><#if id != ""> id="${id?xhtml}"</#if>>
<#nested> <#nested>
</th> </th>
<#elseif width gt 0> <#elseif width gt 0>
<th style="width: ${width}px; text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>> <th style="width: ${width}px; text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if><#if id != ""> id="${id?xhtml}"</#if>>
<#nested> <#nested>
</th> </th>
<#else> <#else>
<td style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if>> <td style="text-align: <#if centered>center<#elseif right>right<#else>left</#if>"<#if colspan gt 1> colspan="${colspan}"</#if><#if id != ""> id="${id?xhtml}"</#if>>
<#nested> <#nested>
</td> </td>
</#if> </#if>

View file

@ -3,11 +3,17 @@
<#nested> <#nested>
</div> </div>
</#macro> </#macro>
<#macro tab id title> <#macro tabStart id title>
<div class="tab" id="${id?xhtml}"> <div class="tab" id="${id?xhtml}">
<h3>${title?xhtml}</h3> <h3>${title?xhtml}</h3>
<div class="tab-contents"> <div class="tab-contents">
<#nested> </#macro>
<#macro tabEnd>
</div> </div>
</div> </div>
</#macro>
<#macro tab id title>
<@tabStart id title />
<#nested>
<@tabEnd />
</#macro> </#macro>

View file

@ -266,10 +266,10 @@ div.button a:focus,div.button a:hover {
/* Forms */ /* Forms */
.form-container { .form-container {
width: -moz-calc( 100% - 128px ); width: -moz-calc(100% - 128px);
width: -webkit-calc( 100% - 128px ); width: -webkit-calc(100% - 128px);
width: -o-calc( 100% - 128px ); width: -o-calc(100% - 128px);
width: calc( 100% - 128px ); width: calc(100% - 128px);
margin: 0 64px; margin: 0 64px;
} }
@ -304,10 +304,10 @@ div.button a:focus,div.button a:hover {
padding: 5px 20px; padding: 5px 20px;
} }
.form-submit .input:hover , .form-submit .input:focus { .form-submit .input:hover,.form-submit .input:focus {
padding: 5px 20px; padding: 5px 20px;
border-color: #dfdfdf; border-color: #dfdfdf;
background-color: rgba(127,127,127,0.6); background-color: rgba(127, 127, 127, 0.6);
} }
.form-extra td { .form-extra td {
@ -323,7 +323,7 @@ div.button a:focus,div.button a:hover {
.form-error td { .form-error td {
font-size: 11pt; font-size: 11pt;
color: white; color: white;
background-color: rgba(255,0,0,0.4); background-color: rgba(255, 0, 0, 0.4);
font-weight: bold; font-weight: bold;
margin: 2px 0px; margin: 2px 0px;
padding: 5px 10px; padding: 5px 10px;
@ -334,7 +334,7 @@ div.button a:focus,div.button a:hover {
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
border-color: #afafaf; border-color: #afafaf;
background-color: rgba(63,63,63,0.6); background-color: rgba(63, 63, 63, 0.6);
color: white; color: white;
font-size: 10pt; font-size: 10pt;
margin: 1px 0px; margin: 1px 0px;
@ -371,10 +371,10 @@ div.button a:focus,div.button a:hover {
/* List display */ /* List display */
.list-view { .list-view {
width: -moz-calc( 100% - 64px ); width: -moz-calc(100% - 64px);
width: -webkit-calc( 100% - 64px ); width: -webkit-calc(100% - 64px);
width: -o-calc( 100% - 64px ); width: -o-calc(100% - 64px);
width: calc( 100% - 64px ); width: calc(100% - 64px);
margin: 0 32px 20px 32px; margin: 0 32px 20px 32px;
border-collapse: collapse; border-collapse: collapse;
} }
@ -604,20 +604,20 @@ table.fleets-planet,table.fleets-moving {
border: 1px solid white; border: 1px solid white;
border-collapse: collapse; border-collapse: collapse;
margin: 0 0 20px 5px; margin: 0 0 20px 5px;
width: -moz-calc( 100% - 20px ); width: -moz-calc(100% - 20px);
width: -webkit-calc( 100% - 20px ); width: -webkit-calc(100% - 20px);
width: -o-calc( 100% - 20px ); width: -o-calc(100% - 20px);
width: calc( 100% - 20px ); width: calc(100% - 20px);
} }
table.selected-fleets { table.selected-fleets {
border: 1px solid white; border: 1px solid white;
border-collapse: collapse; border-collapse: collapse;
margin: 10px 0 20px 15px; margin: 10px 0 20px 15px;
width: -moz-calc( 100% - 30px ); width: -moz-calc(100% - 30px);
width: -webkit-calc( 100% - 30px ); width: -webkit-calc(100% - 30px);
width: -o-calc( 100% - 30px ); width: -o-calc(100% - 30px);
width: calc( 100% - 30px ); width: calc(100% - 30px);
} }
table.fleets-planet td { table.fleets-planet td {
@ -799,4 +799,20 @@ tr.alliance-msg * {
tr.empire-msg * { tr.empire-msg * {
color: #afafaf; color: #afafaf;
}
/*
* Research page
*/
.tech-line {
height: 22px;
}
.tech-view h4 {
margin-bottom: 15px;
}
.tech-view .tech-info {
margin-bottom: 7px;
}
.tech-line.selected, .tech-line.selected td {
background-color: rgba(127, 127, 127, 0.25);
} }

View file

@ -0,0 +1,87 @@
$(function() {
/* Replace dependency identifiers */
$('.tech-info li.dep').each(
function() {
var _id = this.innerHTML;
var _targetTitle = $('#tdesc-' + _id + ' h4');
this.innerHTML = '';
$('<a/>').attr('href', '#tl-' + _id)
.append(_targetTitle.html()).appendTo($(this));
});
/* Map entries to tabs */
var _entries = {};
var _tabs = {};
$('.tech-line').each(
function() {
var _id = $(this).attr('id').replace(/^tl-/, '');
var _tab = $(this).parents('.tab-contents').attr('id').replace(
/^tabc-/, '');
_entries[_id] = _tab;
if (!_tabs[_tab]) {
_tabs[_tab] = [];
}
_tabs[_tab].push(_id);
});
var _selected = {};
for ( var _tab in _tabs) {
_selected[_tab] = _tabs[_tab][0];
$('#tl-' + _selected[_tab]).toggleClass('selected');
}
/* Insert viewing area */
var _viewingArea = $('<div/>').css({
'float' : 'right',
'width' : '300px',
'position' : 'relative',
}).attr('class', 'tech-view').prependTo($('#page-contents'));
$('div.tabs').css({
'margin-right' : '300px'
});
/* When an entry is clicked, set contents of viewing area */
var _displayed = '';
$('.tech-line').click(function() {
var _id = $(this).attr('id').replace(/^tl-/, '');
if (_id == _displayed) {
return;
}
_viewingArea.html($('#tdesc-' + _id).html());
$('a', _viewingArea).click(function() {
var _target = $(this).attr('href').replace(/^#tl-/, '');
$('#tabb-' + _entries[_target]).click();
$('#tl-' + _target).click();
});
if (_selected[_entries[_id]] != _id) {
$('#tl-' + _selected[_entries[_id]]).toggleClass('selected');
$(this).toggleClass('selected');
_selected[_entries[_id]] = _id;
}
location.hash = '#tl-'+_id;
return true;
});
/* When a tab button is clicked, select the appropriate entry */
$('.tab-buttons a').click(function() {
var _id = $(this).attr('id').replace(/^tabb-/, '');
var _tech = _selected[_id];
$('#tl-' + _tech).click();
});
(function() {
var _current = location.hash;
if (_current.match(/^#tl-/)) {
_current = _current.replace(/#tl-/, '');
} else if (_current.match(/^#/)) {
_current = _selected[_current.replace(/^#/, '')];
} else {
for ( var _tab in _selected) {
_current = _selected[_tab];
break;
}
}
$('#tabb-' + _entries[_current]).click();
$('#tl-' + _current).click();
})();
});

View file

@ -10,7 +10,6 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.SessionAttributes;
@ -73,21 +72,15 @@ public class OverviewPage
/** /**
* "Implement technology" command * Empire-wide mining settings update command
* *
* <p> * <p>
* This method is mapped to the technology implementation command URL. * This method is called when a command to update empire-wide mining settings is received.
* *
* @param request * @param request
* the HTTP 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 * @throws SessionException
* if some error occurs on the server * if some error occurs on the server
@ -96,26 +89,8 @@ public class OverviewPage
* @throws SessionMaintenanceException * @throws SessionMaintenanceException
* if the game is under maintenance * 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 ) @RequestMapping( value = "/update-mining-settings.action" , method = RequestMethod.POST )
public String updateMiningSettings( HttpServletRequest request , @ModelAttribute( "language" ) String language , public String updateMiningSettings( HttpServletRequest request )
Model model )
throws SessionException , SessionServerException , SessionMaintenanceException throws SessionException , SessionServerException , SessionMaintenanceException
{ {
Map< String , Integer > miningSettings = this.getMiningSettings( request ); Map< String , Integer > miningSettings = this.getMiningSettings( request );

View file

@ -0,0 +1,186 @@
package com.deepclone.lw.web.main.game;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import com.deepclone.lw.session.SessionException;
import com.deepclone.lw.web.beans.intercept.SessionRequirement;
import com.deepclone.lw.web.beans.session.SessionMaintenanceException;
import com.deepclone.lw.web.beans.session.SessionServerException;
import com.deepclone.lw.web.beans.view.PageControllerBase;
import com.deepclone.lw.web.csess.PlayerSession;
/**
* Controller for the Research page
*
* <p>
* This controller contains all URL handlers associated with the research page: viewing research
* state, setting research priorities, and implementing technologies.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@Controller
@SessionRequirement( value = true , redirectTo = "player-session" , subType = "game" )
@SessionAttributes( "language" )
public class ResearchPage
extends PageControllerBase
{
/**
* Research page view
*
* <p>
* This method fetches all research information from the game server then requests the page
* rendering.
*
* @param request
* the HTTP request
* @param language
* the language from the session
* @param model
* the model
*
* @return the research page rendering information
*
* @throws SessionException
* if some error occurs on the server
* @throws SessionServerException
* if the server is unreachable
* @throws SessionMaintenanceException
* if the game is under maintenance
*/
@RequestMapping( "/research" )
public String view( HttpServletRequest request , @ModelAttribute( "language" ) String language , Model model )
throws SessionException , SessionServerException , SessionMaintenanceException
{
PlayerSession pSession = this.getSession( PlayerSession.class , request );
return this.render( model , "game" , language , "research" , pSession.getResearch( ) );
}
/**
* Research priorities update command
*
* <p>
* This method handles the research priorities update command, which is triggered when the
* "Update" button is clicked on the "In progress" tab of the research page.
*
* @param request
* the HTTP request
*
* @return a redirection to the research page's "in progress" tab
*
* @throws SessionException
* if some error occurs on the server
* @throws SessionServerException
* if the server is unreachable
* @throws SessionMaintenanceException
* if the game is under maintenance
*/
@RequestMapping( value = "/research-set-priorities.action" , method = RequestMethod.POST )
public String setPriorities( HttpServletRequest request )
throws SessionException , SessionServerException , SessionMaintenanceException
{
Map< String , Integer > priorities = this.getPriorities( request );
if ( priorities != null ) {
this.getSession( PlayerSession.class , request ).updateResearchPriorities( priorities );
}
return this.redirect( "research#research" );
}
/**
* Extract research priorities from the HTTP request
*
* <p>
* Look for all submitted fields that begin with "rp-" then try to extract their values into a
* map that associates technology identifiers to priorities.
*
* @param request
* the HTTP request
*
* @return the map containing the submitted priorities, or <code>null</code> if one of the
* values was incorrect.
*/
private Map< String , Integer > getPriorities( HttpServletRequest request )
{
Map< String , Object > input = this.getInput( request );
Map< String , Integer > priorities = new HashMap< String , Integer >( );
for ( Entry< String , Object > entry : input.entrySet( ) ) {
// Ignore items which are not priorities
String name = entry.getKey( );
if ( !name.startsWith( "rp-" ) ) {
continue;
}
name = name.substring( 3 );
// Get values
if ( ! ( entry.getValue( ) instanceof String[] ) ) {
continue;
}
String[] values = (String[]) entry.getValue( );
if ( values.length < 1 ) {
continue;
}
// Pre-validate them
int value;
try {
value = Integer.parseInt( values[ 0 ] );
} catch ( NumberFormatException e ) {
value = -1;
}
if ( value < 0 || value > 4 ) {
return null;
}
priorities.put( name , value );
}
return priorities;
}
/**
* Technology implementation command
*
* <p>
* This method handles the technology implementation command, which is triggered when the
* "Implement" button is clicked on the "Pending" tab of the research page.
*
* @param request
* the HTTP request
* @param tech
* the identifier of the technology
*
* @return a redirection to the research page's "Implemented" tab
*
* @throws SessionException
* if some error occurs on the server
* @throws SessionServerException
* if the server is unreachable
* @throws SessionMaintenanceException
* if the game is under maintenance
*/
@RequestMapping( value = "/research-implement.action" , method = RequestMethod.POST )
public String implement( HttpServletRequest request , @RequestParam( "technology" ) String technology )
throws SessionException , SessionServerException , SessionMaintenanceException
{
this.getSession( PlayerSession.class , request ).implementTechnology( technology );
return this.redirect( "research#implemented" );
}
}

View file

@ -168,6 +168,11 @@
<groupId>com.deepclone.lw</groupId> <groupId>com.deepclone.lw</groupId>
<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version> <version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
</dependency> </dependency>
<dependency>
<artifactId>legacyworlds-server-beans-technologies</artifactId>
<groupId>com.deepclone.lw</groupId>
<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
</dependency>
<dependency> <dependency>
<artifactId>legacyworlds-server-beans-updates</artifactId> <artifactId>legacyworlds-server-beans-updates</artifactId>
<groupId>com.deepclone.lw</groupId> <groupId>com.deepclone.lw</groupId>