Jump to content

Experimental - Full Terrain Render


Recommended Posts

  • Premium

Hi there devs,

Okay, so after reading the title you may (or may not) have some thougts/questions like "Hollllllllly sh..", "Oooookay..." or just "?". About one or two years ago a mapper questioned me: "Is this possible, to make the whole map visible? Like in normal games? This fog is sooo disgusting, I've could create more beautiful view with a normal fog..." I've tried to do it many ways until finally after some sleepless night I've made it about a year ago. Once it was done I didn't know what to do with it. It was pretty good (I think) but since I'm not a mapper I can't do anything with it. I could have sell it, but since there is no way to protect this code its not an option for me, so I've decided to share it with the public. This TuT gonna be a bit long and there are several things to discuss but FIRST lets watch this video (in the first part I disabled the fog).

"Wow, thats actually not bad" this was my first reaction when I first saw this view. BUT enough from me, lets talk about more important things...

So I decided to create stages for this tutorial (4 stages actually). Its a normal down-top thing, so it means that you can do stage 1, 2 and then you can stop, but can't do stage 2 without 1, 3 without 2 and 4 without 3... it will help you to customize these modifications. Also I may write some "interesting" (at least for me) notes/thoughts for each stages. However, as you will see the last 2 stages are not ready at the moment, so I would rather say its an experimental release... Anyway, before we start to implement this lets talk about the tests, performance and other more or less important things.

Table of content:

  • Stage 1: Removing garbage collectors, loading all map related things into memory ("chunks", objects, etc...)
  • Stage 2: Rendering full terrain
  • Stage 3: Rendering all objects on map [NOT READY YET]
  • Stage 4: Create config for players [NOT READY YET]

Performance

One of the most important questions about a game: will I be able to play it with my spec? Well of course this depends on much things. Using high poly objects, large maps, HD textures, lots of trees, effects can highly impact the performance/rendering time. So all I can say about it in a nutshell: its really depends on your maps (and of course the player's machine). Until now this old buddy could be ran on almost every computer but with this could change things so this is why I created config for it, enabling or disabling this feature.

About my experiences/tests

Well the memory usage increased, from the default ~270 MB to ~300 MB (see the explanations later), the processor usage didn't change (~5-10%). About my video card usage I can't tell nothing since I can't monitor it, but I'm sure its usage (both video memory and processing) increased too... You maybe noticed some lag in the video but its not the case, its only because of my recorder, the game is still runs smoothly (remember, its depends on the computer and the map...) If it helps I have a Lenovo U41-70 notebook (Intel i7 5500U, 8GB DDR3 and GeForce 920M) and I used the "(old) wom2 maps".

However, I noticed some fps drop with all shadows enabled, and also couldn't test it with many characters (cus the character render consumes so much processing time without this too), so I recommend to disable it by default and if the player decides to use it, he will be able to turn it on.

Stage 1

Spoiler

Some personal hints:

First let me explain what I meant when I said "chunks". As we all know, each map has a size, like 1x1, 2x2... If a map's size is 2x2 then it has 4 chunks... (I hope its clear now :P )
Before we start to render the universe we need all the map related things already been loaded and ready for use in the memory. By default the game only loads maximum 9 chunks (the one you are standing on, and when its possible 8 other adjacent ones) but its not enough for us, for example when we are standing on a corner chunk of a 2x3 map we have only 4 chunks loaded so another 2 chunks will be "white" and only levitating trees will be seen. By the way this is the reason when we see hovering trees on the maps: The trees are rendered mapwide by default so even when we don't see them they are there so when the "eye"'s edge is near to a hill or something the tree appears before the terrain.

You can do this change even if you don't want to "render the galaxy". You may wonder why... The first reason is the useless garbage collector which runs every rendering time and tries to collect unused objects, textures, chunks (even if we don't have any). It could be useful too but in these days its just fcking with the processor... The other reason is quite simple. When you leave a chunk the game instantly starts to load 3 other one (in the worst case) and it brakes the render for some ms which cause an fps drop.

In this part we will meet with some usual code near to the heart of the area/terrain rendering (no dragons here sry) and we will delete some of them :P Although looks like this part is nearly untouched since the first release of the game and also looks like this part was written by a bit competent person (except the gb collector...) since its not the usual gf like trash code.

Install (FINALLY)

GameLib/MapOutdoorUpdate.cpp

Spoiler

in bool CMapOutdoor::Update(float fX, float fY, float fZ) replace this:





	if ( bNeedInit ||
		(m_CurCoordinate.m_sTerrainCoordX/LOAD_SIZE_WIDTH) != (sCoordX/LOAD_SIZE_WIDTH) || 
		(m_CurCoordinate.m_sTerrainCoordY/LOAD_SIZE_WIDTH) != (sCoordY/LOAD_SIZE_WIDTH) )
	{
		if (bNeedInit)
		{
			m_PrevCoordinate.m_sTerrainCoordX = sCoordX;
			m_PrevCoordinate.m_sTerrainCoordY = sCoordY;
		}
		else
		{
			m_PrevCoordinate.m_sTerrainCoordX = m_CurCoordinate.m_sTerrainCoordX;
			m_PrevCoordinate.m_sTerrainCoordY = m_CurCoordinate.m_sTerrainCoordY;
		}
		
		m_CurCoordinate.m_sTerrainCoordX = sCoordX;
		m_CurCoordinate.m_sTerrainCoordY = sCoordY;
		m_lCurCoordStartX = sCoordX * CTerrainImpl::TERRAIN_XSIZE;
		m_lCurCoordStartY = sCoordY * CTerrainImpl::TERRAIN_YSIZE;

		WORD wCellCoordX = (ix % CTerrainImpl::TERRAIN_XSIZE) / CTerrainImpl::CELLSCALE;
		WORD wCellCoordY = (iy % CTerrainImpl::TERRAIN_YSIZE) / CTerrainImpl::CELLSCALE;

		short sReferenceCoordMinX, sReferenceCoordMaxX, sReferenceCoordMinY, sReferenceCoordMaxY;
		sReferenceCoordMinX = max(m_CurCoordinate.m_sTerrainCoordX - LOAD_SIZE_WIDTH, 0);
		sReferenceCoordMaxX = min(m_CurCoordinate.m_sTerrainCoordX + LOAD_SIZE_WIDTH, m_sTerrainCountX - 1);
		sReferenceCoordMinY = max(m_CurCoordinate.m_sTerrainCoordY - LOAD_SIZE_WIDTH, 0);
		sReferenceCoordMaxY = min(m_CurCoordinate.m_sTerrainCoordY + LOAD_SIZE_WIDTH, m_sTerrainCountY - 1);
		
		for (WORD usY = sReferenceCoordMinY; usY <=sReferenceCoordMaxY; ++usY)
		{
			for (WORD usX = sReferenceCoordMinX; usX <= sReferenceCoordMaxX; ++usX)
			{
				LoadTerrain(usX, usY, wCellCoordX, wCellCoordY);
  				LoadArea(usX, usY, wCellCoordX, wCellCoordY);
			}
		}

		AssignTerrainPtr();
		m_lOldReadX = -1;

		Tracenf("Update::Load spent %d ms\n", ELTimer_GetMSec() - t1);
	}

