442 lines
11 KiB
C++
442 lines
11 KiB
C++
/*
|
|
** Command & Conquer Generals Zero Hour(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 3 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// (c) 2001-2003 Electronic Arts Inc. //
|
|
// //
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// RandomValue.cpp
|
|
// Pseudo-random number generators
|
|
// Author: Michael S. Booth, January 1998
|
|
|
|
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
|
|
|
|
|
|
#include "Lib/BaseType.h"
|
|
#include "Common/RandomValue.h"
|
|
#include "Common/CRC.h"
|
|
#include "Common/Debug.h"
|
|
#include "GameLogic/GameLogic.h"
|
|
|
|
//#define DETERMINISTIC // to allow repetition for debugging
|
|
|
|
#ifdef _INTERNAL
|
|
// for occasional debugging...
|
|
//#pragma optimize("", off)
|
|
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
|
|
#endif
|
|
|
|
#undef DEBUG_RANDOM_CLIENT
|
|
#undef DEBUG_RANDOM_LOGIC
|
|
#undef DEBUG_RANDOM_AUDIO
|
|
|
|
//#define DEBUG_RANDOM_CLIENT
|
|
//#define DEBUG_RANDOM_LOGIC
|
|
//#define DEBUG_RANDOM_AUDIO
|
|
|
|
static const Real theMultFactor = 1.0f / (powf(2, 8 * sizeof(UnsignedInt)) - 1.0f);
|
|
|
|
// Initial seed values.
|
|
static UnsignedInt theGameClientSeed[6] =
|
|
{
|
|
0xf22d0e56L, 0x883126e9L, 0xc624dd2fL, 0x702c49cL, 0x9e353f7dL, 0x6fdf3b64L
|
|
};
|
|
|
|
static UnsignedInt theGameAudioSeed[6] =
|
|
{
|
|
0xf22d0e56L, 0x883126e9L, 0xc624dd2fL, 0x702c49cL, 0x9e353f7dL, 0x6fdf3b64L
|
|
};
|
|
|
|
static UnsignedInt theGameLogicBaseSeed = 0;
|
|
static UnsignedInt theGameLogicSeed[6] =
|
|
{
|
|
0xf22d0e56L, 0x883126e9L, 0xc624dd2fL, 0x702c49cL, 0x9e353f7dL, 0x6fdf3b64L
|
|
};
|
|
|
|
// Add with carry. SUM is replaced with A + B + C, C is replaced with 1 if there was a carry, 0 if there wasn't. A carry occurred if the sum is less than one of the inputs. This is addition, so carry can never be more than one.
|
|
#define ADC(SUM, A, B, C) SUM = (A) + (B) + (C); C = ((SUM < (A)) || (SUM < (B)))
|
|
|
|
static UnsignedInt randomValue(UnsignedInt *seed)
|
|
{
|
|
UnsignedInt ax;
|
|
UnsignedInt c = 0;
|
|
|
|
|
|
ADC(ax, seed[5], seed[4], c); /* mov ax,seed+20 */
|
|
/* add ax,seed+16 */
|
|
seed[4] = ax; /* mov seed+8,ax */
|
|
|
|
ADC(ax, ax, seed[3], c); /* adc ax,seed+12 */
|
|
seed[3] = ax; /* mov seed+12,ax */
|
|
|
|
ADC(ax, ax, seed[2], c); /* adc ax,seed+8 */
|
|
seed[2] = ax; /* mov seed+8,ax */
|
|
|
|
ADC(ax, ax, seed[1], c); /* adc ax,seed+4 */
|
|
seed[1] = ax; /* mov seed+4,ax */
|
|
|
|
ADC(ax, ax, seed[0], c); /* adc ax,seed+0 */
|
|
seed[0] = ax; /* mov seed+0,ax */
|
|
|
|
/* Increment seed array, bubbling up the carries. */
|
|
if (!++seed[5])
|
|
{
|
|
if (!++seed[4])
|
|
{
|
|
if (!++seed[3])
|
|
{
|
|
if (!++seed[2])
|
|
{
|
|
if (!++seed[1])
|
|
{
|
|
++seed[0];
|
|
++ax;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return(ax);
|
|
}
|
|
|
|
static void seedRandom(UnsignedInt SEED, UnsignedInt *seed)
|
|
{
|
|
UnsignedInt ax;
|
|
|
|
ax = SEED; /* mov eax,SEED */
|
|
ax += 0xf22d0e56; /* add eax,0f22d0e56h */
|
|
seed[0] = ax; /* mov seed,eax */
|
|
ax += 0x883126e9 - 0xf22d0e56; /* add eax,0883126e9h-0f22d0e56h */
|
|
seed[1] = ax; /* mov seed+4,eax */
|
|
ax += 0xc624dd2f - 0x883126e9; /* add eax,0c624dd2fh-0883126e9h */
|
|
seed[2] = ax; /* mov seed+8,eax */
|
|
ax += 0x0702c49c - 0xc624dd2f; /* add eax,00702c49ch-0c624dd2fh */
|
|
seed[3] = ax; /* mov seed+12,eax */
|
|
ax += 0x9e353f7d - 0x0702c49c; /* add eax,09e353f7dh-00702c49ch */
|
|
seed[4] = ax; /* mov seed+16,eax */
|
|
ax += 0x6fdf3b64 - 0x9e353f7d; /* add eax,06fdf3b64h-09e353f7dh */
|
|
seed[5] = ax; /* mov seed+20,eax */
|
|
}
|
|
|
|
//
|
|
// It is necessary to separate the GameClient and GameLogic usage of random
|
|
// values to ensure that the GameLogic remains deterministic, regardless
|
|
// of the effects displayed on the GameClient.
|
|
//
|
|
|
|
UnsignedInt GetGameLogicRandomSeed( void )
|
|
{
|
|
return theGameLogicBaseSeed;
|
|
}
|
|
|
|
UnsignedInt GetGameLogicRandomSeedCRC( void )
|
|
{
|
|
CRC c;
|
|
c.computeCRC(theGameLogicSeed, 6*sizeof(UnsignedInt));
|
|
return c.get();
|
|
}
|
|
|
|
void InitRandom( void )
|
|
{
|
|
#ifdef DETERMINISTIC
|
|
// needs to be the same every time
|
|
seedRandom(0, theGameClientSeed);
|
|
seedRandom(0, theGameAudioSeed);
|
|
seedRandom(0, theGameLogicSeed);
|
|
theGameLogicBaseSeed = 0;
|
|
#else
|
|
time_t seconds = time( NULL );
|
|
|
|
seedRandom(seconds, theGameAudioSeed);
|
|
seedRandom(seconds, theGameClientSeed);
|
|
seedRandom(seconds, theGameLogicSeed);
|
|
theGameLogicBaseSeed = seconds;
|
|
#endif
|
|
}
|
|
|
|
void InitRandom( UnsignedInt seed )
|
|
{
|
|
seedRandom(seed, theGameAudioSeed);
|
|
seedRandom(seed, theGameClientSeed);
|
|
seedRandom(seed, theGameLogicSeed);
|
|
theGameLogicBaseSeed = seed;
|
|
#ifdef DEBUG_RANDOM_LOGIC
|
|
DEBUG_LOG(( "InitRandom %08lx\n",seed));
|
|
#endif
|
|
}
|
|
|
|
void InitGameLogicRandom( UnsignedInt seed )
|
|
{
|
|
#ifdef DETERMINISTIC
|
|
// needs to be the same every time
|
|
seedRandom(0, theGameLogicSeed);
|
|
theGameLogicBaseSeed = 0;
|
|
#else
|
|
seedRandom(seed, theGameLogicSeed);
|
|
theGameLogicBaseSeed = seed;
|
|
#endif
|
|
#ifdef DEBUG_RANDOM_LOGIC
|
|
DEBUG_LOG(( "InitRandom Logic %08lx\n",seed));
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Integer random value
|
|
//
|
|
Int GetGameLogicRandomValue( int lo, int hi, char *file, int line )
|
|
{
|
|
//Int delta = hi - lo + 1;
|
|
//Int rval;
|
|
|
|
//if (delta == 0)
|
|
//return hi;
|
|
|
|
//rval = ((Int)(randomValue(theGameLogicSeed) % delta)) + lo;
|
|
|
|
UnsignedInt delta = hi - lo + 1;
|
|
//UnsignedInt temp;
|
|
Int rval;
|
|
|
|
if (delta == 0)
|
|
return hi;
|
|
|
|
rval = ((Int)(randomValue(theGameLogicSeed) % delta)) + lo;
|
|
//temp = randomValue(theGameLogicSeed);
|
|
//temp = temp % delta;
|
|
|
|
//rval = temp + lo;
|
|
|
|
/**/
|
|
#ifdef DEBUG_RANDOM_LOGIC
|
|
DEBUG_LOG(( "%d: GetGameLogicRandomValue = %d (%d - %d), %s line %d\n",
|
|
TheGameLogic->getFrame(), rval, lo, hi, file, line ));
|
|
#endif
|
|
/**/
|
|
|
|
return rval;
|
|
}
|
|
|
|
//
|
|
// Integer random value
|
|
//
|
|
Int GetGameClientRandomValue( int lo, int hi, char *file, int line )
|
|
{
|
|
UnsignedInt delta = hi - lo + 1;
|
|
Int rval;
|
|
|
|
if (delta == 0)
|
|
return hi;
|
|
|
|
rval = ((Int)(randomValue(theGameClientSeed) % delta)) + lo;
|
|
|
|
/**/
|
|
#ifdef DEBUG_RANDOM_CLIENT
|
|
DEBUG_LOG(( "%d: GetGameClientRandomValue = %d (%d - %d), %s line %d\n",
|
|
TheGameLogic->getFrame(), rval, lo, hi, file, line ));
|
|
#endif
|
|
/**/
|
|
|
|
return rval;
|
|
}
|
|
|
|
//
|
|
// Integer random value
|
|
//
|
|
Int GetGameAudioRandomValue( int lo, int hi, char *file, int line )
|
|
{
|
|
UnsignedInt delta = hi - lo + 1;
|
|
Int rval;
|
|
|
|
if (delta == 0)
|
|
return hi;
|
|
|
|
rval = ((Int)(randomValue(theGameAudioSeed) % delta)) + lo;
|
|
|
|
/**/
|
|
#ifdef DEBUG_RANDOM_AUDIO
|
|
DEBUG_LOG(( "%d: GetGameAudioRandomValue = %d (%d - %d), %s line %d\n",
|
|
TheGameLogic->getFrame(), rval, lo, hi, file, line ));
|
|
#endif
|
|
/**/
|
|
|
|
return rval;
|
|
}
|
|
|
|
//
|
|
// Real valued random value
|
|
//
|
|
Real GetGameLogicRandomValueReal( Real lo, Real hi, char *file, int line )
|
|
{
|
|
Real delta = hi - lo;
|
|
Real rval;
|
|
|
|
if (delta <= 0.0f)
|
|
return hi;
|
|
|
|
rval = ((Real)(randomValue(theGameLogicSeed)) * theMultFactor ) * delta + lo;
|
|
|
|
DEBUG_ASSERTCRASH( rval >= lo && rval <= hi, ("Bad random val"));
|
|
/**/
|
|
#ifdef DEBUG_RANDOM_LOGIC
|
|
DEBUG_LOG(( "%d: GetGameLogicRandomValueReal = %f, %s line %d\n",
|
|
TheGameLogic->getFrame(), rval, file, line ));
|
|
#endif
|
|
/**/
|
|
|
|
return rval;
|
|
}
|
|
|
|
//
|
|
// Real valued random value
|
|
//
|
|
Real GetGameClientRandomValueReal( Real lo, Real hi, char *file, int line )
|
|
{
|
|
Real delta = hi - lo;
|
|
Real rval;
|
|
|
|
if (delta <= 0.0f)
|
|
return hi;
|
|
|
|
rval = ((Real)(randomValue(theGameClientSeed)) * theMultFactor ) * delta + lo;
|
|
|
|
DEBUG_ASSERTCRASH( rval >= lo && rval <= hi, ("Bad random val"));
|
|
/**/
|
|
#ifdef DEBUG_RANDOM_CLIENT
|
|
DEBUG_LOG(( "%d: GetGameClientRandomValueReal = %f, %s line %d\n",
|
|
TheGameLogic->getFrame(), rval, file, line ));
|
|
#endif
|
|
/**/
|
|
|
|
return rval;
|
|
}
|
|
|
|
//
|
|
// Real valued random value
|
|
//
|
|
Real GetGameAudioRandomValueReal( Real lo, Real hi, char *file, int line )
|
|
{
|
|
Real delta = hi - lo;
|
|
Real rval;
|
|
|
|
if (delta <= 0.0f)
|
|
return hi;
|
|
|
|
rval = ((Real)(randomValue(theGameAudioSeed)) * theMultFactor ) * delta + lo;
|
|
|
|
DEBUG_ASSERTCRASH( rval >= lo && rval <= hi, ("Bad random val"));
|
|
/**/
|
|
#ifdef DEBUG_RANDOM_AUDIO
|
|
DEBUG_LOG(( "%d: GetGameAudioRandomValueReal = %f, %s line %d\n",
|
|
TheGameLogic->getFrame(), rval, file, line ));
|
|
#endif
|
|
/**/
|
|
|
|
return rval;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
// GameClientRandomVariable
|
|
//
|
|
|
|
/*static*/ const char *GameClientRandomVariable::DistributionTypeNames[] =
|
|
{
|
|
"CONSTANT", "UNIFORM", "GAUSSIAN", "TRIANGULAR", "LOW_BIAS", "HIGH_BIAS"
|
|
};
|
|
|
|
/**
|
|
define the range of random values, and the distribution of values
|
|
*/
|
|
void GameClientRandomVariable::setRange( Real low, Real high, DistributionType type )
|
|
{
|
|
DEBUG_ASSERTCRASH(!(m_type == CONSTANT && m_low != m_high), ("CONSTANT GameClientRandomVariables should have low == high"));
|
|
m_low = low;
|
|
m_high = high;
|
|
m_type = type;
|
|
}
|
|
|
|
/**
|
|
* Return a value from the random distribution
|
|
*/
|
|
Real GameClientRandomVariable::getValue( void ) const
|
|
{
|
|
switch( m_type )
|
|
{
|
|
case CONSTANT:
|
|
DEBUG_ASSERTLOG(m_low == m_high, ("m_low != m_high for a CONSTANT GameClientRandomVariable\n"));
|
|
if (m_low == m_high) {
|
|
return m_low;
|
|
} // else return as though a UNIFORM.
|
|
|
|
case UNIFORM:
|
|
return GameClientRandomValueReal( m_low, m_high );
|
|
|
|
default:
|
|
/// @todo fill in support for nonuniform GameClientRandomVariables.
|
|
DEBUG_CRASH(("unsupported DistributionType in GameClientRandomVariable::getValue\n"));
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
// GameLogicRandomVariable
|
|
//
|
|
|
|
/*static*/ const char *GameLogicRandomVariable::DistributionTypeNames[] =
|
|
{
|
|
"CONSTANT", "UNIFORM", "GAUSSIAN", "TRIANGULAR", "LOW_BIAS", "HIGH_BIAS"
|
|
};
|
|
|
|
/**
|
|
define the range of random values, and the distribution of values
|
|
*/
|
|
void GameLogicRandomVariable::setRange( Real low, Real high, DistributionType type )
|
|
{
|
|
DEBUG_ASSERTCRASH(!(m_type == CONSTANT && m_low != m_high), ("CONSTANT GameLogicRandomVariables should have low == high"));
|
|
m_low = low;
|
|
m_high = high;
|
|
m_type = type;
|
|
}
|
|
|
|
/**
|
|
* Return a value from the random distribution
|
|
*/
|
|
Real GameLogicRandomVariable::getValue( void ) const
|
|
{
|
|
switch( m_type )
|
|
{
|
|
case CONSTANT:
|
|
DEBUG_ASSERTLOG(m_low == m_high, ("m_low != m_high for a CONSTANT GameLogicRandomVariable"));
|
|
if (m_low == m_high) {
|
|
return m_low;
|
|
} // else return as though a UNIFORM.
|
|
|
|
case UNIFORM:
|
|
return GameLogicRandomValueReal( m_low, m_high );
|
|
|
|
default:
|
|
/// @todo fill in support for nonuniform GameLogicRandomVariables.
|
|
DEBUG_CRASH(("unsupported DistributionType in GameLogicRandomVariable::getValue\n"));
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
|