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;
}
}