with this:





	if (bNeedInit ||
		m_CurCoordinate.m_sTerrainCoordX / LOAD_SIZE_WIDTH != sCoordX / LOAD_SIZE_WIDTH ||
		m_CurCoordinate.m_sTerrainCoordY / LOAD_SIZE_WIDTH != sCoordY / LOAD_SIZE_WIDTH)
	{
		if (bNeedInit)
		{
			m_PrevCoordinate.m_sTerrainCoordX = sCoordX;
			m_PrevCoordinate.m_sTerrainCoordY = sCoordY;
			
			//during the loading screen load everything
			for (WORD usY = 0; usY < m_sTerrainCountY; ++usY)
			{
				for (WORD usX = 0; usX < m_sTerrainCountX; ++usX)
				{
					LoadTerrain(usX, usY);
					LoadArea(usX, usY);
				}
			}
		}
		else
		{
			m_PrevCoordinate.m_sTerrainCoordX = m_CurCoordinate.m_sTerrainCoordX;
			m_PrevCoordinate.m_sTerrainCoordY = m_CurCoordinate.m_sTerrainCoordY;
		}

		m_CurCoordinate.m_sTerrainCoordX = sCoordX;
		m_CurCoordinate.m_sTerrainCoordY = sCoordY;
		m_lCurCoordStartX = sCoordX * CTerrainImpl::TERRAIN_XSIZE;
		m_lCurCoordStartY = sCoordY * CTerrainImpl::TERRAIN_YSIZE;

		AssignTerrainPtr();
		m_lOldReadX = -1;

		Tracenf("Update::Load spent %d ms\n", ELTimer_GetMSec() - t1);
	}

Also delete this from the function:





	__UpdateGarvage();

In void CMapOutdoor::UpdateTerrain(float fX, float fY) delete this:





	UpdateAreaList(lRealCenterX, lRealCenterY);

Under this function delete the following functions:





void CMapOutdoor::FPushTerrainToDeleteVector::operator () (CTerrain * pTerrain)
void CMapOutdoor::FPushAreaToDeleteVector::operator () (CArea * pArea)
void CMapOutdoor::__ClearGarvage()
void CMapOutdoor::__UpdateGarvage()
void CMapOutdoor::UpdateAreaList(long lCenterX, long lCenterY)

 

GameLib/MapOutdoor.h

Spoiler

Delete these things:





		virtual void	__ClearGarvage();
		virtual void	__UpdateGarvage();
		virtual void	UpdateAreaList(long lCenterX, long lCenterY);

And replace these ones (those args are unused, so its just "cleaning"... 😞





		virtual bool	LoadTerrain(WORD wTerrainCoordX, WORD wTerrainCoordY, WORD wCellCoordX, WORD wCellCoordY);
		virtual bool	LoadArea(WORD wAreaCoordX, WORD wAreaCoordY, WORD wCellCoordX, WORD wCellCoordY);

With these ones:





		virtual bool	LoadTerrain(WORD wTerrainCoordX, WORD wTerrainCoordY);
		virtual bool	LoadArea(WORD wAreaCoordX, WORD wAreaCoordY);

Remove also these things:





		TTerrainPtrVector			m_TerrainDeleteVector;
		TTerrainPtrVectorIterator	m_TerrainPtrVectorIterator;
		TTerrainPtrVector			m_TerrainLoadWaitVector;
		TTerrainPtrVector			m_TerrainLoadRequestVector;

		TAreaPtrVector				m_AreaDeleteVector;
		TAreaPtrVectorIterator		m_AreaPtrVectorIterator;
		TAreaPtrVector				m_AreaLoadWaitVector;
		TAreaPtrVector				m_AreaLoadRequestVector;

		struct FPushToDeleteVector
		{
			enum EDeleteDir
			{
				DELETE_LEFT,
				DELETE_RIGHT,
				DELETE_TOP,
				DELETE_BOTTOM,
			};

			EDeleteDir m_eLRDeleteDir;
			EDeleteDir m_eTBDeleteDir;
			TOutdoorMapCoordinate m_CurCoordinate;

			FPushToDeleteVector(EDeleteDir eLRDeleteDir, EDeleteDir eTBDeleteDir, TOutdoorMapCoordinate CurCoord)
			{
				m_eLRDeleteDir = eLRDeleteDir;
				m_eTBDeleteDir = eTBDeleteDir;
				m_CurCoordinate = CurCoord;
			}
		};
		struct FPushTerrainToDeleteVector : public FPushToDeleteVector
		{
			TTerrainPtrVector	m_ReturnTerrainVector;

			FPushTerrainToDeleteVector(EDeleteDir eLRDeleteDir, EDeleteDir eTBDeleteDir, TOutdoorMapCoordinate CurCoord)
				: FPushToDeleteVector(eLRDeleteDir, eTBDeleteDir, CurCoord)
			{
				m_ReturnTerrainVector.clear();
			}

			void operator() (CTerrain * pTerrain);
		};

		struct FPushAreaToDeleteVector : public FPushToDeleteVector
		{
			TAreaPtrVector		m_ReturnAreaVector;

			FPushAreaToDeleteVector(EDeleteDir eLRDeleteDir, EDeleteDir eTBDeleteDir, TOutdoorMapCoordinate CurCoord)
				: FPushToDeleteVector(eLRDeleteDir, eTBDeleteDir, CurCoord)
			{
				m_ReturnAreaVector.clear();
			}

			void operator() (CArea * pArea);
		};

 

MapOutdoorLoad.cpp

Spoiler

Replace this:





bool CMapOutdoor::LoadArea(WORD wAreaCoordX, WORD wAreaCoordY, WORD wCellCoordX, WORD wCellCoordY)

with this:





bool CMapOutdoor::LoadArea(WORD wAreaCoordX, WORD wAreaCoordY)

And this:





bool CMapOutdoor::LoadTerrain(WORD wTerrainCoordX, WORD wTerrainCoordY, WORD wCellCoordX, WORD wCellCoordY)

With this:





bool CMapOutdoor::LoadTerrain(WORD wTerrainCoordX, WORD wTerrainCoordY)

 

MapOutdoor.cpp

Spoiler

Delete these ones from void Initialize():





	m_TerrainDeleteVector.clear();
	m_TerrainLoadRequestVector.clear();
	m_TerrainLoadWaitVector.clear();
	
	m_AreaDeleteVector.clear();
	m_AreaLoadRequestVector.clear();
	m_AreaLoadWaitVector.clear();

From void CMapOutdoor::DestroyArea() delete this:





	m_AreaDeleteVector.clear();

From void CMapOutdoor::DestroyTerrain() delete this:





	m_TerrainDeleteVector.clear();

 

 

Stage 2

Spoiler

Some personal hints:

Okay, now here we are, its one of the most complex part of the game (after the statemanager) and here we will see some magical animals (there could be dragons too *o*) like the quad tree and the terrainPatchProxy (yeah the first things we are gonna kick the fck out). These two mentioned things (the quadtree and the proxy) are managing the distance render. When you are just standing and notice that the terrain is just random growing from the ground or random disappears its because these guys (the rendering is depends on the option "far or near"). Since we want to render the whole map we don't need such magic.

Important: I only modified the HTP rendering, it means that it will work only when the hardware tilling is selected (and ofc the full render is activated).

GameLib/MapManager.h

Spoiler

After this:





		void	ReserveSoftwareTilingEnable(bool isEnable);

Add this:





		bool	IsFullMapRenderEnable() { return CTerrainPatch::FULL_MAP_RENDER_ENABLE; }
		void	SetFullMapRender(bool isEnable) { m_isFullMapRenderEnable = isEnable; }

After this:





		bool m_isSoftwareTilingEnableReserved;

Add this:





		bool m_isFullMapRenderEnable;

 

GameLib/MapManager.cpp

Spoiler

In CMapManager::CMapManager() add this:





	m_isFullMapRenderEnable = false;

After this:





	m_isSoftwareTilingEnableReserved=false;

In void CMapManager::Create() after this:





	CTerrainPatch::SOFTWARE_TRANSFORM_PATCH_ENABLE=m_isSoftwareTilingEnableReserved;

Add this:





	CTerrainPatch::FULL_MAP_RENDER_ENABLE = m_isFullMapRenderEnable && !IsSoftwareTilingEnable();

 

GameLib/TerrainPatch.h

Spoiler

After this:





	static bool SOFTWARE_TRANSFORM_PATCH_ENABLE;

Add this:





	static bool FULL_MAP_RENDER_ENABLE;

 

GameLib/TerrainPatch.cpp

Spoiler

After this:





bool CTerrainPatch::SOFTWARE_TRANSFORM_PATCH_ENABLE=TRUE;

Add this:





bool CTerrainPatch::FULL_MAP_RENDER_ENABLE = false;

 

GameLib/MapOutdoor.cpp

Spoiler

In void CMapOutdoor::CreateTerrainPatchProxyList() replace this:





	m_pTerrainPatchProxyList = new CTerrainPatchProxy[m_wPatchCount * m_wPatchCount];

With this:





	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // don't need this nasty buddy for full map render
		m_pTerrainPatchProxyList = new CTerrainPatchProxy[m_wPatchCount * m_wPatchCount];

 

GameLib/MapOutdoor.h

Spoiler

After this:





		void					DrawWater(long patchnum);

Add this:





		void					DrawFullWater();

After this:





		void __RenderTerrain_RenderHardwareTransformPatch();

Add this:





		void RenderWholeMapMB(WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType);

 

GameLib/MapOutdoorLoad.cpp

Spoiler

In bool CMapOutdoor::Load(float x, float y, float z) relpace this:





	BuildQuadTree();

With this:





	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // we don't need this too
		BuildQuadTree();

 

GameLib/MapOutdoorRender.cpp

Spoiler

In void CMapOutdoor::RenderTerrain() replace this:





	if (!m_pTerrainPatchProxyList)

With this:





	if (!m_pTerrainPatchProxyList && !CTerrainPatch::FULL_MAP_RENDER_ENABLE) // we don't use proxy and full render together

Replace this:





	m_PatchVector.clear();
	
	__RenderTerrain_RecurseRenderQuadTree(m_pRootNode);
	
	// °Ĺ¸®Ľř Á¤·Ä
	std::sort(m_PatchVector.begin(),m_PatchVector.end());

With this:





	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE)
	{
		// this part responsible for the distance sorting for the basic render
		m_PatchVector.clear();
		__RenderTerrain_RecurseRenderQuadTree(m_pRootNode);
		std::sort(m_PatchVector.begin(), m_PatchVector.end());
	}

 

