[BTS] Potentially fixed Random Event OOS

lemmy101

Emperor
Joined
Apr 10, 2006
Messages
1,064
Hello all!

I *think* I may have fixed the Random Event OOS bug. After a few days of poking about, I came up with the not so elegant solution of creating massive buffers of pre-generated random values for each civ, generated with its own random number generator seeded from the initial synced seed value. Therefore it's completely detached from the game random from that point on, and each civ maintains their own list of random numbers, so if the order the civ's events are processed doesn't matter either...

I'm posting this now as i've been writing up the changes needed. I've got it in my mod but not sure when I'll release that, so here it is.

NOTE: Unless you're willing to help me test it, and just want to use it, I'd hold off implementing it a while longer until I've given it more of a test. I'm pretty confident but I'd hate to see anyone go to all the effort of implementing it and it still OSSing. If you'd be willing to help us with a large scale test then that would be much appreciated! :)

Here are the changes:

CvGame.h

Add above class:
Code:
#define MAX_EVENTS_PER_CIV_PER_TURN		3
#define MAX_ROLLS_PER_EVENT				20
#define MAX_EVENT_TURNS					5000 // The buffers could probably be a LOT smaller, I'm just playing safe since it seems I have to precreate it and can't calculate in advance...
Add in class:
Code:
	unsigned long getRandomEventBufferRoll(int iPlayer, int index);

	void EventRandomizer_init();

	int m_iEventSeed;

	bool m_bEventInit;

	unsigned long** m_aiTurnRands;
CvMessageControl.h

Change: (add last argument)
Code:
	void sendEventTriggered(PlayerTypes ePlayer, EventTypes eEvent, int iEventTriggeredId, int iPositionInBuffer);

CvMessageData.h

Change: (add last argument)
Code:
	CvNetEventTriggered(PlayerTypes ePlayer = NO_PLAYER, EventTypes eEvent = NO_EVENT, int iEventTriggeredId = -1, int iPositionInBuffer = -1);


Add in class:
Code:
	int m_iPositionInBuffer;
CvPlayer.h

Add in class:
Code:
	int getCurrentEventBufferPlace();
	void setPlaceInEventBuffer(int val);
	int generateEventSeeds(int LastSeed);
	int getEventRollFromBuffer(int iWeight, const TCHAR* str);
	int m_iEventBufferPos;

Change: (remove const)

	CvCity* pickTriggerCity(EventTriggerTypes eTrigger);
	CvUnit* pickTriggerUnit(EventTriggerTypes eTrigger, CvPlot* pPlot, bool bPickPlot);
CyPlayer.h

Add to class:
Code:
	int getEventRollFromBuffer(int iPlayer, std::wstring szStr);
CvDLLButtonPopup.h

Change: (Add last parameter)
Code:
	CvMessageControl::getInstance().sendEventTriggered(GC.getGameINLINE().getActivePlayer(), (EventTypes)pPopupReturn->getButtonClicked(), info.getData1(), GET_PLAYER(GC.getGameINLINE().getActivePlayer()).getCurrentEventBufferPlace());
CvGame.cpp

Add to CvGame::CvGame:
Code:
	m_aiTurnRands = new unsigned long*[MAX_CIV_PLAYERS];
	for (int i = 0; i < MAX_PLAYERS; i++)
	{
		m_aiTurnRands[i] = new unsigned long[MAX_EVENT_TURNS * MAX_EVENTS_PER_CIV_PER_TURN * MAX_ROLLS_PER_EVENT];
	}
Add to CvGame::~CvGame:
Code:
	for (int i = 0; i < MAX_PLAYERS; i++)
	{
		SAFE_DELETE_ARRAY(m_aiTurnRands[i]);
	}
	SAFE_DELETE_ARRAY(m_aiTurnRands);
Add:
Code:
#define CJSRANDOM_A      (1103515245)
#define CJSRANDOM_C      (12345)
#define CJSRANDOM_SHIFT  (16)

