Research update

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

* Fixed a major bug in the constants registrar

* Added game.research.vacation constant (determines the rate of
research when players are in vacation mode)
This commit is contained in:
Emmanuel BENOîT 2012-04-02 13:29:43 +02:00
parent a14601df37
commit c7949e41cc
11 changed files with 414 additions and 85 deletions

View file

@ -128,9 +128,9 @@ public class ConstantsRegistrarBean
// Research // Research
String[] rcNames = { String[] rcNames = {
"basePoints" , "visibility.points" , "visibility.ratio" , "weightBase" "basePoints" , "visibility.points" , "visibility.ratio" , "weightBase" , "vacation"
}; };
for ( int i = 0 ; i < wcNames.length ; i++ ) { for ( int i = 0 ; i < rcNames.length ; i++ ) {
rcNames[ i ] = "game.research." + rcNames[ i ]; rcNames[ i ] = "game.research." + rcNames[ i ];
} }
cat = "Research & technologies"; cat = "Research & technologies";
@ -144,6 +144,8 @@ public class ConstantsRegistrarBean
+ "to compute the actual weight when determining how research points are distributed between " + "to compute the actual weight when determining how research points are distributed between "
+ "an empire's in-progress research."; + "an empire's in-progress research.";
defs.add( new ConstantDefinition( rcNames[ 3 ] , cat , cDesc , 10.0 , 1.0 , true ) ); defs.add( new ConstantDefinition( rcNames[ 3 ] , cat , cDesc , 10.0 , 1.0 , true ) );
cDesc = "Research points multiplier used when the player is on vacation.";
defs.add( new ConstantDefinition( rcNames[ 4 ] , cat , cDesc , 0.1 , 0.01 , 0.99 ) );
// Vacation mode // Vacation mode
cDesc = "Initial vacation credits."; cDesc = "Initial vacation credits.";
@ -205,21 +207,28 @@ public class ConstantsRegistrarBean
// Accounts - warnings // Accounts - warnings
cDesc = "Amount of warnings that triggers an automatic ban request."; cDesc = "Amount of warnings that triggers an automatic ban request.";
defs.add( new ConstantDefinition( "accounts.warnings.autoBan" , "Accounts - Warnings" , cDesc , 3.0 , 1.0 , true ) ); defs.add( new ConstantDefinition( "accounts.warnings.autoBan" , "Accounts - Warnings" , cDesc , 3.0 , 1.0 ,
true ) );
cDesc = "Period after a warning is received during which additional warnings will be ignored (seconds)."; cDesc = "Period after a warning is received during which additional warnings will be ignored (seconds).";
defs.add( new ConstantDefinition( "accounts.warnings.grace" , "Accounts - Warnings" , cDesc , 7200.0 , 60.0 , true ) ); defs.add( new ConstantDefinition( "accounts.warnings.grace" , "Accounts - Warnings" , cDesc , 7200.0 , 60.0 ,
true ) );
cDesc = "Time after which warnings are decreased (expressed in units as defined by a.w.expiration.units)."; cDesc = "Time after which warnings are decreased (expressed in units as defined by a.w.expiration.units).";
defs.add( new ConstantDefinition( "accounts.warnings.expiration" , "Accounts - Warnings" , cDesc , 60.0 , 1.0 , true ) ); defs.add( new ConstantDefinition( "accounts.warnings.expiration" , "Accounts - Warnings" , cDesc , 60.0 , 1.0 ,
true ) );
cDesc = "Units used to express warning expiration time (seconds)."; cDesc = "Units used to express warning expiration time (seconds).";
defs.add( new ConstantDefinition( "accounts.warnings.expiration.units" , "Accounts - Warnings" , cDesc , 86400.0 , 1.0 , true ) ); defs.add( new ConstantDefinition( "accounts.warnings.expiration.units" , "Accounts - Warnings" , cDesc ,
86400.0 , 1.0 , true ) );
// Account inactivity // Account inactivity
cDesc = "Time units (seconds)"; cDesc = "Time units (seconds)";
defs.add( new ConstantDefinition( "accounts.inactivity.units" , "Accounts - Inactivity" , cDesc , oneWeek , 3600.0 , true ) ); defs.add( new ConstantDefinition( "accounts.inactivity.units" , "Accounts - Inactivity" , cDesc , oneWeek ,
3600.0 , true ) );
cDesc = "Time after which the inactivity warning e-mail is to be sent, expressed using units defined by a.i.units."; cDesc = "Time after which the inactivity warning e-mail is to be sent, expressed using units defined by a.i.units.";
defs.add( new ConstantDefinition( "accounts.inactivity.warningMail" , "Accounts - Inactivity" , cDesc , 3.0 , 1.0 , true ) ); defs.add( new ConstantDefinition( "accounts.inactivity.warningMail" , "Accounts - Inactivity" , cDesc , 3.0 ,
1.0 , true ) );
cDesc = "Time between the inactivity warning e-mail and actual account deletion, expressed using units defined by a.i.units."; cDesc = "Time between the inactivity warning e-mail and actual account deletion, expressed using units defined by a.i.units.";
defs.add( new ConstantDefinition( "accounts.inactivity.deletion" , "Accounts - Inactivity" , cDesc , 1.0 , 1.0 , true ) ); defs.add( new ConstantDefinition( "accounts.inactivity.deletion" , "Accounts - Inactivity" , cDesc , 1.0 , 1.0 ,
true ) );
// Bug reports // Bug reports
cDesc = "Amount of credits granted for low priority bug reports."; cDesc = "Amount of credits granted for low priority bug reports.";