GameLib/MapOutdoorRenderHTP.cpp

Spoiler

Add this to the end of the file:





void CMapOutdoor::RenderWholeMapMB(WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType)
{
	// now here we might see some dragons, cous I don't know everything's functionality here (and when I know it its because I messed them up before lol...)
	// its mostly a mixed up code from the original ones
	DWORD dwFogColor;
	bool bEnableFog = false;
	if (mc_pEnvironmentData) // check if the fog is enabled and its color
	{
		dwFogColor = mc_pEnvironmentData->FogColor;
		bEnableFog = mc_pEnvironmentData->bFogEnable;
	}

	for (auto it = m_TerrainVector.begin(); it != m_TerrainVector.end(); ++it) // lets start render the whole map (all chunks)
	{
		CTerrain * pTerrain = (*it);

		WORD wCoordX, wCoordY;
		pTerrain->GetCoordinate(&wCoordX, &wCoordY);

		TTerrainSplatPatch & rTerrainSplatPatch = pTerrain->GetTerrainSplatPatch();
		for (BYTE y = 0; y < CTerrainImpl::PATCH_YCOUNT; ++y)
		{
			for (BYTE x = 0; x < CTerrainImpl::PATCH_XCOUNT; ++x)
			{
				// each chunk contains 64 patches, and its rendering order is important, otherwise we might would see some abstract textures :P
				CTerrainPatch* pTerrainPath = pTerrain->GetTerrainPatchPtr(x, y);
				if (!pTerrainPath)
					continue;

				// OKAY we gonna do SOMETHING with alpha texures and tiling here, I'm not a graphical processing expert...
				D3DXMATRIX matTexTransform, matSplatAlphaTexTransform, matSplatColorTexTransform;
				m_matWorldForCommonUse._41 = -(float)(wCoordX * CTerrainImpl::TERRAIN_XSIZE);
				m_matWorldForCommonUse._42 = (float)(wCoordY * CTerrainImpl::TERRAIN_YSIZE);
				D3DXMatrixMultiply(&matTexTransform, &m_matViewInverse, &m_matWorldForCommonUse);
				D3DXMatrixMultiply(&matSplatAlphaTexTransform, &matTexTransform, &m_matSplatAlpha);
				STATEMANAGER.SetTransform(D3DTS_TEXTURE1, &matSplatAlphaTexTransform);

				D3DXMATRIX matTiling;
				D3DXMatrixScaling(&matTiling, 1.0f / 640.0f, -1.0f / 640.0f, 0.0f);
				matTiling._41 = 0.0f;
				matTiling._42 = 0.0f;

				D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &matTiling);
				STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);

				CGraphicVertexBuffer* pkVB = pTerrainPath->HardwareTransformPatch_GetVertexBufferPtr();
				if (!pkVB)
					return;

				STATEMANAGER.SetStreamSource(0, pkVB->GetD3DVertexBuffer(), m_iPatchTerrainVertexSize);
				STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);

				bool isFirst = true;
				for (DWORD j = 1; j < pTerrain->GetNumTextures(); ++j)
				{
					// okay lets start rendering the textures here (still don't fully get this part...)
					TTerainSplat & rSplat = rTerrainSplatPatch.Splats[j];

					if (!rSplat.Active)
						continue;

					if (rTerrainSplatPatch.PatchTileCount[y * CTerrainImpl::PATCH_YCOUNT + x][j] == 0)
						continue;

					const TTerrainTexture & rTexture = m_TextureSet.GetTexture(j);
					D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &rTexture.m_matTransform);
					STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);
					if (isFirst)
					{
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
						STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
						STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
						STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
						isFirst = false;
					}
					else
					{
						STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
						STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
						STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
					}

				}

				if (m_bDrawShadow) // lets render the shadows (its still some sort of graphic processing magic)
				{
					STATEMANAGER.SetRenderState(D3DRS_LIGHTING, TRUE);
					STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, 0xFFFFFFFF);
					STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
					STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);

					D3DXMATRIX matShadowTexTransform;
					D3DXMatrixMultiply(&matShadowTexTransform, &matTexTransform, &m_matStaticShadow);

					STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matShadowTexTransform);
					STATEMANAGER.SetTexture(0, pTerrain->GetShadowTexture());

					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);

					if (m_bDrawChrShadow)
					{
						STATEMANAGER.SetTransform(D3DTS_TEXTURE1, &m_matDynamicShadow);
						STATEMANAGER.SetTexture(1, m_lpCharacterShadowMapTexture);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
					}
					else
					{
						STATEMANAGER.SetTexture(1, NULL);
					}

					ms_faceCount += wPrimitiveCount;
					STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
					++m_iRenderedSplatNum;

					if (m_bDrawChrShadow)
					{
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
					}

					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);


					STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
					STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
					if (bEnableFog)
						STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, dwFogColor);

					STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
				}
			}
		}
	}
}

In void CMapOutdoor::__RenderTerrain_RenderHardwareTransformPatch() after this:





	float fFogNearDistance;

Add this:





	bool bEnableFog; //there is an option in the map's config to disable config but LOL its not implemented properly...

After this:





		fFogFarDistance=mc_pEnvironmentData->GetFogFarDistance();

Add this:





		bEnableFog = mc_pEnvironmentData->bFogEnable;

Replace this:





	{
		dwFogColor=0xffffffff;
		fFogNearDistance=5000.0f;
		fFogFarDistance=10000.0f;
	}

With this:





		bEnableFog = false;

Replace this:





	STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, dwFogColor);

With this:





	if (bEnableFog) // we don't need fog render when no fog activated
		STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, dwFogColor);

Replace this:





	DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE);

With this:





	DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE);
	if (!bEnableFog)
		dwFogEnable = 0;

Add this:





	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // when full load is disabled this part renders the near things, without any fog

Before this:





	for( ; it != near_it; ++it)