unsigned long CvGame::getRandomEventBufferRoll(int iPlayer, int index)
{
	if(!m_bEventInit)
		EventRandomizer_init();

	FAssertMsg(m_aiTurnRands[iPlayer][index] != 0, "On roll: We don't seem to have random values CRY");

	return m_aiTurnRands[iPlayer][index];
}

void CvGame::EventRandomizer_init()
{
	if(m_bEventInit)
		return;

	m_bEventInit = true;

	int seed = GC.getInitCore().getSyncRandSeed() % 52319761;

	if(m_iEventSeed==0)
		m_iEventSeed = seed;

	// Now generate a seed list for each player...
	int eventSeed = m_iEventSeed;
	for (int i = 0; i < MAX_CIV_PLAYERS; i++)
	{
		eventSeed = ((CJSRANDOM_A * eventSeed) + CJSRANDOM_C);
		unsigned __int64 us = eventSeed;

		for (int n=0;n<MAX_EVENT_TURNS*(MAX_EVENTS_PER_CIV_PER_TURN * MAX_ROLLS_PER_EVENT);n++)
		{
			us = ((CJSRANDOM_A * us) + CJSRANDOM_C);
			m_aiTurnRands[i][n] = us;
		}
	}

}
Add to bottom of CvGame::init below AI_init()
Code:
	EventRandomizer_init();
Add to CvGame::reset:
Code:
	m_iEventSeed = 0;
	m_bEventInit = false;
	for (int iJ = 0; iJ < MAX_PLAYERS; iJ++)
	{
		for (int n=0;n<MAX_EVENT_TURNS * MAX_EVENTS_PER_CIV_PER_TURN * MAX_ROLLS_PER_EVENT; n++)
		{
			m_aiTurnRands[iJ][n] = 0;

		}
	}
Add to CvGame::write:
Code:
	pStream->Write(m_iEventSeed);



Add to CvGame::read (IN SAME ORDER AND POSITION AS THE WRITE)

	pStream->Read(&m_iEventSeed);
In CvMessageControl.cpp:

Change: (add last parameter)
Code:
void CvMessageControl::sendEventTriggered(PlayerTypes ePlayer, EventTypes eEvent, int iEventTriggeredId, int iPositionInBuffer)
{
	gDLL->sendMessageData(new CvNetEventTriggered(ePlayer, eEvent, iEventTriggeredId, iPositionInBuffer));
}
In CvMessageData.cpp:

Change: (add last constructor call)
Code:
CvNetEventTriggered::CvNetEventTriggered(PlayerTypes ePlayer, EventTypes eEvent, int iEventTriggeredId, int iPositionInBuffer) : CvMessageData(GAMEMESSAGE_EVENT_TRIGGERED),
	m_ePlayer(ePlayer),
	m_eEvent(eEvent),
	m_iEventTriggeredId(iEventTriggeredId),
	m_iPositionInBuffer(iPositionInBuffer)
{
}
In CvNetEventTriggered::Execute() Change: (Last parameter)
Code:
	GET_PLAYER(m_ePlayer).applyEvent(m_eEvent, m_iEventTriggeredId, true, m_iPositionInBuffer);
In CvNetEventTriggered::putInBuffer(FDataStreamBase* pStream) add
Code:
	pStream->Write(m_iPositionInBuffer);
In CvNetEventTriggered::SetFromBuffer add (IN SAME POSITION):
Code:
	pStream->Read((int*)&m_iPositionInBuffer);
In CvPlayer.cpp:

Add to CvPlayer::CvPlayer:
Code:
	m_iEventBufferPos = 0;
