Civilization Tactical Combat Development Thread (Input/Suggestions Wanted)

Well, maybe I should look into that then. IMO that would be the optimal solution.
I think the key is to destroy all the entities that are associated with cities and units of the normal map. It might be enough to set them invisible but that needs to be tested.
You can recreate them afterwards.
Then the symbols like feature, routes, rivers need to be removed (not the information itself but their graphical representation via the right interface).
(Fun fact: The Civ4 engine can have more than one feature symbol on the same plot).
Last the plot builders need to be destroyed (they are responsible for improvements and bonuses).
Some fog of war changes might be needed as well.

That should unlink your normal map from the graphics engine and you can then swap it and create the correct graphical representations for the tactical map.

Actually that is not even that much code.
Go through all cities of currently displayed map and destroy (maybe set invisible) the entity.
Go through all units of the currently displayed map and do the same.
Go through all plots of the currently displayed map and deal with its graphical elements (check the different methods in CvPlot.cpp that deal with the symbols or plot builder or fog of war). Rebuilding the plots now might be needed.
Swap the map.
Now do the inverse with the new to be displayed map. For the plots you can probably use the existing methods for setting up the graphics.
Go through all cities and units of the new to be displayed map and create the entity.
Likely RebuildAllPlots should do the job now unless InitGraphics has to be called first.
 
Personally I'm not afraid to wait a little for the new map loading, and maybe this solution is far more simple.

Anyway, both solutions are preferible than the sacrifice of a portion of the map IMO.

But again, I'm not a modder so I have no technical preparation about this matter.


Waiting to see this coming out.
 
@AIAndy - to destroy and then recreate entities you need to store this information somewhere...
You keep your normal CvUnit and CvCity but call their destroyEntity method (they are derived from CvDLLEntity). That does not destroy the CvCity object itself but the CvEntity associated with it, which is an object in the exe code which is then destroyed via an interface:
gDLL->getEntityIFace()->destroyEntity(m_pEntity);

Check CvDLLEntityIFaceBase.h which also contains easy functions to create a CvEntity from a unit or city object.
It might be enough to add/remove the entity or set it visible/invisible with the same interface.
 
I haven't gotten a chance to look at any of the code yet, but wouldn't I need to destroy the actual CvUnit and CvCity objects, so they don't try to access invalid plots in the map? I would obviously have to store the objects somewhere, but I would remove them from CvPlayer so it would appear to the game that the players had no units and cities (except those used in the tactical map).
 
I haven't gotten a chance to look at any of the code yet, but wouldn't I need to destroy the actual CvUnit and CvCity objects, so they don't try to access invalid plots in the map? I would obviously have to store the objects somewhere, but I would remove them from CvPlayer so it would appear to the game that the players had no units and cities (except those used in the tactical map).

I think so; you would also have to add some lines in CvPlayer::verifyAlive() to make the player's not be killed due to not having units.
 
You keep your normal CvUnit and CvCity but call their destroyEntity method (they are derived from CvDLLEntity). That does not destroy the CvCity object itself but the CvEntity associated with it, which is an object in the exe code which is then destroyed via an interface:
gDLL->getEntityIFace()->destroyEntity(m_pEntity);

Check CvDLLEntityIFaceBase.h which also contains easy functions to create a CvEntity from a unit or city object.
It might be enough to add/remove the entity or set it visible/invisible with the same interface.

I don't remember using these interfaces before. I'll have to take a look at them sometime.
 
I haven't gotten a chance to look at any of the code yet, but wouldn't I need to destroy the actual CvUnit and CvCity objects, so they don't try to access invalid plots in the map? I would obviously have to store the objects somewhere, but I would remove them from CvPlayer so it would appear to the game that the players had no units and cities (except those used in the tactical map).
I don't think you need to destroy those objects. For the DLL all objects will still be present. Only the graphics engine will not display them any more.
Some game mechanic code will need to be aware that you are in tactical though (mainly the code dealing with movement or combat).
 
I don't think you need to destroy those objects. For the DLL all objects will still be present. Only the graphics engine will not display them any more.
Some game mechanic code will need to be aware that you are in tactical though (mainly the code dealing with movement or combat).