Before this:





	if (m_iRenderedSplatNum < m_iSplatLimit) //there are two of them this is why I inserted the next few lines too
	{
		for(it = near_it; it != far_it; ++it)
		{

Add this:





	if (CTerrainPatch::FULL_MAP_RENDER_ENABLE)
		RenderWholeMapMB(wPrimitiveCount, ePrimitiveType);

Replace this (there are two of them in the code, replace both):





	if (m_iRenderedSplatNum < m_iSplatLimit)

With this:





	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE && m_iRenderedSplatNum < m_iSplatLimit)

 

GameLib/MapOutdoorUpdate.cpp

Spoiler

In void CMapOutdoor::UpdateTerrain(float fX, float fY) before this:





		ConvertTerrainToTnL(lRealCenterX, lRealCenterY);

Add this:





		if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // the ConvertTerrainToTnl fills the quadtree, but we don't need this when full render active

 

Okay, now the terrain should be fully rendered if you activate it by setting m_isFullMapRenderEnable = true in the CMapManager::CMapManager() (and remember: you must set the tilling mode to GPU), but yet the water would be missing, so lets fix it.

GameLib/MapOutdoorWater.cpp

Spoiler

Add this to the end of the file:





void CMapOutdoor::DrawFullWater()
{
	CTerrain* terra;
	CTerrainPatch* pTerrainPath;
	CGraphicVertexBuffer* pkVB;
	UINT uPriCount;
	for (auto it = m_TerrainVector.begin(); it != m_TerrainVector.end(); ++it)
	{
		terra = *it;
		for (BYTE y = 0; y < CTerrainImpl::PATCH_YCOUNT; ++y)
		{
			for (BYTE x = 0; x < CTerrainImpl::PATCH_XCOUNT; ++x)
			{
				pTerrainPath = terra->GetTerrainPatchPtr(x, y);
				if (!pTerrainPath || !pTerrainPath->IsWaterExist())
					continue;

				pkVB = pTerrainPath->GetWaterVertexBufferPointer();
				if (!pkVB || !pkVB->GetD3DVertexBuffer())
					continue;

				uPriCount = pTerrainPath->GetWaterFaceCount();
				if (!uPriCount)
					return;

				STATEMANAGER.SetStreamSource(0, pkVB->GetD3DVertexBuffer(), sizeof(SWaterVertex));
				STATEMANAGER.DrawPrimitive(D3DPT_TRIANGLELIST, 0, uPriCount);
			}
		}
	}
}

In void CMapOutdoor::RenderWater() replace this:





	if (m_PatchVector.empty())

With this:





	if (m_PatchVector.empty() && !CTerrainPatch::FULL_MAP_RENDER_ENABLE) // the patch vector will always be empty when using full render

Replace this:





	std::vector<std::pair<float, long> >::iterator i;

	for (i = m_PatchVector.begin(); i != m_PatchVector.end(); ++i)
	{
		if (i->first < fFogDistance)
			DrawWater(i->second);
	}

With this:





	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE)
	{
		std::vector<std::pair<float, long> >::iterator i;

		for (i = m_PatchVector.begin(); i != m_PatchVector.end(); ++i)
		{
			if (i->first < fFogDistance)
				DrawWater(i->second);
		}
	}
	else
		DrawFullWater();

Then this:





	for (i = m_PatchVector.begin(); i != m_PatchVector.end(); ++i)
	{
		if (i->first >= fFogDistance)
			DrawWater(i->second);
	}

With this:





	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // this part disables the water behind fog when full render disabled (but we need that water when enabled!)
	{
		std::vector<std::pair<float, long> >::iterator i;

		for (i = m_PatchVector.begin(); i != m_PatchVector.end(); ++i)
		{
			if (i->first >= fFogDistance)
				DrawWater(i->second);
		}
	}

 

Now the render should be done, but you have increase the fog level in the constinfo, because it controls the "eye", which cuts the terrain and the other things by that white thing... For me example:

ReIlJUF.png

Stage 3

Spoiler

Not right now :P  So yeah as you may noticed on the video, the building rendering is the same as the terrain rendering, so only renders the building on the current and on 8 others chunks. If you have fog enabled you probably won't notice that they are not there :D Hopefully I will do it sometimes soon, but no promises...

Stage 4

Spoiler

Yeeeah so atm if you want to enable it you have to set the m_isFullMapRenderEnable to true, its enable by default (you can set it in CMapManager::CMapManager() in the MapManager.cpp, but if you don't want to wait me to do it you can easily code it, just have to call SetFullMapRender when you load the config file (and add a new variable to the config ofc).

So yeah this is it so far, if you have problem with the code (not compiling, etc) is probably because you did something wrong, I made these steps too on a full untouched client source so it should work for you too... (So pls don't ask me to help with this :D ) However, if something is not clear in the tutorial you can ask me for pictures or clarification. I didn't test it in dungeons (indoor maps) so there could be problems...

Also if you made this and you have some nice maps or you find some great spots for some ingame pictures or videos, feel free to post them here :)

Edited by Metin2 Dev
Core X - External 2 Internal
  • Metin2 Dev 4
  • Sad 1
  • Think 1
  • Confused 2
  • Good 1
  • muscle 1
  • Love 1
  • Love 26

The one and only UI programming guideline

Link to comment
Share on other sites

  • Premium
35 minutes ago, .T4Ump said:

Will it stay in the cache? When several times teleport or every teleport again loading all? @masodikbela

The loaded terrains and objects should be cleared when the bool CMapOutdoor::Destroy() is called, which should happen every time when you change map. If you want to make sure if its working as its intended, you can put a MessageBoxA into that function, and it will appear when its called.

  • Love 2

The one and only UI programming guideline

Link to comment
Share on other sites

  • Premium
1 hour ago, alondark said:

Well. Do you know how can i make real-time shadow on trees and models like character?

I haven't thinking about it before so no, but I guess it has a purpose, for example rendering static shadows is way more easier and costs less processing time than rendering dynamic shadows...

The one and only UI programming guideline

Link to comment
Share on other sites

How are you supposed to make an option in gameoption if you deleted these functions 

 

void CMapOutdoor::FPushTerrainToDeleteVector::operator () (CTerrain * pTerrain)
void CMapOutdoor::FPushAreaToDeleteVector::operator () (CArea * pArea)
void CMapOutdoor::__ClearGarvage()
void CMapOutdoor::__UpdateGarvage()
void CMapOutdoor::UpdateAreaList(long lCenterX, long lCenterY)
Link to comment
Share on other sites

  • Premium
9 hours ago, PeaceMaker said:

How are you supposed to make an option in gameoption if you deleted these functions 

 


void CMapOutdoor::FPushTerrainToDeleteVector::operator () (CTerrain * pTerrain)
void CMapOutdoor::FPushAreaToDeleteVector::operator () (CArea * pArea)
void CMapOutdoor::__ClearGarvage()
void CMapOutdoor::__UpdateGarvage()
void CMapOutdoor::UpdateAreaList(long lCenterX, long lCenterY)

Well if you do the first step, the client will load the whole map into the memory. This would increase the memory usage a little bit but would increase the performance too. Ofc even if the full render is disabled, this can't be disable because like you said I erased the garbage collector. But I think its not that big change, should not cause any problem even on low spec PCs, no need to worry about it.

  • Love 3

The one and only UI programming guideline

Link to comment
Share on other sites

  • Premium
2 hours ago, zeimpekis9 said:

I installed it without problems, it worked, but now my fps is lower. Why ??

Because of obvious reasons I described at the beginning of the topic... Like rendering everything would use more resources than rendering things around your character...

The one and only UI programming guideline

Link to comment
Share on other sites

  • Premium

You can disable the shadows cus as far as I saw that consumes the most... however the whole rendering thing is old and not well optimized, also it could be that I made mistakes in this solution and it could be improved too, but I think the real problem is the rendering itself... Btw the fps in metin is capped at 60 and I had somewhere between 40 and 60 with this on the original map1s(cus the map can also be the problem for your low fps).

So in short: no I don't plan to improve it...

The one and only UI programming guideline

Link to comment
Share on other sites

  • 2 weeks later...
  • 4 years later...
On 06.09.2018 at 17:02, masodikbela said:

Merhaba devler,

Tamam, başlığı okuduktan sonra "Hollllllllly sh..", "Oooookay..." veya sadece "?" gibi bazı düşünceleriniz/sorunuz olabilir (veya olmayabilir). Yaklaşık 1-2 yıl önce bir haritacı bana şunu sordu: "Bu, tüm haritayı görünür kılmak mümkün mü? Normal oyunlardaki gibi mi? Bu sis çok iğrenç, normal bir sisle daha güzel bir manzara yaratabilirdim..." Yaklaşık bir yıl önce yaptığım uykusuz bir geceden sonra bunu birçok şekilde yapmaya çalıştım. Bir kez bittiğinde, onunla ne yapacağımı bilmiyordum. Oldukça iyiydi (sanırım) ama haritacı olmadığım için onunla hiçbir şey yapamam. Satabilirdim ama bu kodu korumanın bir yolu olmadığı için bu benim için bir seçenek değil, bu yüzden onu halkla paylaşmaya karar verdim.

"Vay canına, bu aslında fena değil" Bu manzarayı ilk gördüğümde bu benim ilk tepkimdi. AMA benden bu kadar, daha önemli şeylerden bahsedelim...

Bu yüzden bu eğitim için aşamalar oluşturmaya karar verdim (aslında 4 aşama). Bu normal bir aşağıdan yukarıya doğru bir şey, yani 1., 2. aşamayı yapabilirsin ve sonra durabilirsin, ama 2. aşamayı 1 olmadan, 3'ü 2 olmadan ve 4'ü 3 olmadan yapamazsın... bu değişiklikleri özelleştirin. Ayrıca her aşama için bazı "ilginç" (en azından benim için) notlar/düşünceler yazabilirim. Ancak, göreceğiniz üzere son 2 aşama şu anda hazır değil, bu yüzden deneysel bir sürüm olduğunu söylemeyi tercih ederim... Neyse, bunu uygulamaya başlamadan önce testler, performans ve diğer az çok önemli hakkında konuşalım. şeyler.

İçerik tablosu:

  • Aşama 1: Çöp toplayıcıları kaldırmak, haritayla ilgili tüm şeyleri belleğe yüklemek ("parçalar", nesneler vb.)
  • Aşama 2: Tam arazi oluşturma
  • Aşama 3: Tüm nesneleri haritada oluşturma [HENÜZ HAZIR DEĞİL]
  • 4. Aşama: Oyuncular için yapılandırma oluşturun [HENÜZ HAZIR DEĞİL]

Verim

Bir oyunla ilgili en önemli sorulardan biri: Onu teknik özelliklerimle oynayabilecek miyim? Tabi bu pek çok şeye bağlı. Yüksek poli nesneler, büyük haritalar, HD dokular, çok sayıda ağaç, efekt kullanmak, performans/oluşturma süresini oldukça etkileyebilir. Bu konuda kısaca söyleyebileceğim tek şey: bu gerçekten haritalarınıza (ve tabii ki oyuncunun makinesine) bağlıdır. Şimdiye kadar bu eski dostum hemen hemen her bilgisayarda çalıştırılabilirdi, ancak bununla bir şeyler değişebilir, bu yüzden bu özelliği etkinleştirerek veya devre dışı bırakarak bunun için yapılandırma oluşturdum.

Deneyimlerim/testlerim hakkında

Bellek kullanımı, varsayılan ~270 MB'den ~300 MB'a yükseldi (daha sonra açıklamalara bakın), işlemci kullanımı değişmedi (~%5-10). Ekran kartı kullanımım hakkında izleyemediğim için bir şey diyemem ama kullanımının (hem video belleği hem de işlemci) arttığına eminim... Videoda biraz gecikme fark etmişsinizdir belki ama durumda, bunun tek nedeni kayıt cihazım, oyun hala sorunsuz çalışıyor (unutmayın, bilgisayara ve haritaya bağlı...) Eğer yardımcı olursa bir Lenovo U41-70 dizüstü bilgisayarım var (Intel i7 5500U, 8GB DDR3 ve GeForce) 920M) ve "(eski) wom2 haritalarını" kullandım.

