-- LegacyWorlds Beta 6
-- PostgreSQL database scripts
--
-- Various functions for in-game computations
--
-- Copyright(C) 2004-2010, DeepClone Development
-- --------------------------------------------------------


/*
 * Random value with deviation
 * 
 * Parameters:
 *		_mean		the mean value
 *		_deviation	the deviation
 *
 * Returns:
 *		?			a random value between _mean - _deviation and
 *						_mean + _deviation, with a higher probability
 *						of a value that is close to _mean
 */
DROP FUNCTION IF EXISTS verse.random_deviation( DOUBLE PRECISION , DOUBLE PRECISION );
CREATE FUNCTION verse.random_deviation( _mean DOUBLE PRECISION , _deviation DOUBLE PRECISION )
		RETURNS DOUBLE PRECISION
		STRICT VOLATILE
		SECURITY INVOKER
	AS $random_deviation$

DECLARE
	_result	DOUBLE PRECISION;

BEGIN
	_result := _deviation * RANDOM( ) ^ 2.5;
	IF RANDOM() < 0.5 THEN
		_result := -_result;
	END IF;
	RETURN _result + _mean;
END;
$random_deviation$ LANGUAGE PLPGSQL;


REVOKE EXECUTE
	ON FUNCTION verse.random_deviation( DOUBLE PRECISION , DOUBLE PRECISION )
	FROM PUBLIC;



/*
 * Randomly distribute some part of a total value
 * 
 * This function can be used when a total value must be distributed between
 * various items. It will compute the minimal and maximal values that may be
 * attributed, enforcing the fact that the whole value needs to be consumed
 * in the end, and that values must conform to a specific range expressed as
 * a mean value and a deviation.
 * 
 * The total value is assumed to be valid with regards to the mean and
 * deviation. That is:
 * 
 *		_parts * ( _mean - _deviation ) <= _quantity
 *		_parts * ( _mean + _deviation ) >= _quantity
 *
 * Parameters:
 *		_quantity		the total quantity left to distribute
 *		_parts			the amount of items left
 *		_mean			the result's mean value
 *		_deviation		the result's deviation
 *
 * Returns:
 * 		?				the value to attribute to the nex item
 */
DROP FUNCTION IF EXISTS verse.get_random_part( DOUBLE PRECISION , INT , DOUBLE PRECISION , DOUBLE PRECISION );
CREATE FUNCTION verse.get_random_part(
			_quantity DOUBLE PRECISION ,
			_parts INT ,
			_mean DOUBLE PRECISION ,
			_deviation DOUBLE PRECISION )
		RETURNS DOUBLE PRECISION
		STRICT VOLATILE
		SECURITY INVOKER
	AS $get_random_part$

DECLARE
	_min	DOUBLE PRECISION;
	_max	DOUBLE PRECISION;
	_n_mean	DOUBLE PRECISION;

BEGIN
	IF _parts = 1 THEN
		RETURN _quantity;
	END IF;

	_min := _quantity - ( _mean + _deviation ) * ( _parts - 1 );
	IF _min < _mean - _deviation THEN
		_min := _mean - _deviation;
	END IF;

	_max := _quantity - ( _mean - _deviation ) * ( _parts - 1 );
	IF _max > _mean + _deviation THEN
		_max := _mean + _deviation;
	END IF;
	
	IF _min = _max THEN
		RETURN _min;
	END IF;
	
	_n_mean := ( _min + _max ) * 0.5;
	RETURN verse.random_deviation( _n_mean , _n_mean - _min );
END;
$get_random_part$ LANGUAGE PLPGSQL;

REVOKE EXECUTE
	ON FUNCTION verse.get_random_part( DOUBLE PRECISION , INT , DOUBLE PRECISION , DOUBLE PRECISION )
	FROM PUBLIC;




--
-- sigma( x ) = exp( x ) / ( 1 + exp( x ) )
--