I believe the problem Asaf and Androrc ran into before, though, was that a unit or city was calling an invalid plot, even after they tried to if out everything that would do this. IMO it would be simpler just to create a temporary storage for the units and cities, and then remove them from the CvPlayer objects. Plus, since the removeEntity() and destroyEntity() functions are called in the destructors, I won't have to worry about calling them either.

EDIT: Or will I? When I copy the units/cities over to the temporary location in memory, I'm creating new instances of them. Since createUnitEntity()/createCityEntity() is called in the constructor, even though the units/cities technically won't be in the game after I remove them from CvPlayer, they'll still have entities. Do I need to destroy these entities as well?
 
Alright, here's the code I have so far:

Spoiler :

PHP:
void CvGlobals::swapMaps()
{
	int i, j;
	CvUnit* pLoopUnit, * pUnit;
	CvCity* pLoopCity, * pCity;
	CvPlayerAI kPlayer;
	std::vector<FFreeListTrashArray<CvCityAI> > m_cities;
	std::vector<FFreeListTrashArray<CvUnitAI> > m_units;
	
	setTacticalMap(true);     // used in CvPlayer::verifyAlive() so that the players aren't destroyed
	
	for (i = 0; i < MAX_PLAYERS; i++)
	{
		kPlayer = GET_PLAYER((PlayerTypes)i);
		
		if (kPlayer.isAlive())
		{
			FFreeListTrashArray<CvUnitAI> units;
			FFreeListTrashArray<CvCityAI> cities;

			m_units.push_back(units);
			m_cities.push_back(cities);
			
			for (j = 0; j < kPlayer.getNumUnits(); j++)
			{
				pLoopUnit = kPlayer.getUnit(j);
				pUnit = m_units[i].add();
				*pUnit = *pLoopUnit;
				kPlayer.deleteUnit(j);     // do I need pUnit->destroyEntity() here?
			}

			for (j = 0; j < kPlayer.getNumCities(); j++)
			{
				pLoopCity = kPlayer.getCity(j);
				pCity = m_cities[i].add();
				*pCity = *pLoopCity;
				kPlayer.deleteCity(j);     // do I need pCity->destroyEntity() here?
			}
		}
	}

	for (i = 0; i < GC.getMap().numPlots(); i++)
	{
		getMap().plotByIndex(i)->destroyGraphics();
	}

	CvMap* pTempMap = m_map;
	m_map = m_altMap;     // m_altMap defined in CvGlobals.h
	m_altMap = pTempMap;
}

void CvPlot::destroyGraphics()
{
	gDLL->getFeatureIFace()->destroy(m_pFeatureSymbol);
	if(m_pPlotBuilder)
	{
		gDLL->getPlotBuilderIFace()->destroy(m_pPlotBuilder);
	}
	gDLL->getRouteIFace()->destroy(m_pRouteSymbol);
	gDLL->getRiverIFace()->destroy(m_pRiverSymbol);
	gDLL->getFlagEntityIFace()->destroy(m_pFlagSymbol);
	gDLL->getFlagEntityIFace()->destroy(m_pFlagSymbolOffset);
	m_pCenterUnit = NULL;

	deleteAllSymbols();
}


it's compiling fine, but I have yet to test it out.
 
The unit and city stuff won't work like this.
deleteUnit and deleteCity mainly remove the city/unit from the list of the player object. It does not do anything about the unit or city object itself.

You also can't copy a city or unit object like that.
What you are doing is that first the normal constructor is called when you create the new object. That among other things also creates another entity which is automatically linked to the engine. Then you do a shallow copy of the object which means that all the dynamic allocations that the old object has are not copied but only the pointers to that. So both objects point to the same memory now. Would you actually delete the old object now, then it would call the destructor and delete all those dynamic allocations and the new object would have a lot of pointers that point to freed memory which might be overwritten at any time.

EDIT: Btw, I have done some experiments myself with removing/destroying all city or unit entities but it crashed somewhere in the exe code afterwards so likely there is some other delinking that the kill operation for the unit or city does that is important. Maybe the link between plot and city (as I did not actually swap the map in my tests, I just wanted to get rid of all unit and city visual representations).
 
Those are some great points...I just wish I had thought of them myself :rolleyes:.

Though, deleteUnit()/deleteCity() call FFreeListTrashArray<T>::removeAt(), which, unless I'm wrong, does in fact delete the objects.

Instead of actually destroying the units and cities, I decided to create two more FFreeListTrashArrays in CvPlayer called m_altUnits and m_altPlayers. They can now hold the units and cities in the map/game not currently in use. Here's the current code:

Spoiler :
Code:
void CvGlobals::swapMaps()
{
	int i, iLoop;
	CvPlayerAI kPlayer;
	CvUnit* pLoopUnit;
	CvCity* pLoopCity;

	GC.setMapSwapping(true);

	for (i = 0; i < MAX_PLAYERS; i++)
	{
		kPlayer = GET_PLAYER((PlayerTypes)i);
		
		if (kPlayer.isAlive())
		{
			kPlayer.swapUnitsAndCities();

			for (pLoopCity = kPlayer.firstCity(&iLoop); pLoopCity != NULL; pLoopCity = kPlayer.nextCity(&iLoop))
			{
				pLoopCity->removeEntity();
				pLoopCity->destroyEntity();
			}
			
			for (pLoopUnit = kPlayer.firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = kPlayer.nextUnit(&iLoop))
			{
				if (GC.IsGraphicsInitialized())
				{
					gDLL->getEntityIFace()->RemoveUnitFromBattle(pLoopUnit);
					pLoopUnit->removeEntity();
				}
				pLoopUnit->destroyEntity();
			}
		}
	}

	for (i = 0; i < GC.getMap().numPlots(); i++)
	{
		GC.getMap().plotByIndex(i)->destroyGraphics();
	}

	CvMap* tempMap = m_map;
	m_map = m_altMap;
	m_altMap = tempMap;
}

void CvPlayer::swapUnitsAndCities()
{
	FFreeListTrashArray<CvCityAI> tempCities;
	FFreeListTrashArray<CvUnitAI> tempUnits;

	tempCities = m_cities;
	m_cities = m_altCities;
	m_altCities = tempCities;

	tempUnits = m_units;
	m_units = m_altUnits;
	m_altUnits = tempUnits;
}

void CvPlot::destroyGraphics()
{
	gDLL->getFeatureIFace()->destroy(m_pFeatureSymbol);
	if(m_pPlotBuilder)
	{
		gDLL->getPlotBuilderIFace()->destroy(m_pPlotBuilder);
	}
	gDLL->getRouteIFace()->destroy(m_pRouteSymbol);
	gDLL->getRiverIFace()->destroy(m_pRiverSymbol);
	gDLL->getFlagEntityIFace()->destroy(m_pFlagSymbol);
	gDLL->getFlagEntityIFace()->destroy(m_pFlagSymbolOffset);
	m_pCenterUnit = NULL;

	deleteAllSymbols();
}

Since I'm not deleting any of the objects, the copying should be fine here, correct?
 
Those are some great points...I just wish I had thought of them myself :rolleyes:.

Though, deleteUnit()/deleteCity() call FFreeListTrashArray<T>::removeAt(), which, unless I'm wrong, does in fact delete the objects.
You are right. Unless what I expected it actually deletes objects instead of just removing them from the list.

Instead of actually destroying the units and cities, I decided to create two more FFreeListTrashArrays in CvPlayer called m_altUnits and m_altPlayers. They can now hold the units and cities in the map/game not currently in use. Here's the current code:

Since I'm not deleting any of the objects, the copying should be fine here, correct?
Correct, the shallow copy of the list should just copy the head which does not contain data itself. There is just a small problem left.
At the end of swapUnitsAndCities the temporary lists reach the end of their life and their destructor is called. Because of the shallow copy they contain the data of the alt lists and therefore those lists get destroyed (except for the head which now has a bad pointer). You need to restore the lists to their original states manually.
Apart from that it looks fine and I am looking forward to hearing if it works.
 
You need to restore the lists to their original states manually.

How would I go about manually copying the lists? If I loop through all the units/cities and copy them over, I'll have the same problem I had before because of the shallow copy. If I could just set tempCities.m_pArray and tempUnits.m_pArray to NULL after I copied them, the destructor wouldn't delete the objects, but the only way I can see to do this if I make m_pArray public or create a public function to return it so I can access it in CvPlayer. I'm sure there's a better method than this, but I'm having trouble finding it.
 
One option would be to have two global lists that you keep empty and then do a shallow copy to make the temp lists empty.
 
I'm running into a problem with the terrain of the plots on the new map. After I switch the map objects themselves, I call GC.getMap().init() and then set the terrain type of each plot to grassland (for the time being).