Ancak, tüm gölgeler etkinken bazı fps düşüşleri fark ettim ve birçok karakterle test edemedim (çünkü karakter oluşturma bu olmadan da çok fazla işlem süresi tüketir), bu nedenle varsayılan olarak devre dışı bırakmanızı ve oyuncu karar verirse tavsiye ederim. kullanmak için, onu açabilecektir.

1. Aşama

  Gizli içerikleri ortaya çıkarın

Some personal hints:

First let me explain what I meant when I said "chunks". As we all know, each map has a size, like 1x1, 2x2... If a map's size is 2x2 then it has 4 chunks... (I hope its clear now :P )
Before we start to render the universe we need all the map related things already been loaded and ready for use in the memory. By default the game only loads maximum 9 chunks (the one you are standing on, and when its possible 8 other adjacent ones) but its not enough for us, for example when we are standing on a corner chunk of a 2x3 map we have only 4 chunks loaded so another 2 chunks will be "white" and only levitating trees will be seen. By the way this is the reason when we see hovering trees on the maps: The trees are rendered mapwide by default so even when we don't see them they are there so when the "eye"'s edge is near to a hill or something the tree appears before the terrain.

You can do this change even if you don't want to "render the galaxy". You may wonder why... The first reason is the useless garbage collector which runs every rendering time and tries to collect unused objects, textures, chunks (even if we don't have any). It could be useful too but in these days its just fcking with the processor... The other reason is quite simple. When you leave a chunk the game instantly starts to load 3 other one (in the worst case) and it brakes the render for some ms which cause an fps drop.

In this part we will meet with some usual code near to the heart of the area/terrain rendering (no dragons here sry) and we will delete some of them :P Although looks like this part is nearly untouched since the first release of the game and also looks like this part was written by a bit competent person (except the gb collector...) since its not the usual gf like trash code.

Install (FINALLY)

GameLib/MapOutdoorUpdate.cpp

  Hide contents

in bool CMapOutdoor::Update(float fX, float fY, float fZ) replace this:

	if ( bNeedInit ||
		(m_CurCoordinate.m_sTerrainCoordX/LOAD_SIZE_WIDTH) != (sCoordX/LOAD_SIZE_WIDTH) || 
		(m_CurCoordinate.m_sTerrainCoordY/LOAD_SIZE_WIDTH) != (sCoordY/LOAD_SIZE_WIDTH) )
	{
		if (bNeedInit)
		{
			m_PrevCoordinate.m_sTerrainCoordX = sCoordX;
			m_PrevCoordinate.m_sTerrainCoordY = sCoordY;
		}
		else
		{
			m_PrevCoordinate.m_sTerrainCoordX = m_CurCoordinate.m_sTerrainCoordX;
			m_PrevCoordinate.m_sTerrainCoordY = m_CurCoordinate.m_sTerrainCoordY;
		}
		
		m_CurCoordinate.m_sTerrainCoordX = sCoordX;
		m_CurCoordinate.m_sTerrainCoordY = sCoordY;
		m_lCurCoordStartX = sCoordX * CTerrainImpl::TERRAIN_XSIZE;
		m_lCurCoordStartY = sCoordY * CTerrainImpl::TERRAIN_YSIZE;

		WORD wCellCoordX = (ix % CTerrainImpl::TERRAIN_XSIZE) / CTerrainImpl::CELLSCALE;
		WORD wCellCoordY = (iy % CTerrainImpl::TERRAIN_YSIZE) / CTerrainImpl::CELLSCALE;

		short sReferenceCoordMinX, sReferenceCoordMaxX, sReferenceCoordMinY, sReferenceCoordMaxY;
		sReferenceCoordMinX = max(m_CurCoordinate.m_sTerrainCoordX - LOAD_SIZE_WIDTH, 0);
		sReferenceCoordMaxX = min(m_CurCoordinate.m_sTerrainCoordX + LOAD_SIZE_WIDTH, m_sTerrainCountX - 1);
		sReferenceCoordMinY = max(m_CurCoordinate.m_sTerrainCoordY - LOAD_SIZE_WIDTH, 0);
		sReferenceCoordMaxY = min(m_CurCoordinate.m_sTerrainCoordY + LOAD_SIZE_WIDTH, m_sTerrainCountY - 1);
		
		for (WORD usY = sReferenceCoordMinY; usY <=sReferenceCoordMaxY; ++usY)
		{
			for (WORD usX = sReferenceCoordMinX; usX <= sReferenceCoordMaxX; ++usX)
			{
				LoadTerrain(usX, usY, wCellCoordX, wCellCoordY);
  				LoadArea(usX, usY, wCellCoordX, wCellCoordY);
			}
		}

		AssignTerrainPtr();
		m_lOldReadX = -1;

		Tracenf("Update::Load spent %d ms\n", ELTimer_GetMSec() - t1);
	}