CREATE OR REPLACE FUNCTION verse.sigma( x REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
	SELECT ( CASE
		WHEN $1 < -100 THEN 0
		WHEN $1 > 100 THEN 1
		ELSE ( exp( $1 ) / ( 1 + exp( $1 ) ) )::REAL
	END );
$$ LANGUAGE SQL;



--
-- poly( x , a , b , c ) = ( a * x + b ) * x + c 
--

CREATE OR REPLACE FUNCTION verse.poly( x REAL , a REAL , b REAL , c REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
	SELECT ( $2 * $1 + $3 ) * $1 + $4;
$$ LANGUAGE SQL;



--
-- Happiness curve, K1 constant
--

CREATE OR REPLACE FUNCTION verse.hcc_const_k1( xmax REAL , ymax REAL , xlimit REAL , ylimit REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
	SELECT ( ( $4 - $2 ) / ( ( $3 - $1 ) ^ 2 ) )::REAL;
$$ LANGUAGE SQL;



--
-- Happiness curve, K2 constant
--

CREATE OR REPLACE FUNCTION verse.hcc_const_k2( ylimit REAL , yasymptote REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
	SELECT ( 2 * ( $1 - $2 ) )::REAL;
$$ LANGUAGE SQL;



--
-- Happiness curve, K3 constant
--

CREATE OR REPLACE FUNCTION verse.hcc_const_k3( xmax REAL , ymax REAL , xlimit REAL , ylimit REAL , yasymptote REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
	SELECT ( verse.hcc_const_k1( $1 , $2 , $3 , $4 ) * 4 * ( $3 - $1 ) / ( $5 - $4 ) ) ::REAL;
$$ LANGUAGE SQL;



--
-- Happiness curve, first part
--

CREATE OR REPLACE FUNCTION verse.hcc_part_1( x REAL , ymin REAL , ymax REAL , xmax REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
DECLARE
	v	REAL;
BEGIN
	v := ( ymin - ymax ) / xmax;
	RETURN verse.poly( x , ( v / xmax )::REAL , ( -2 * v )::REAL , ymin );
END;
$$ LANGUAGE plpgsql;



--
-- Happiness curve, second part
--

CREATE OR REPLACE FUNCTION verse.hcc_part_2( x REAL , xmax REAL , ymax REAL , xlimit REAL , ylimit REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
DECLARE
	k1	REAL;
BEGIN
	k1 := verse.hcc_const_k1( xmax , ymax , xlimit , ylimit );
	RETURN verse.poly( x , k1 , ( -2 * xmax * k1 )::REAL , ( ymax + k1 * xmax * xmax )::REAL );
END;
$$ LANGUAGE plpgsql;



--
-- Happiness curve, third part
--

CREATE OR REPLACE FUNCTION verse.hcc_part_3( x REAL , xmax REAL , ymax REAL , xlimit REAL , ylimit REAL , yasymptote REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
DECLARE
	k2	REAL;
	k3	REAL;
BEGIN
	k2 := verse.hcc_const_k2( ylimit , yasymptote );
	k3 := verse.hcc_const_k3( xmax , ymax , xlimit , ylimit , yasymptote );
	RETURN yasymptote + k2 * ( 1 - verse.sigma( ( k3 * ( x - xlimit ) ) )::REAL );
END;
$$ LANGUAGE plpgsql;



--
-- Happiness curve
--

CREATE OR REPLACE FUNCTION verse.happiness_curve( x REAL , ymin REAL , xmax REAL , ymax REAL , xlimit REAL , ylimit REAL , yasymptote REAL )
	RETURNS REAL
	STRICT IMMUTABLE SECURITY INVOKER
AS $$
	SELECT (CASE
		WHEN $1 < $3 THEN
			verse.hcc_part_1( $1 , $2 , $4 , $3 )
		WHEN $1 < $5 THEN
			verse.hcc_part_2( $1 , $3 , $4 , $5 , $6 )
		ELSE
			verse.hcc_part_3( $1 , $3 , $4 , $5 , $6 , $7 )
	END)
$$ LANGUAGE SQL;



--
-- Happiness computation
--

CREATE OR REPLACE FUNCTION verse.compute_happiness( population REAL , workers REAL , defence REAL , empsize INT )
		RETURNS REAL
		STRICT STABLE SECURITY INVOKER
	AS $$
DECLARE
	whappiness	REAL;
	dhappiness	REAL;
	shappiness	REAL;
BEGIN
	-- Work-related happiness
	whappiness := verse.happiness_curve(
		( workers / population )::REAL ,
		sys.get_constant( 'game.happiness.noEmployment' ) , 1.0 , 1.0 , 
		sys.get_constant( 'game.happiness.employmentLimit' ) , 0.5 , 0
	);

	-- Defence-related happiness
	dhappiness := verse.happiness_curve(
		( sys.get_constant( 'game.happiness.popPerDefencePoint' ) * defence / population )::REAL ,
		sys.get_constant( 'game.happiness.noDefence' ) , 1.0 , 1.0 , 
		sys.get_constant( 'game.happiness.defenceLimit' ) , 0.5 , 0
	);
	
	-- Influence of empire size
	shappiness := verse.happiness_curve(
		( empsize::REAL / sys.get_constant( 'game.happiness.idealEmpireSize' ) )::REAL ,
		sys.get_constant( 'game.happiness.smallEmpire' ) , 1.0 , 1.0 , 
		sys.get_constant( 'game.happiness.eSizeLimit' ) , 0.5 , 0
	);
	
	RETURN ( shappiness * ( whappiness + dhappiness ) / 2.0 )::REAL;
END;
$$ LANGUAGE plpgsql;



--
-- Production adjustment
--
CREATE OR REPLACE FUNCTION verse.adjust_production( prod REAL , happiness REAL )
	RETURNS REAL
	STRICT IMMUTABLE
	SECURITY INVOKER
AS $$
	SELECT ( CASE
		WHEN $2 < sys.get_constant( 'game.happiness.strike' ) THEN
			( $1 * ( 1 - ( $2 / sys.get_constant( 'game.happiness.strike' ) ) ) )::REAL
		ELSE
			$1
	END );
$$ LANGUAGE SQL;


--
-- Income computation
--

CREATE OR REPLACE FUNCTION verse.compute_income( population REAL , happiness REAL , cashprod REAL )
		RETURNS REAL
		STRICT STABLE
		SECURITY INVOKER
	AS $$
DECLARE
	base	REAL;
	badj	REAL;
	cprod	REAL;
BEGIN
	badj := ( 1 - verse.adjust_production( 1.0 , happiness ) ) * sys.get_constant( 'game.work.strikeEffect' );
	base := floor( population ) * sys.get_constant( 'game.work.population' ) * ( 1 - badj );
	cprod := verse.adjust_production( cashprod , happiness ) * sys.get_constant( 'game.work.factory' );
	RETURN cprod + base;
END;
$$ LANGUAGE plpgsql;