The problem comes when setTerrainType() calls gDLL->getEngineIFace()->RebuildPlot(). A few functions later (all in the exe) getTerrainInfo(TerrainTypes eTerrainNum) is called. When it's called, eTerrainNum is set to 255. Assuming the function is still dealing with the same plot, I have no idea how eTerrainNum gets to 255, since I set m_eTerrainType of the current plot to "TERRAIN_GRASS" (in other words, 0) in setTerrainType(). Any idea what's going on here?
 
I'm running into a problem with the terrain of the plots on the new map. After I switch the map objects themselves, I call GC.getMap().init() and then set the terrain type of each plot to grassland (for the time being).

The problem comes when setTerrainType() calls gDLL->getEngineIFace()->RebuildPlot(). A few functions later (all in the exe) getTerrainInfo(TerrainTypes eTerrainNum) is called. When it's called, eTerrainNum is set to 255. Assuming the function is still dealing with the same plot, I have no idea how eTerrainNum gets to 255, since I set m_eTerrainType of the current plot to "TERRAIN_GRASS" (in other words, 0) in setTerrainType(). Any idea what's going on here?
Have you set the plot type before setting the terrain type?
 
If I remember correctly, the plot type is set in the constructor (or some function called by it). Anyway, I fixed that problem by setting bUpdateGraphical to false. It didn't matter anyway, since I call rebuildAllPlots() after I set the terrain types, and now that part is working fine.

Now I'm having problems with the unit entities, though. Here's the full code for swapMaps()

Spoiler Code :
Code:
void CvGlobals::swapMaps()
{
	int i, iLoop;
	CvPlayerAI kPlayer;
	CvUnit* pLoopUnit;
	CvCity* pLoopCity;

	GC.setMapSwapping(true);

	for (i = 0; i < MAX_PLAYERS; i++)
	{
		kPlayer = GET_PLAYER((PlayerTypes)i);
		
		if (kPlayer.isAlive())
		{
			for (pLoopCity = kPlayer.firstCity(&iLoop); pLoopCity != NULL; pLoopCity = kPlayer.nextCity(&iLoop))
			{
				pLoopCity->removeEntity();
				pLoopCity->destroyEntity();
			}
			
			for (pLoopUnit = kPlayer.firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = kPlayer.nextUnit(&iLoop))
			{
				if (GC.IsGraphicsInitialized())
				{
					gDLL->getEntityIFace()->RemoveUnitFromBattle(pLoopUnit);
					pLoopUnit->removeEntity();
				}
				pLoopUnit->destroyEntity();
			}
			
			kPlayer.swapUnitsAndCities();

			for (pLoopCity = kPlayer.firstCity(&iLoop); pLoopCity != NULL; pLoopCity = kPlayer.nextCity(&iLoop))
			{
				pLoopCity->removeEntity();
				pLoopCity->destroyEntity();
			}
			
			for (pLoopUnit = kPlayer.firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = kPlayer.nextUnit(&iLoop))
			{
				if (GC.IsGraphicsInitialized())
				{
					gDLL->getEntityIFace()->RemoveUnitFromBattle(pLoopUnit);
					pLoopUnit->removeEntity();
				}
				pLoopUnit->destroyEntity();
			}
		}
	}

	for (i = 0; i < GC.getMap().numPlots(); i++)
	{
		GC.getMap().plotByIndex(i)->destroyGraphics();
	}

	CvMap* tempMap = m_map;
	m_map = m_altMap;
	m_altMap = tempMap;

	GC.getMap().init();
	for (i = 0; i < GC.getMap().numPlots(); i++)
	{
		GC.getMap().plotByIndex(i)->setTerrainType((TerrainTypes)GC.getInfoTypeForString("TERRAIN_GRASS"), false, false);
	}
	gDLL->getEngineIFace()->RebuildAllPlots();
	GC.getMap().setupGraphical();

	for (pLoopCity = kPlayer.firstCity(&iLoop); pLoopCity != NULL; pLoopCity = kPlayer.nextCity(&iLoop))
	{
		gDLL->getEntityIFace()->createCityEntity(pLoopCity);
		pLoopCity->setupGraphical();
	}
	
	for (pLoopUnit = kPlayer.firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = kPlayer.nextUnit(&iLoop))
	{
		gDLL->getEntityIFace()->createUnitEntity(pLoopUnit);
		pLoopUnit->setupGraphical();
	}
}