with this:

	if (bNeedInit ||
		m_CurCoordinate.m_sTerrainCoordX / LOAD_SIZE_WIDTH != sCoordX / LOAD_SIZE_WIDTH ||
		m_CurCoordinate.m_sTerrainCoordY / LOAD_SIZE_WIDTH != sCoordY / LOAD_SIZE_WIDTH)
	{
		if (bNeedInit)
		{
			m_PrevCoordinate.m_sTerrainCoordX = sCoordX;
			m_PrevCoordinate.m_sTerrainCoordY = sCoordY;
			
			//during the loading screen load everything
			for (WORD usY = 0; usY < m_sTerrainCountY; ++usY)
			{
				for (WORD usX = 0; usX < m_sTerrainCountX; ++usX)
				{
					LoadTerrain(usX, usY);
					LoadArea(usX, usY);
				}
			}
		}
		else
		{
			m_PrevCoordinate.m_sTerrainCoordX = m_CurCoordinate.m_sTerrainCoordX;
			m_PrevCoordinate.m_sTerrainCoordY = m_CurCoordinate.m_sTerrainCoordY;
		}

		m_CurCoordinate.m_sTerrainCoordX = sCoordX;
		m_CurCoordinate.m_sTerrainCoordY = sCoordY;
		m_lCurCoordStartX = sCoordX * CTerrainImpl::TERRAIN_XSIZE;
		m_lCurCoordStartY = sCoordY * CTerrainImpl::TERRAIN_YSIZE;

		AssignTerrainPtr();
		m_lOldReadX = -1;

		Tracenf("Update::Load spent %d ms\n", ELTimer_GetMSec() - t1);
	}

Also delete this from the function:

	__UpdateGarvage();

In void CMapOutdoor::UpdateTerrain(float fX, float fY) delete this:

	UpdateAreaList(lRealCenterX, lRealCenterY);

Under this function delete the following functions:

void CMapOutdoor::FPushTerrainToDeleteVector::operator () (CTerrain * pTerrain)
void CMapOutdoor::FPushAreaToDeleteVector::operator () (CArea * pArea)
void CMapOutdoor::__ClearGarvage()
void CMapOutdoor::__UpdateGarvage()
void CMapOutdoor::UpdateAreaList(long lCenterX, long lCenterY)

 

GameLib/MapOutdoor.h

  Hide contents

Delete these things:

		virtual void	__ClearGarvage();
		virtual void	__UpdateGarvage();
		virtual void	UpdateAreaList(long lCenterX, long lCenterY);

And replace these ones (those args are unused, so its just "cleaning"... 😞

		virtual bool	LoadTerrain(WORD wTerrainCoordX, WORD wTerrainCoordY, WORD wCellCoordX, WORD wCellCoordY);
		virtual bool	LoadArea(WORD wAreaCoordX, WORD wAreaCoordY, WORD wCellCoordX, WORD wCellCoordY);

With these ones:

		virtual bool	LoadTerrain(WORD wTerrainCoordX, WORD wTerrainCoordY);
		virtual bool	LoadArea(WORD wAreaCoordX, WORD wAreaCoordY);

Remove also these things:

		TTerrainPtrVector			m_TerrainDeleteVector;
		TTerrainPtrVectorIterator	m_TerrainPtrVectorIterator;
		TTerrainPtrVector			m_TerrainLoadWaitVector;
		TTerrainPtrVector			m_TerrainLoadRequestVector;

		TAreaPtrVector				m_AreaDeleteVector;
		TAreaPtrVectorIterator		m_AreaPtrVectorIterator;
		TAreaPtrVector				m_AreaLoadWaitVector;
		TAreaPtrVector				m_AreaLoadRequestVector;

		struct FPushToDeleteVector
		{
			enum EDeleteDir
			{
				DELETE_LEFT,
				DELETE_RIGHT,
				DELETE_TOP,
				DELETE_BOTTOM,
			};

			EDeleteDir m_eLRDeleteDir;
			EDeleteDir m_eTBDeleteDir;
			TOutdoorMapCoordinate m_CurCoordinate;

			FPushToDeleteVector(EDeleteDir eLRDeleteDir, EDeleteDir eTBDeleteDir, TOutdoorMapCoordinate CurCoord)
			{
				m_eLRDeleteDir = eLRDeleteDir;
				m_eTBDeleteDir = eTBDeleteDir;
				m_CurCoordinate = CurCoord;
			}
		};
		struct FPushTerrainToDeleteVector : public FPushToDeleteVector
		{
			TTerrainPtrVector	m_ReturnTerrainVector;

			FPushTerrainToDeleteVector(EDeleteDir eLRDeleteDir, EDeleteDir eTBDeleteDir, TOutdoorMapCoordinate CurCoord)
				: FPushToDeleteVector(eLRDeleteDir, eTBDeleteDir, CurCoord)
			{
				m_ReturnTerrainVector.clear();
			}

			void operator() (CTerrain * pTerrain);
		};

		struct FPushAreaToDeleteVector : public FPushToDeleteVector
		{
			TAreaPtrVector		m_ReturnAreaVector;

			FPushAreaToDeleteVector(EDeleteDir eLRDeleteDir, EDeleteDir eTBDeleteDir, TOutdoorMapCoordinate CurCoord)
				: FPushToDeleteVector(eLRDeleteDir, eTBDeleteDir, CurCoord)
			{
				m_ReturnAreaVector.clear();
			}

			void operator() (CArea * pArea);
		};

 

MapOutdoorLoad.cpp

  Hide contents

Replace this:

bool CMapOutdoor::LoadArea(WORD wAreaCoordX, WORD wAreaCoordY, WORD wCellCoordX, WORD wCellCoordY)

with this:

bool CMapOutdoor::LoadArea(WORD wAreaCoordX, WORD wAreaCoordY)

And this:

bool CMapOutdoor::LoadTerrain(WORD wTerrainCoordX, WORD wTerrainCoordY, WORD wCellCoordX, WORD wCellCoordY)

With this:

bool CMapOutdoor::LoadTerrain(WORD wTerrainCoordX, WORD wTerrainCoordY)

 

MapOutdoor.cpp

  Hide contents

Delete these ones from void Initialize():

	m_TerrainDeleteVector.clear();
	m_TerrainLoadRequestVector.clear();
	m_TerrainLoadWaitVector.clear();
	
	m_AreaDeleteVector.clear();
	m_AreaLoadRequestVector.clear();
	m_AreaLoadWaitVector.clear();

From void CMapOutdoor::DestroyArea() delete this:

void CMapOutdoor::DestroyTerrain() öğesinden şunu silin :

 

 

2. aşama

  Gizli içerikleri ortaya çıkarın

Bazı kişisel ipuçları:

Tamam, şimdi buradayız, oyunun en karmaşık kısımlarından biri (devlet yöneticisinden sonra) ve burada dörtlü ağaç ve araziPatchProxy (evet ilk şeyler fck dışarı atacağız). Bahsedilen bu iki şey (dörtlü ağaç ve proxy) mesafe oluşturmayı yönetiyor. Sadece ayakta dururken ve arazinin yerden rastgele büyüdüğünü veya rastgele kaybolduğunu fark ettiğinizde, bunun nedeni bu adamlar (oluşturma "uzak veya yakın" seçeneğine bağlıdır). Tüm haritayı oluşturmak istediğimiz için böyle bir sihire ihtiyacımız yok.

Önemli: Yalnızca HTP oluşturmayı değiştirdim, bu, yalnızca donanım döşeme seçildiğinde (ve tam oluşturma etkinleştirildiğinde) çalışacağı anlamına gelir.

GameLib/MapManager.h

  Gizli içerikleri ortaya çıkarın

Bundan sonra:

			

Bunu ekle:

			    
			  

Bundan sonra:

		

Bunu ekle:

		

 

GameLib/MapManager.cpp

  Gizli içerikleri ortaya çıkarın

CMapManager ::CMapManager() içinde şunu ekleyin:

 

Bundan sonra:

Bundan sonra void CMapManager::Create() içinde:

	

Bunu ekle:

	 

 

GameLib/TerrainPatch.h

  Gizli içerikleri ortaya çıkarın

Bundan sonra:

	 

Bunu ekle:

	 

 

GameLib/TerrainPatch.cpp

  Gizli içerikleri ortaya çıkarın

Bundan sonra:

bool CTerrainPatch::SOFTWARE_TRANSFORM_PATCH_ENABLE=TRUE;

Add this:

bool CTerrainPatch::FULL_MAP_RENDER_ENABLE = false;

 

GameLib/MapOutdoor.cpp

  Gizli içerikleri ortaya çıkarın

In void CMapOutdoor::CreateTerrainPatchProxyList() replace this:

	m_pTerrainPatchProxyList = new CTerrainPatchProxy[m_wPatchCount * m_wPatchCount];

With this:

	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // don't need this nasty buddy for full map render
		m_pTerrainPatchProxyList = new CTerrainPatchProxy[m_wPatchCount * m_wPatchCount];

 

GameLib/MapOutdoor.h

  Gizli içerikleri ortaya çıkarın

After this:

		void					DrawWater(long patchnum);

Add this:

		void					DrawFullWater();

After this:

		void __RenderTerrain_RenderHardwareTransformPatch();

Add this:

		void RenderWholeMapMB(WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType);

 

GameLib/MapOutdoorLoad.cpp

  Gizli içerikleri ortaya çıkarın

In bool CMapOutdoor::Load(float x, float y, float z) relpace this:

	BuildQuadTree();

With this:

	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // we don't need this too
		BuildQuadTree();

 

GameLib/MapOutdoorRender.cpp

  Gizli içerikleri ortaya çıkarın

In void CMapOutdoor::RenderTerrain() replace this:

	if (!m_pTerrainPatchProxyList)

With this:

	if (!m_pTerrainPatchProxyList && !CTerrainPatch::FULL_MAP_RENDER_ENABLE) // we don't use proxy and full render together

Replace this:

	m_PatchVector.clear();
	
	__RenderTerrain_RecurseRenderQuadTree(m_pRootNode);
	
	// °Ĺ¸®Ľř Á¤·Ä
	std::sort(m_PatchVector.begin(),m_PatchVector.end());

With this:

	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE)
	{
		// this part responsible for the distance sorting for the basic render
		m_PatchVector.clear();
		__RenderTerrain_RecurseRenderQuadTree(m_pRootNode);
		std::sort(m_PatchVector.begin(), m_PatchVector.end());
	}

 