View file

@ -21,7 +21,7 @@
* _empire The empire's identifier * _empire The empire's identifier
* _technology The string identifier for the technology to implement * _technology The string identifier for the technology to implement
*/ */
DROP FUNCTION emp.technology_implement( INT , TEXT ); DROP FUNCTION IF EXISTS emp.technology_implement( INT , TEXT );
CREATE FUNCTION emp.technology_implement( _empire INT , _technology TEXT ) CREATE FUNCTION emp.technology_implement( _empire INT , _technology TEXT )
RETURNS BOOLEAN RETURNS BOOLEAN
LANGUAGE PLPGSQL LANGUAGE PLPGSQL
@ -291,6 +291,56 @@ GRANT EXECUTE
/*
* Compute an empire's total research points
* ------------------------------------------
*
* Obtain an empire's total research points by adding the happiness-adjusted
* value for each planet, then applying the global modifier for vacation mode
* if necessary.
*
* FIXME: time factor is hard-coded
*
* Parameters:
* _empire The empire's identifier
* _on_vacation TRUE if the player is on vacation, FALSE otherwise
*
* Returns:
* ? The amount of research points.
*/
DROP FUNCTION IF EXISTS emp.research_get_points( INT , BOOLEAN );
CREATE FUNCTION emp.research_get_points( _empire INT , _on_vacation BOOLEAN )
RETURNS DOUBLE PRECISION
LANGUAGE SQL
STRICT STABLE
SECURITY INVOKER
AS $research_get_points$
SELECT SUM( verse.adjust_production(
_planet.population * sys.get_constant( 'game.research.basePoints' ) ,
_happiness.current / _planet.population
) ) * ( CASE
WHEN $2 THEN
sys.get_constant( 'game.research.vacation' )
ELSE
1.0
END )::DOUBLE PRECISION
FROM emp.planets _emp_planet
INNER JOIN verse.planets _planet
ON _emp_planet.planet_id = _planet.name_id
INNER JOIN verse.planet_happiness _happiness
USING ( planet_id )
WHERE _emp_planet.empire_id = $1;
$research_get_points$;
REVOKE EXECUTE
ON FUNCTION emp.research_get_points( INT , BOOLEAN )
FROM PUBLIC;
/* /*
* Technology visibility view * Technology visibility view

View file

@ -3,90 +3,188 @@
-- --
-- Game updates - empire research -- Game updates - empire research
-- --
-- Copyright(C) 2004-2010, DeepClone Development -- Copyright(C) 2004-2012, DeepClone Development
-- -------------------------------------------------------- -- --------------------------------------------------------
CREATE OR REPLACE FUNCTION sys.process_empire_research_updates( c_tick BIGINT ) /*
RETURNS VOID * Empire update data
* -------------------
*
* This type can be used to return empires along with their "on vacation"
* status.
*
* FIXME: it should probably be somewhere else, but for now this is the only
* file that uses it.
*/
DROP TYPE IF EXISTS sys.empire_update_type CASCADE;
CREATE TYPE sys.empire_update_type AS (
/* The empire's identifier */
empire_id INT ,
/* TRUE if the player is on vacation, FALSE otherwise */
on_vacation BOOLEAN
);
/*
* Lock records and list empires which require a research update
* --------------------------------------------------------------
*
* This function will lock all records needed for a research update, and
* return the list of empires to be updated. These empires, in addition to
* being marked for update, must possess planets and have in-progress
* research.
*
* Parameters:
* _tick The identifier of the current update cycle
*
* Returns a set of:
* empire_id The empire's identifier
* on_vacation TRUE if the player is on vacation, FALSE otherwise
*/
DROP FUNCTION IF EXISTS sys.gu_research_get_empires( BIGINT ) CASCADE;
CREATE FUNCTION sys.gu_research_get_empires( _tick BIGINT )
RETURNS SETOF sys.empire_update_type
LANGUAGE SQL
STRICT VOLATILE STRICT VOLATILE
SECURITY INVOKER SECURITY INVOKER
AS $$ AS $gu_research_get_empires$
SELECT DISTINCT * FROM (
SELECT _empire.name_id AS empire_id ,
( _vacation.status IS NOT NULL AND
_vacation.status = 'PROCESSED' ) AS on_vacation
FROM sys.updates _upd_sys
INNER JOIN emp.empires_updates _emp_update
USING ( updtgt_id , updtype_id , update_id )
INNER JOIN emp.empires _empire
USING ( name_id )
INNER JOIN emp.technologies_v2 _emp_tech
ON _emp_tech.empire_id = _empire.name_id
INNER JOIN defs.technologies _tech
USING ( technology_name_id )
INNER JOIN emp.planets _emp_planet
USING ( empire_id )
INNER JOIN verse.planets _planet
ON _emp_planet.planet_id = _planet.name_id
INNER JOIN verse.planet_happiness _happiness
USING ( planet_id )
INNER JOIN naming.empire_names _emp_name
ON _emp_name.id = _empire.name_id
INNER JOIN users.credentials _user
ON _user.address_id = _emp_name.owner_id
LEFT OUTER JOIN users.vacations _vacation
ON _vacation.account_id = _emp_name.owner_id
WHERE _upd_sys.update_last = $1
AND _upd_sys.update_state = 'PROCESSING'
AND _emp_tech.emptech_state = 'RESEARCH'
FOR UPDATE OF _upd_sys , _emp_update , _emp_tech
FOR SHARE OF _empire , _tech , _emp_planet , _planet ,
_happiness , _emp_name , _user
) _sub;
$gu_research_get_empires$;
REVOKE EXECUTE
ON FUNCTION sys.gu_research_get_empires( BIGINT )
FROM PUBLIC;
/*
* Update research for a single empire
* ------------------------------------
*
* This stored procedure updates research points for all in-progress research
* of a single empire.
*
* Parameters:
* _empire_id The empire's identifier
* _on_vacation TRUE if the player is on vacation, FALSE otherwise
*/
DROP FUNCTION IF EXISTS sys.gu_research_update_empire( INT , BOOLEAN ) CASCADE;
CREATE FUNCTION sys.gu_research_update_empire( _empire INT , _on_vacation BOOLEAN )
RETURNS VOID
LANGUAGE PLPGSQL
STRICT VOLATILE
SECURITY INVOKER
AS $gu_research_update_empire$
DECLARE DECLARE
rec RECORD; _points DOUBLE PRECISION;
r_points REAL; _record RECORD;
tu_rec RECORD;
BEGIN BEGIN
-- Lock empires for update and planets for share
PERFORM e.name_id
FROM sys.updates _upd_sys
INNER JOIN emp.empires_updates eu
USING ( updtgt_id , updtype_id , update_id )
INNER JOIN emp.empires e USING ( name_id )
INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
INNER JOIN verse.planets p ON p.name_id = ep.planet_id
WHERE _upd_sys.update_last = c_tick
AND _upd_sys.update_state = 'PROCESSING'
FOR UPDATE OF e
FOR SHARE OF ep , p;
-- Process empires _points := emp.research_get_points( _empire , _on_vacation );
FOR rec IN SELECT e.name_id AS id , ( v.status = 'PROCESSED' ) AS on_vacation , FOR _record IN
sum( p.population ) AS population SELECT _emp_tech.technology_name_id , _emp_tech.emptech_points , _emp_tech.emptech_priority ,
FROM sys.updates _upd_sys _points * _weights.emptech_weight / ( _totals.emptech_total_weight * 1440 ) AS emptech_new_points ,
INNER JOIN emp.empires_updates eu _def.technology_points::DOUBLE PRECISION AS technology_points
USING ( updtgt_id , updtype_id , update_id ) FROM emp.technologies_v2 _emp_tech
INNER JOIN emp.empires e USING ( name_id ) INNER JOIN emp.research_weights_view _weights
INNER JOIN emp.planets ep ON ep.empire_id = e.name_id USING ( empire_id , technology_name_id )
INNER JOIN verse.planets p ON p.name_id = ep.planet_id INNER JOIN emp.research_total_weights_view _totals
INNER JOIN naming.empire_names en ON en.id = e.name_id USING ( empire_id )
LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id INNER JOIN defs.technologies _def
WHERE _upd_sys.update_last = c_tick USING ( technology_name_id )
AND _upd_sys.update_state = 'PROCESSING' WHERE _emp_tech.empire_id = _empire
GROUP BY e.name_id , v.status AND _emp_tech.emptech_state = 'RESEARCH'
LOOP LOOP
-- Insert any missing tech line
INSERT INTO emp.technologies ( empire_id , line_id )
SELECT rec.id , l.name_id
FROM tech.lines l
LEFT OUTER JOIN emp.technologies t
ON t.line_id = l.name_id AND t.empire_id = rec.id
WHERE t.empire_id IS NULL;
-- Compute research output IF _record.emptech_points + _record.emptech_new_points >= _record.technology_points THEN
r_points := rec.population * sys.get_constant( 'game.work.rpPerPopUnit' ) / 1440.0; UPDATE emp.technologies_v2
IF rec.on_vacation SET emptech_state = 'PENDING' ,
THEN emptech_points = NULL ,
r_points := r_points / sys.get_constant( 'vacation.researchDivider' ); emptech_priority = NULL
WHERE technology_name_id = _record.technology_name_id
AND empire_id = _empire;
ELSE
UPDATE emp.technologies_v2
SET emptech_points = emptech_points + _record.emptech_new_points
WHERE technology_name_id = _record.technology_name_id
AND empire_id = _empire;
END IF; END IF;
-- Update technologies where: END LOOP;
-- 1) the level actually exists and
-- 2) accumulated points haven't reach the level's
FOR tu_rec IN SELECT t.line_id AS line_id , t.accumulated AS accumulated ,
l.points AS points , ( l.points - t.accumulated ) AS diff ,
l.id AS level_id
FROM emp.technologies t
INNER JOIN tech.levels l ON l.line_id = t.line_id
AND l.level = t.level AND t.accumulated < l.points
WHERE t.empire_id = rec.id
FOR UPDATE OF t
LOOP
UPDATE emp.technologies t SET accumulated = ( CASE
WHEN tu_rec.diff <= r_points THEN tu_rec.points
ELSE tu_rec.accumulated + r_points
END )
WHERE t.line_id = tu_rec.line_id AND t.empire_id = rec.id;
-- Send message
IF tu_rec.diff <= r_points
THEN
PERFORM events.tech_ready_event( rec.id , tu_rec.level_id );
END IF;
END LOOP;
END LOOP;
END; END;
$$ LANGUAGE plpgsql; $gu_research_update_empire$;
REVOKE EXECUTE
ON FUNCTION sys.gu_research_update_empire( INT , BOOLEAN )
FROM PUBLIC;
/*
* Process a batch of empire research updates
* -------------------------------------------
*
* Update all empires in the batch which have both in-progress research and
* planets.
*
* Parameters:
* _tick The identifier of the current update cycle
*/
DROP FUNCTION IF EXISTS sys.process_empire_research_updates( BIGINT ) CASCADE;
CREATE FUNCTION sys.process_empire_research_updates( _tick BIGINT )
RETURNS VOID
LANGUAGE SQL
STRICT VOLATILE
SECURITY INVOKER
AS $process_empire_research_updates$
SELECT sys.gu_research_update_empire( empire_id , on_vacation )
FROM sys.gu_research_get_empires( $1::BIGINT );
$process_empire_research_updates$;
REVOKE EXECUTE
ON FUNCTION sys.process_empire_research_updates( BIGINT )
FROM PUBLIC;
SELECT sys.register_update_type( 'Empires' , 'EmpireResearch' , SELECT sys.register_update_type( 'Empires' , 'EmpireResearch' ,
'Empire research points are being attributed to technologies.' , 'Empire research points are being attributed to technologies.' ,

View file

@ -0,0 +1,24 @@
/*
* Test the sys.gu_research_get_empires() function
*/
BEGIN;
\i utils/common-setup/setup-gu-research-get-empires-test.sql
SELECT plan( 2 );
SELECT diag_test_name( 'gu_research_get_empires() - Selected empires and vacation state' );
SELECT set_eq( $$
SELECT * FROM sys.gu_research_get_empires( 0::BIGINT );
$$ , $$ VALUES (
_get_emp_name( 'emp4') , FALSE
) , (
_get_emp_name( 'emp5') , TRUE
) $$ );
SELECT diag_test_name( 'gu_research_get_empires() - No results with "bad" update identifier' );
SELECT is_empty( $$
SELECT * FROM sys.gu_research_get_empires( 12::BIGINT );
$$ );
SELECT * FROM finish( );
ROLLBACK;

View file

@ -0,0 +1,11 @@
/*
* Test privileges on sys.gu_research_get_empires()
*/
BEGIN;
SELECT plan( 1 );
SELECT diag_test_name( 'sys.gu_research_get_empires() - Privileges' );
SELECT throws_ok( 'SELECT sys.gu_research_get_empires( 0::BIGINT )' , 42501 );
SELECT * FROM finish( );
ROLLBACK;

View file

@ -0,0 +1,137 @@
/*
* Common setup for research update locking and empire selection
* --------------------------------------------------------------
*
* Empires are selected when:
* - they are part of the current update batch,
* - they possess planets,
* - they have in-progress research entries.
* All related data is locked: the update entries, empire, empire technology
* records, technology definitions, planets (including ownership records and
* happiness records), as well as the empire's name and associated account.
*/
-- Make sure the only update type left is EmpireResearch
DELETE FROM sys.update_types
WHERE updtype_name <> 'EmpireResearch';
\i utils/strings.sql
\i utils/resources.sql
\i utils/accounts.sql
\i utils/naming.sql
\i utils/universe.sql
-- Create some technology definitions, dropping constraints on unused columns
ALTER TABLE defs.technologies
ALTER technology_description_id DROP NOT NULL ,
ALTER technology_discovery_id DROP NOT NULL ,
ALTER technology_category_id DROP NOT NULL ,
ALTER technology_price DROP NOT NULL;
SELECT _create_test_strings( 3 , 'tech' );
INSERT INTO defs.technologies ( technology_name_id , technology_points )
SELECT id , 100.0 FROM defs.strings
WHERE name LIKE 'tech%';
-- Create the empire names and planets
SELECT _create_emp_names( 6 , 'emp' );
SELECT _create_raw_planets( 4 , 'planet' );
INSERT INTO verse.planet_happiness( planet_id , target , current )
SELECT id , 0.75 , 1500
FROM naming.map_names
WHERE name LIKE 'planet%';
/*
* Empire 1 has no planets and no in-progress research. It will not be
* selected or locked.
*/
INSERT INTO emp.empires( name_id , cash )
VALUES( _get_emp_name( 'emp1' ) , 100.0 );
INSERT INTO emp.technologies_v2 (
empire_id , technology_name_id ,
emptech_state , emptech_points , emptech_priority )
SELECT _get_emp_name( 'emp1' ) , technology_name_id , 'KNOWN' , NULL , NULL
FROM defs.technologies;
/*
* Empire 2 has planets, but no in-progress research. It will not be
* selected.
*/
INSERT INTO emp.empires( name_id , cash )
VALUES( _get_emp_name( 'emp2' ) , 100.0 );
INSERT INTO emp.technologies_v2 (
empire_id , technology_name_id ,
emptech_state , emptech_points , emptech_priority )
SELECT _get_emp_name( 'emp2' ) , technology_name_id , 'KNOWN' , NULL , NULL
FROM defs.technologies;
INSERT INTO emp.planets( empire_id , planet_id )
VALUES( _get_emp_name( 'emp2' ) , _get_map_name( 'planet1' ) );
/*
* Empire 3 has in-progress research, but no planets. It will not be selected.
*/
INSERT INTO emp.empires( name_id , cash )
VALUES( _get_emp_name( 'emp3' ) , 100.0 );
INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
SELECT _get_emp_name( 'emp3' ) , technology_name_id
FROM defs.technologies;
/*
* Empire 4 has in-progress research and planets. It has no vacation mode
* record. It will be selected.
*/
INSERT INTO emp.empires( name_id , cash )
VALUES( _get_emp_name( 'emp4' ) , 100.0 );
INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
SELECT _get_emp_name( 'emp4' ) , technology_name_id
FROM defs.technologies;
INSERT INTO emp.planets( empire_id , planet_id )
VALUES( _get_emp_name( 'emp4' ) , _get_map_name( 'planet2' ) );
/*
* Empire 5 has in-progress research, planets and a vacation mode record.
* It will be selected.
*
* Note: we need to add an entry to users.active_accounts for vacation mode
* records to work.
*/
INSERT INTO emp.empires( name_id , cash )
VALUES( _get_emp_name( 'emp5' ) , 100.0 );
INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
SELECT _get_emp_name( 'emp5' ) , technology_name_id
FROM defs.technologies;
INSERT INTO emp.planets( empire_id , planet_id )
VALUES( _get_emp_name( 'emp5' ) , _get_map_name( 'planet3' ) );
INSERT INTO users.active_accounts( credentials_id ,vacation_credits )
SELECT id , 12
FROM users.addresses
WHERE address = 'emp5@example.org';
INSERT INTO users.vacations ( account_id , since , status )
SELECT id , NOW() , 'PROCESSED'
FROM users.addresses
WHERE address = 'emp5@example.org';
/*
* Empire 6 is similar to empire 4, but will not be marked for update in the
* current batch.
*/
INSERT INTO emp.empires( name_id , cash )
VALUES( _get_emp_name( 'emp6' ) , 100.0 );
INSERT INTO emp.technologies_v2 ( empire_id , technology_name_id )
SELECT _get_emp_name( 'emp6' ) , technology_name_id
FROM defs.technologies;
INSERT INTO emp.planets( empire_id , planet_id )
VALUES( _get_emp_name( 'emp6' ) , _get_map_name( 'planet4' ) );
/* Speaking of current batch - set it up */
UPDATE sys.updates su
SET update_state = 'PROCESSING' , update_last = 0
FROM emp.empires_updates eu
WHERE eu.update_id = su.update_id
AND eu.name_id <> _get_emp_name( 'emp6' );
UPDATE sys.updates su
SET update_state = 'PROCESSED' , update_last = 0
FROM emp.empires_updates eu
WHERE eu.update_id = su.update_id
AND eu.name_id = _get_emp_name( 'emp6' );