As you can see, I'm creating a unit entity for each unit after I swap the maps. However, when CvDLLEntity::isSelected() calls getEntity() for a unit, it returns NULL. I'm guessing there's another function somewhere I have to call?
 
If I remember correctly, the plot type is set in the constructor (or some function called by it). Anyway, I fixed that problem by setting bUpdateGraphical to false. It didn't matter anyway, since I call rebuildAllPlots() after I set the terrain types, and now that part is working fine.

Now I'm having problems with the unit entities, though. Here's the full code for swapMaps()

Spoiler Code :
Code:
void CvGlobals::swapMaps()
{
	int i, iLoop;
	CvPlayerAI kPlayer;
	CvUnit* pLoopUnit;
	CvCity* pLoopCity;

	GC.setMapSwapping(true);

	for (i = 0; i < MAX_PLAYERS; i++)
	{
		kPlayer = GET_PLAYER((PlayerTypes)i);
		
		if (kPlayer.isAlive())
		{
			for (pLoopCity = kPlayer.firstCity(&iLoop); pLoopCity != NULL; pLoopCity = kPlayer.nextCity(&iLoop))
			{
				pLoopCity->removeEntity();
				pLoopCity->destroyEntity();
			}
			
			for (pLoopUnit = kPlayer.firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = kPlayer.nextUnit(&iLoop))
			{
				if (GC.IsGraphicsInitialized())
				{
					gDLL->getEntityIFace()->RemoveUnitFromBattle(pLoopUnit);
					pLoopUnit->removeEntity();
				}
				pLoopUnit->destroyEntity();
			}
			
			kPlayer.swapUnitsAndCities();

			for (pLoopCity = kPlayer.firstCity(&iLoop); pLoopCity != NULL; pLoopCity = kPlayer.nextCity(&iLoop))
			{
				pLoopCity->removeEntity();
				pLoopCity->destroyEntity();
			}
			
			for (pLoopUnit = kPlayer.firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = kPlayer.nextUnit(&iLoop))
			{
				if (GC.IsGraphicsInitialized())
				{
					gDLL->getEntityIFace()->RemoveUnitFromBattle(pLoopUnit);
					pLoopUnit->removeEntity();
				}
				pLoopUnit->destroyEntity();
			}
		}
	}

	for (i = 0; i < GC.getMap().numPlots(); i++)
	{
		GC.getMap().plotByIndex(i)->destroyGraphics();
	}

	CvMap* tempMap = m_map;
	m_map = m_altMap;
	m_altMap = tempMap;

	GC.getMap().init();
	for (i = 0; i < GC.getMap().numPlots(); i++)
	{
		GC.getMap().plotByIndex(i)->setTerrainType((TerrainTypes)GC.getInfoTypeForString("TERRAIN_GRASS"), false, false);
	}
	gDLL->getEngineIFace()->RebuildAllPlots();
	GC.getMap().setupGraphical();

	for (pLoopCity = kPlayer.firstCity(&iLoop); pLoopCity != NULL; pLoopCity = kPlayer.nextCity(&iLoop))
	{
		gDLL->getEntityIFace()->createCityEntity(pLoopCity);
		pLoopCity->setupGraphical();
	}
	
	for (pLoopUnit = kPlayer.firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = kPlayer.nextUnit(&iLoop))
	{
		gDLL->getEntityIFace()->createUnitEntity(pLoopUnit);
		pLoopUnit->setupGraphical();
	}
}

As you can see, I'm initializing a unit entity for each unit. However, when CvDLLEntity::isSelected() calls getEntity() for a unit, it returns NULL. I'm guessing there's another function somewhere I have to call?
 
If I remember correctly, the plot type is set in the constructor (or some function called by it).
Iirc it is set to ocean so it might cause weird behavior later if plot type is ocean but terrain type grassland.

As you can see, I'm creating a unit entity for each unit after I swap the maps. However, when CvDLLEntity::isSelected() calls getEntity() for a unit, it returns NULL. I'm guessing there's another function somewhere I have to call?
Maybe a unit for which you destroyed the entity is still selected.
So best deselect all units and cities at the start of the function.
Looking at CvUnit::reloadEntity() there do not seem to be other functions to call.
 
Top Bottom