GameLib/MapOutdoorRenderHTP.cpp

  Gizli içerikleri ortaya çıkarın

Add this to the end of the file:

void CMapOutdoor::RenderWholeMapMB(WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType)
{
	// now here we might see some dragons, cous I don't know everything's functionality here (and when I know it its because I messed them up before lol...)
	// its mostly a mixed up code from the original ones
	DWORD dwFogColor;
	bool bEnableFog = false;
	if (mc_pEnvironmentData) // check if the fog is enabled and its color
	{
		dwFogColor = mc_pEnvironmentData->FogColor;
		bEnableFog = mc_pEnvironmentData->bFogEnable;
	}

	for (auto it = m_TerrainVector.begin(); it != m_TerrainVector.end(); ++it) // lets start render the whole map (all chunks)
	{
		CTerrain * pTerrain = (*it);

		WORD wCoordX, wCoordY;
		pTerrain->GetCoordinate(&wCoordX, &wCoordY);

		TTerrainSplatPatch & rTerrainSplatPatch = pTerrain->GetTerrainSplatPatch();
		for (BYTE y = 0; y < CTerrainImpl::PATCH_YCOUNT; ++y)
		{
			for (BYTE x = 0; x < CTerrainImpl::PATCH_XCOUNT; ++x)
			{
				// each chunk contains 64 patches, and its rendering order is important, otherwise we might would see some abstract textures :P
				CTerrainPatch* pTerrainPath = pTerrain->GetTerrainPatchPtr(x, y);
				if (!pTerrainPath)
					continue;

				// OKAY we gonna do SOMETHING with alpha texures and tiling here, I'm not a graphical processing expert...
				D3DXMATRIX matTexTransform, matSplatAlphaTexTransform, matSplatColorTexTransform;
				m_matWorldForCommonUse._41 = -(float)(wCoordX * CTerrainImpl::TERRAIN_XSIZE);
				m_matWorldForCommonUse._42 = (float)(wCoordY * CTerrainImpl::TERRAIN_YSIZE);
				D3DXMatrixMultiply(&matTexTransform, &m_matViewInverse, &m_matWorldForCommonUse);
				D3DXMatrixMultiply(&matSplatAlphaTexTransform, &matTexTransform, &m_matSplatAlpha);
				STATEMANAGER.SetTransform(D3DTS_TEXTURE1, &matSplatAlphaTexTransform);

				D3DXMATRIX matTiling;
				D3DXMatrixScaling(&matTiling, 1.0f / 640.0f, -1.0f / 640.0f, 0.0f);
				matTiling._41 = 0.0f;
				matTiling._42 = 0.0f;

				D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &matTiling);
				STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);

				CGraphicVertexBuffer* pkVB = pTerrainPath->HardwareTransformPatch_GetVertexBufferPtr();
				if (!pkVB)
					return;

				STATEMANAGER.SetStreamSource(0, pkVB->GetD3DVertexBuffer(), m_iPatchTerrainVertexSize);
				STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);

				bool isFirst = true;
				for (DWORD j = 1; j < pTerrain->GetNumTextures(); ++j)
				{
					// okay lets start rendering the textures here (still don't fully get this part...)
					TTerainSplat & rSplat = rTerrainSplatPatch.Splats[j];

					if (!rSplat.Active)
						continue;

					if (rTerrainSplatPatch.PatchTileCount[y * CTerrainImpl::PATCH_YCOUNT + x][j] == 0)
						continue;

					const TTerrainTexture & rTexture = m_TextureSet.GetTexture(j);
					D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &rTexture.m_matTransform);
					STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);
					if (isFirst)
					{
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
						STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
						STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
						STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
						isFirst = false;
					}
					else
					{
						STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
						STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
						STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
					}

				}

				if (m_bDrawShadow) // lets render the shadows (its still some sort of graphic processing magic)
				{
					STATEMANAGER.SetRenderState(D3DRS_LIGHTING, TRUE);
					STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, 0xFFFFFFFF);
					STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
					STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);

					D3DXMATRIX matShadowTexTransform;
					D3DXMatrixMultiply(&matShadowTexTransform, &matTexTransform, &m_matStaticShadow);

					STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matShadowTexTransform);
					STATEMANAGER.SetTexture(0, pTerrain->GetShadowTexture());

					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);

					if (m_bDrawChrShadow)
					{
						STATEMANAGER.SetTransform(D3DTS_TEXTURE1, &m_matDynamicShadow);
						STATEMANAGER.SetTexture(1, m_lpCharacterShadowMapTexture);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
					}
					else
					{
						STATEMANAGER.SetTexture(1, NULL);
					}

					ms_faceCount += wPrimitiveCount;
					STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
					++m_iRenderedSplatNum;

					if (m_bDrawChrShadow)
					{
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
						STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
					}

					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
					STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);


					STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
					STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
					if (bEnableFog)
						STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, dwFogColor);

					STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
				}
			}
		}
	}
}