Add:
Code:
int CvPlayer::generateEventSeeds(int inSeed)
{
	return 0;

} 
#define CJSRANDOM_A      (1103515245)
#define CJSRANDOM_C      (12345)
#define CJSRANDOM_SHIFT  (16)
int CvPlayer::getEventRollFromBuffer(int iWeight, const TCHAR* szBuf)
{
	int index = m_iEventBufferPos;

	m_iEventBufferPos++;
	unsigned short us = ((unsigned short)((((GC.getGame().getRandomEventBufferRoll(getID(), index) >> CJSRANDOM_SHIFT) & MAX_UNSIGNED_SHORT) * ((unsigned long)iWeight)) / (MAX_UNSIGNED_SHORT + 1)));

	TCHAR szOut[1024];
	
	if(GC.getGame().getActivePlayer()==getID())
		sprintf(szOut, "Turn %d - Local Player %d (%s) %s = %d of %d - roll #%d\n", GC.getGame().getGameTurn(), getID(), getName(), szBuf, us, iWeight, m_iEventBufferPos-1);
	else if(isHuman())
		sprintf(szOut, "Turn %d - Remote Player %d (%s) %s = %d of %d - roll #%d\n", GC.getGame().getGameTurn(), getID(), getName(), szBuf, us, iWeight, m_iEventBufferPos-1);
	else
		sprintf(szOut, "Turn %d - AI Player %d (%s) %s = %d of %d - roll #%d\n", GC.getGame().getGameTurn(), getID(), getName(), szBuf, us, iWeight, m_iEventBufferPos-1);
	gDLL->messageControlLog(szOut);
	
	return us;

}

void CvPlayer::setPlaceInEventBuffer(int val)
{
	m_iEventBufferPos = val;
}
int CvPlayer::getCurrentEventBufferPlace()
{
	int index = m_iEventBufferPos;

	return index;

}

Add to CvPlayer::read
Code:
	pStream->Read(&m_iEventBufferPos);
And in same place in write:
Code:
	pStream->Write(m_iEventBufferPos);
Select everything from:
Code:
	EventTriggeredData* CvPlayer::initTriggeredData(EventTriggerTypes eEventTrigger, bool bFire, int iCityId, int iPlotX, int iPlotY, PlayerTypes eOtherPlayer, int iOtherPlayerCityId, ReligionTypes eReligion, CorporationTypes eCorporation, int iUnitId, BuildingTypes eBuilding)

to above (not including):
Code:
	bool CvPlayer::splitEmpire(int iAreaId)

and so a replace on the selected text only:

Replace: GC.getGameINLINE().getSorenRandNum

with: getEventRollFromBuffer

Change:
Code:
	void CvPlayer::applyEvent(EventTypes eEvent, int iEventTriggeredId, bool bUpdateTrigger)
	{
		FAssert(eEvent != NO_EVENT);

To:

Code:
void CvPlayer::applyEvent(EventTypes eEvent, int iEventTriggeredId, bool bUpdateTrigger, int iPlaceInBuffer)
	{
		FAssert(eEvent != NO_EVENT);
	
		if(iPlaceInBuffer != -1)
			setPlaceInEventBuffer(iPlaceInBuffer);
In CyPlayer.cpp:

Add:
Code:
	int CyPlayer::getEventRollFromBuffer(int iPlayer, std::wstring szStr)
	{
		return m_pPlayer ? m_pPlayer->getEventRollFromBuffer(iPlayer, CvString(szStr.c_str())) : 0;
	}

In CyPlayerInterface.cpp:

Add:
Code:
		.def("getEventRollFromBuffer", &CyPlayer::getEventRollFromBuffer, "int (int max, std::wstring str)")
In CvRandomEventInterface.py

Replace all:
Code:
	gc.getGame().getSorenRandNum
With:
Code:
	gc.getPlayer(gc.getGame().getActivePlayer()).getEventRollFromBuffer
And ta da!!! Random Events should now work in multiplayer, Windows 7 / Vista or whatever!
 
For the less informed, what random event OOS bugs occurred in BTS? I never noticed any, but I rarely play multiplayer either...
 
Basically prior to the above I'd never managed to finish a game with random events turned on. At some point the game would descend into an OOS every 3-4 turns even after a complete all players reload, whenever an event occured and after searching here I found out that a) It seems to be a common bug in 3.19 and the sole advice people had was 'delete the cache and turn random events off'

It may also be down to Vista / Widows 7.

Either way I've actually been able to finish multiplayer games now where I wasn't previously able, but have limited ability to test multiple players with slower connection speeds so still not sure if this is just fluke.

We did get some OOS issues using our mod but it transpired to be caused by other elements of the mod and not random events, so I'm still unaware whether it's 100% fixed or just not popped out to say hello yet. ;)
 
Top Bottom