In void CMapOutdoor::__RenderTerrain_RenderHardwareTransformPatch() after this:

	float fFogNearDistance;

Add this:

	bool bEnableFog; //there is an option in the map's config to disable config but LOL its not implemented properly...

After this:

		fFogFarDistance=mc_pEnvironmentData->GetFogFarDistance();

Add this:

		bEnableFog = mc_pEnvironmentData->bFogEnable;

Replace this:

	{
		dwFogColor=0xffffffff;
		fFogNearDistance=5000.0f;
		fFogFarDistance=10000.0f;
	}

With this:

		bEnableFog = false;

Replace this:

	STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, dwFogColor);

With this:

	if (bEnableFog) // we don't need fog render when no fog activated
		STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, dwFogColor);

Replace this:

	DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE);

With this:

	DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE);
	if (!bEnableFog)
		dwFogEnable = 0;

Add this:

	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // when full load is disabled this part renders the near things, without any fog

Before this:

	for( ; it != near_it; ++it)

Before this:

	if (m_iRenderedSplatNum < m_iSplatLimit) //there are two of them this is why I inserted the next few lines too
	{
		for(it = near_it; it != far_it; ++it)
		{

Add this:

	if (CTerrainPatch::FULL_MAP_RENDER_ENABLE)
		RenderWholeMapMB(wPrimitiveCount, ePrimitiveType);

Replace this (there are two of them in the code, replace both):

	if (m_iRenderedSplatNum < m_iSplatLimit)

With this:

	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE && m_iRenderedSplatNum < m_iSplatLimit)

 

GameLib/MapOutdoorUpdate.cpp

  Gizli içerikleri ortaya çıkarın

In void CMapOutdoor::UpdateTerrain(float fX, float fY) before this:

		ConvertTerrainToTnL(lRealCenterX, lRealCenterY);

Add this:

		if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE) // the ConvertTerrainToTnl fills the quadtree, but we don't need this when full render active

 

Okay, now the terrain should be fully rendered if you activate it by setting m_isFullMapRenderEnable = true in the CMapManager::CMapManager() (and remember: you must set the tilling mode to GPU), but yet the water would be missing, so lets fix it.

GameLib/MapOutdoorWater.cpp

  Gizli içerikleri ortaya çıkarın

Add this to the end of the file:

void CMapOutdoor::DrawFullWater()
{
	CTerrain* terra;
	CTerrainPatch* pTerrainPath;
	CGraphicVertexBuffer* pkVB;
	UINT uPriCount;
	for (auto it = m_TerrainVector.begin(); it != m_TerrainVector.end(); ++it)
	{
		terra = *it;
		for (BYTE y = 0; y < CTerrainImpl::PATCH_YCOUNT; ++y)
		{
			for (BYTE x = 0; x < CTerrainImpl::PATCH_XCOUNT; ++x)
			{
				pTerrainPath = terra->GetTerrainPatchPtr(x, y);
				if (!pTerrainPath || !pTerrainPath->IsWaterExist())
					continue;

				pkVB = pTerrainPath->GetWaterVertexBufferPointer();
				if (!pkVB || !pkVB->GetD3DVertexBuffer())
					continue;

				uPriCount = pTerrainPath->GetWaterFaceCount();
				if (!uPriCount)
					return;

				STATEMANAGER.SetStreamSource(0, pkVB->GetD3DVertexBuffer(), sizeof(SWaterVertex));
				STATEMANAGER.DrawPrimitive(D3DPT_TRIANGLELIST, 0, uPriCount);
			}
		}
	}
}

In void CMapOutdoor::RenderWater() replace this:

	if (m_PatchVector.empty())

With this:

	if (m_PatchVector.empty() && !CTerrainPatch::FULL_MAP_RENDER_ENABLE) // the patch vector will always be empty when using full render

Replace this:

	std::vector<std::pair<float, long> >::iterator i;

	for (i = m_PatchVector.begin(); i != m_PatchVector.end(); ++i)
	{
		if (i->first < fFogDistance)
			DrawWater(i->second);
	}

With this:

	if (!CTerrainPatch::FULL_MAP_RENDER_ENABLE)
	{
		std::vector<std::pair<float, long> >::iterator i;

		for (i = m_PatchVector.begin(); i != m_PatchVector.end(); ++i)
		{
			if (i->first < fFogDistance)
				DrawWater(i->second);
		}
	}
	else
		DrawFullWater();

Then this:

	for (i = m_PatchVector.begin(); i != m_PatchVector.end(); ++i)
	{
		if (i->first >= fFogDistance)
			DrawWater(i->second);
	}

With this:

	  
	  

		  
		
			 
				
		
	

 

Şimdi render yapılmalı, ancak constinfo'daki sis seviyesini artırdınız, çünkü araziyi ve diğer şeyleri o beyaz şeyle kesen "göz" ü kontrol ediyor... Benim için örnek:

ReIlJUF.png

Sahne 3

  Gizli içerikleri ortaya çıkarın

Şu anda değil :P  Yani evet, videoda fark edebileceğiniz gibi, bina görselleştirmesi arazi görselleştirmesiyle aynıdır, bu nedenle binayı yalnızca mevcut durumda ve diğer 8 parça üzerinde oluşturur . Sis özelliğini etkinleştirdiyseniz, muhtemelen orada olmadıklarını fark etmeyeceksiniz :DUmarım bazen yakında yapacağım, ama söz yok...

4. Aşama

  Gizli içerikleri ortaya çıkarın

Evet, yani atm, etkinleştirmek istiyorsanız, m_isFullMapRenderEnable'ı true olarak ayarlamanız gerekir, varsayılan olarak etkinleştirme ( MapManager.cpp'deki CMapManager::CMapManager() içinde ayarlayabilirsiniz, ancak beni beklemek istemiyorsanız) Bunu yapmak için kolayca kodlayabilirsiniz , yapılandırma dosyasını yüklediğinizde SetFullMapRender'ı çağırmanız (ve config ofc'ye yeni bir değişken eklemeniz) yeterlidir.

Yani evet, şimdiye kadar bu kadar, kodla ilgili bir sorununuz varsa (derleme değil, vb.) Muhtemelen yanlış bir şey yapmış olmanızdan kaynaklanıyorsa, bu adımları tam el değmemiş bir istemci kaynağında da yaptım, bu yüzden sizin için de çalışması gerekir ... (Bu yüzden lütfen benden bu konuda yardım etmemi istemeyin :D) Ancak, eğitimde net olmayan bir şey varsa, benden resim veya açıklama isteyebilirsiniz. Zindanlarda (iç mekan haritaları) test etmedim, bu yüzden problemler olabilir...

Ayrıca bunu yaptıysanız ve bazı güzel haritalarınız varsa veya bazı oyun içi resimler veya videolar için harika yerler bulursanız, burada yayınlamaktan çekinmeyin. :)

https://metin2.download/picture/BLC850vFvO9grQZyABjkn7szm1I82266/.gif

 

hello, thanks for sharing, when you turn off the map upload, colors appear like this when uploading

Edited by Metin2 Dev
Core X - External 2 Internal
Link to comment
Share on other sites

  • 11 months later...
  • 1 month later...

Thank you very much for sharing with us. I'm currently using DirectX 9, and after making some adjustments, it works perfectly. Of course, there will be a drop in FPS, but nothing too concerning if the client is well-optimized. The "test" in the video was conducted using a potato PC 😂.
If anyone needs assistance, feel free to send me your Discord in PM.

---> https://metin2.download/video/Wj0M4r58o9zM4A3RoiV0qp6MAScTn6Gw/.mp4

Edited by Metin2 Dev International
Core X - External 2 Internal
Link to comment
Share on other sites

Announcements



×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.