mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@1871 3b4a5df2-a311-0410-b5c6-a8a6f20db521
1001 lines
28 KiB
C++
1001 lines
28 KiB
C++
#ifdef PRECOMPILEDHEADERS
|
|
#include "Editor All.h"
|
|
#else
|
|
#include "builddefines.h"
|
|
#endif
|
|
|
|
#ifdef JA2EDITOR
|
|
|
|
#ifndef PRECOMPILEDHEADERS
|
|
#include "worlddef.h"
|
|
#include "worldman.h"
|
|
#include "smooth.h"
|
|
#include "input.h"
|
|
#include "debug.h"
|
|
#include "Isometric Utils.h"
|
|
|
|
#include "editscreen.h"
|
|
#include "Editor Undo.h"
|
|
#include "Render Fun.h" //for access to gubWorldRoomInfo;
|
|
#include "Cursor Modes.h"
|
|
#include "Exit Grids.h"
|
|
#endif
|
|
|
|
/*
|
|
Kris -- Notes on how the undo code works:
|
|
|
|
At the bottom of the hierarchy, we need to determine the state of the undo command. The idea
|
|
is that we want to separate undo commands by separating them by new mouse clicks. By holding a mouse
|
|
down and painting various objects in the world would all constitute a single undo command. As soon as
|
|
the mouse is release, then a new undo command is setup. So, to automate this, there is a call every
|
|
frame to DetermineUndoState().
|
|
|
|
At the next level, there is a binary tree that keeps track of what map indices have been backup up in
|
|
the current undo command. The whole reason to maintain this list, is to avoid multiple map elements of
|
|
the same map index from being saved. In the outer code, everytime something is changed, a call to
|
|
AddToUndoList() is called, so there are many cases (especially with building/terrain smoothing) that the
|
|
same mapindex is added to the undo list. This is also completely transparent, and doesn't need to be
|
|
maintained.
|
|
|
|
In the outer code, there are several calls to AddToUndoList( iMapIndex ). This function basically looks
|
|
in the binary tree for an existing entry, and if there isn't, then the entire mapelement is saved (with
|
|
the exception of the merc level ). Lights are also supported, but there is a totally different methodology
|
|
for accomplishing this. The equivalent function is AddLightToUndoList( iMapIndex ). In this case, only the
|
|
light is saved, along with internal maintanance of several flags.
|
|
|
|
The actual mapelement copy code, is very complex. The mapelement is copied in parallel with a new one which
|
|
has to allocate several nodes of several types because a mapelement contains over a dozen separate lists, and
|
|
all of them need to be saved. The structure information of certain mapelements may be multitiled and must
|
|
also save the affected gridno's as well. This is also done internally. Basically, your call to
|
|
AddToUndoList() for any particular gridno, may actually save several entries (like for a car which could be 6+
|
|
tiles)
|
|
|
|
MERCS
|
|
Mercs are not supported in the undo code. Because they are so dynamic, and stats could change, they could
|
|
move, etc., it doesn't need to be in the undo code. The editor has its own way of dealing with mercs which
|
|
doesn't use the undo methodology.
|
|
|
|
*/
|
|
|
|
|
|
BOOLEAN gfUndoEnabled = FALSE;
|
|
|
|
void EnableUndo()
|
|
{
|
|
gfUndoEnabled = TRUE;
|
|
}
|
|
|
|
void DisableUndo()
|
|
{
|
|
gfUndoEnabled = FALSE;
|
|
}
|
|
|
|
// undo node data element
|
|
typedef struct
|
|
{
|
|
INT32 iMapIndex;
|
|
MAP_ELEMENT *pMapTile;
|
|
BOOLEAN fLightSaved; //determines that a light has been saved
|
|
UINT8 ubLightRadius; //the radius of the light to build if undo is called
|
|
UINT8 ubLightID; //only applies if a light was saved.
|
|
UINT8 ubRoomNum;
|
|
} undo_struct;
|
|
|
|
// Undo stack node
|
|
typedef struct TAG_undo_stack
|
|
{
|
|
INT32 iCmdCount;
|
|
undo_struct *pData;
|
|
struct TAG_undo_stack *pNext;
|
|
INT32 iUndoType;
|
|
} undo_stack;
|
|
undo_stack *gpTileUndoStack = NULL;
|
|
|
|
// Map tile element manipulation functions
|
|
BOOLEAN CopyMapElementFromWorld( MAP_ELEMENT *pMapTile, INT32 iMapIndex );
|
|
BOOLEAN SwapMapElementWithWorld( INT32 iDestMapTileIndex, MAP_ELEMENT *pMapTile );
|
|
|
|
// Undo structure functions
|
|
BOOLEAN DeleteTopStackNode( void );
|
|
undo_stack *DeleteThisStackNode( undo_stack *pThisNode );
|
|
BOOLEAN DeleteStackNodeContents( undo_stack *pCurrent );
|
|
BOOLEAN AddToUndoListCmd( INT32 iMapIndex, INT32 iCmdCount );
|
|
void CropStackToMaxLength( INT32 iMaxCmds );
|
|
void SmoothUndoMapTileTerrain( INT32 iWorldTile, MAP_ELEMENT *pUndoTile );
|
|
|
|
|
|
BOOLEAN fNewUndoCmd = TRUE;
|
|
BOOLEAN gfIgnoreUndoCmdsForLights = FALSE;
|
|
|
|
//New pre-undo binary tree stuff
|
|
//With this, new undo commands will not duplicate saves in the same command. This will
|
|
//increase speed, and save memory.
|
|
typedef struct MapIndexBinaryTree
|
|
{
|
|
struct MapIndexBinaryTree *left, *right;
|
|
INT16 sMapIndex;
|
|
}MapIndexBinaryTree;
|
|
|
|
MapIndexBinaryTree *top = NULL;
|
|
void ClearUndoMapIndexTree();
|
|
BOOLEAN AddMapIndexToTree( INT16 sMapIndex );
|
|
void CheckMapIndexForMultiTileStructures( INT16 sMapIndex );
|
|
void CheckForMultiTilesInTreeAndAddToUndoList( MapIndexBinaryTree *node );
|
|
|
|
|
|
//Recursively deletes all nodes below the node passed including itself.
|
|
void DeleteTreeNode( MapIndexBinaryTree **node )
|
|
{
|
|
if( (*node)->left )
|
|
DeleteTreeNode( &((*node)->left) );
|
|
if( (*node)->right )
|
|
DeleteTreeNode( &((*node)->right) );
|
|
MemFree( *node );
|
|
*node = NULL;
|
|
}
|
|
|
|
//Recursively delete all nodes (from the top down).
|
|
void ClearUndoMapIndexTree()
|
|
{
|
|
if( top )
|
|
DeleteTreeNode( &top );
|
|
}
|
|
|
|
BOOLEAN AddMapIndexToTree( INT16 sMapIndex )
|
|
{
|
|
MapIndexBinaryTree *curr, *parent;
|
|
if( !top )
|
|
{
|
|
top = (MapIndexBinaryTree*)MemAlloc( sizeof( MapIndexBinaryTree ) );
|
|
Assert( top );
|
|
top->sMapIndex = sMapIndex;
|
|
top->left = NULL;
|
|
top->right = NULL;
|
|
return TRUE;
|
|
}
|
|
curr = top;
|
|
parent = NULL;
|
|
//Traverse down the tree and attempt to find a matching mapindex.
|
|
//If one is encountered, then we fail, and don't add the mapindex to the
|
|
//tree.
|
|
while( curr )
|
|
{
|
|
parent = curr;
|
|
if( curr->sMapIndex == sMapIndex ) //found a match, so stop
|
|
return FALSE;
|
|
//if the mapIndex is < node's mapIndex, then go left, else right
|
|
curr = ( sMapIndex < curr->sMapIndex ) ? curr->left : curr->right;
|
|
}
|
|
//if we made it this far, then curr is null and parent is pointing
|
|
//directly above.
|
|
//Create the new node and fill in the information.
|
|
curr = (MapIndexBinaryTree*)MemAlloc( sizeof( MapIndexBinaryTree ) );
|
|
Assert( curr );
|
|
curr->sMapIndex = sMapIndex;
|
|
curr->left = NULL;
|
|
curr->right = NULL;
|
|
//Now link the new node to the parent.
|
|
if( curr->sMapIndex < parent->sMapIndex )
|
|
parent->left = curr;
|
|
else
|
|
parent->right = curr;
|
|
return TRUE;
|
|
}
|
|
//*************************************************************************
|
|
//
|
|
// Start of Undo code
|
|
//
|
|
//*************************************************************************
|
|
|
|
BOOLEAN DeleteTopStackNode( void )
|
|
{
|
|
undo_stack *pCurrent;
|
|
|
|
pCurrent = gpTileUndoStack;
|
|
|
|
DeleteStackNodeContents( pCurrent );
|
|
|
|
// Remove node from stack, and free it's memory
|
|
gpTileUndoStack = gpTileUndoStack->pNext;
|
|
MemFree( pCurrent );
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
undo_stack *DeleteThisStackNode( undo_stack *pThisNode )
|
|
{
|
|
undo_stack *pCurrent;
|
|
undo_stack *pNextNode;
|
|
|
|
pCurrent = pThisNode;
|
|
pNextNode = pThisNode->pNext;
|
|
|
|
// Remove node from stack, and free it's memory
|
|
DeleteStackNodeContents( pCurrent );
|
|
MemFree( pCurrent );
|
|
|
|
return( pNextNode );
|
|
}
|
|
|
|
|
|
BOOLEAN DeleteStackNodeContents( undo_stack *pCurrent )
|
|
{
|
|
undo_struct *pData;
|
|
MAP_ELEMENT *pMapTile;
|
|
LEVELNODE *pLandNode;
|
|
LEVELNODE *pObjectNode;
|
|
LEVELNODE *pStructNode;
|
|
LEVELNODE *pShadowNode;
|
|
LEVELNODE *pMercNode;
|
|
LEVELNODE *pTopmostNode;
|
|
LEVELNODE *pRoofNode;
|
|
LEVELNODE *pOnRoofNode;
|
|
STRUCTURE *pStructureNode;
|
|
|
|
pData = pCurrent->pData;
|
|
pMapTile = pData->pMapTile;
|
|
|
|
if( !pMapTile )
|
|
return TRUE; //light was saved -- mapelement wasn't saved.
|
|
|
|
// Free the memory associated with the map tile liked lists
|
|
pLandNode = pMapTile->pLandHead;
|
|
while ( pLandNode != NULL )
|
|
{
|
|
pMapTile->pLandHead = pLandNode->pNext;
|
|
MemFree( pLandNode );
|
|
pLandNode = pMapTile->pLandHead;
|
|
}
|
|
|
|
pObjectNode = pMapTile->pObjectHead;
|
|
while ( pObjectNode != NULL )
|
|
{
|
|
pMapTile->pObjectHead = pObjectNode->pNext;
|
|
MemFree( pObjectNode );
|
|
pObjectNode = pMapTile->pObjectHead;
|
|
}
|
|
|
|
pStructNode = pMapTile->pStructHead;
|
|
while ( pStructNode != NULL )
|
|
{
|
|
pMapTile->pStructHead = pStructNode->pNext;
|
|
MemFree( pStructNode );
|
|
pStructNode = pMapTile->pStructHead;
|
|
}
|
|
|
|
pShadowNode = pMapTile->pShadowHead;
|
|
while ( pShadowNode != NULL )
|
|
{
|
|
pMapTile->pShadowHead = pShadowNode->pNext;
|
|
MemFree( pShadowNode );
|
|
pShadowNode = pMapTile->pShadowHead;
|
|
}
|
|
|
|
pMercNode = pMapTile->pMercHead;
|
|
while ( pMercNode != NULL )
|
|
{
|
|
pMapTile->pMercHead = pMercNode->pNext;
|
|
MemFree( pMercNode );
|
|
pMercNode = pMapTile->pMercHead;
|
|
}
|
|
|
|
pRoofNode = pMapTile->pRoofHead;
|
|
while ( pRoofNode != NULL )
|
|
{
|
|
pMapTile->pRoofHead = pRoofNode->pNext;
|
|
MemFree( pRoofNode );
|
|
pRoofNode = pMapTile->pRoofHead;
|
|
}
|
|
|
|
pOnRoofNode = pMapTile->pOnRoofHead;
|
|
while ( pOnRoofNode != NULL )
|
|
{
|
|
pMapTile->pOnRoofHead = pOnRoofNode->pNext;
|
|
MemFree( pOnRoofNode );
|
|
pOnRoofNode = pMapTile->pOnRoofHead;
|
|
}
|
|
|
|
pTopmostNode = pMapTile->pTopmostHead;
|
|
while ( pTopmostNode != NULL )
|
|
{
|
|
pMapTile->pTopmostHead = pTopmostNode->pNext;
|
|
MemFree( pTopmostNode );
|
|
pTopmostNode = pMapTile->pTopmostHead;
|
|
}
|
|
|
|
pStructureNode = pMapTile->pStructureHead;
|
|
while( pStructureNode )
|
|
{
|
|
pMapTile->pStructureHead = pStructureNode->pNext;
|
|
if( pStructureNode->usStructureID > INVALID_STRUCTURE_ID )
|
|
{ //Okay to delete the structure data -- otherwise, this would be
|
|
//merc structure data that we DON'T want to delete, because the merc node
|
|
//that hasn't been modified will still use this structure data!
|
|
MemFree( pStructureNode );
|
|
}
|
|
pStructureNode = pMapTile->pStructureHead;
|
|
}
|
|
|
|
// Free the map tile structure itself
|
|
MemFree( pMapTile );
|
|
|
|
// Free the undo struct
|
|
MemFree( pData );
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
void CropStackToMaxLength( INT32 iMaxCmds )
|
|
{
|
|
INT32 iCmdCount;
|
|
undo_stack *pCurrent;
|
|
|
|
iCmdCount = 0;
|
|
pCurrent = gpTileUndoStack;
|
|
|
|
// If stack is empty, leave
|
|
if ( pCurrent == NULL )
|
|
return;
|
|
|
|
while ( (iCmdCount <= (iMaxCmds - 1)) && (pCurrent != NULL) )
|
|
{
|
|
if ( pCurrent->iCmdCount == 1 )
|
|
iCmdCount++;
|
|
pCurrent = pCurrent->pNext;
|
|
}
|
|
|
|
// If the max number of commands was reached, and there is something
|
|
// to crop, from the rest of the stack, remove it.
|
|
if ( (iCmdCount >= iMaxCmds) && pCurrent != NULL )
|
|
{
|
|
while ( pCurrent->pNext != NULL )
|
|
pCurrent->pNext = DeleteThisStackNode( pCurrent->pNext );
|
|
}
|
|
}
|
|
|
|
|
|
//We are adding a light to the undo list. We won't save the mapelement, nor will
|
|
//we validate the gridno in the binary tree. This works differently than a mapelement,
|
|
//because lights work on a different system. By setting the fLightSaved flag to TRUE,
|
|
//this will handle the way the undo command is handled. If there is no lightradius in
|
|
//our saved light, then we intend on erasing the light upon undo execution, otherwise, we
|
|
//save the light radius and light ID, so that we place it during undo execution.
|
|
void AddLightToUndoList( INT32 iMapIndex, INT32 iLightRadius, UINT8 ubLightID )
|
|
{
|
|
undo_stack *pNode;
|
|
undo_struct *pUndoInfo;
|
|
|
|
if( !gfUndoEnabled )
|
|
return;
|
|
//When executing an undo command (by adding a light or removing one), that command
|
|
//actually tries to add it to the undo list. So we wrap the execution of the undo
|
|
//command by temporarily setting this flag, so it'll ignore, and not place a new undo
|
|
//command. When finished, the flag is cleared, and lights are again allowed to be saved
|
|
//in the undo list.
|
|
if( gfIgnoreUndoCmdsForLights )
|
|
return;
|
|
|
|
pNode = (undo_stack*)MemAlloc( sizeof( undo_stack ) );
|
|
if( !pNode )
|
|
return;
|
|
|
|
pUndoInfo = (undo_struct *)MemAlloc( sizeof( undo_struct ) );
|
|
if( !pUndoInfo )
|
|
{
|
|
MemFree( pNode );
|
|
return;
|
|
}
|
|
|
|
pUndoInfo->fLightSaved = TRUE;
|
|
//if ubLightRadius is 0, then we don't need to save the light information because we
|
|
//will erase it when it comes time to execute the undo command.
|
|
pUndoInfo->ubLightRadius = (UINT8)iLightRadius;
|
|
pUndoInfo->ubLightID = ubLightID;
|
|
pUndoInfo->iMapIndex = iMapIndex;
|
|
pUndoInfo->pMapTile = NULL;
|
|
|
|
//Add to undo stack
|
|
pNode->iCmdCount = 1;
|
|
pNode->pData = pUndoInfo;
|
|
pNode->pNext = gpTileUndoStack;
|
|
gpTileUndoStack = pNode;
|
|
|
|
CropStackToMaxLength( MAX_UNDO_COMMAND_LENGTH );
|
|
}
|
|
|
|
BOOLEAN AddToUndoList( INT32 iMapIndex )
|
|
{
|
|
static INT32 iCount = 1;
|
|
|
|
if( !gfUndoEnabled )
|
|
return FALSE;
|
|
if ( fNewUndoCmd )
|
|
{
|
|
iCount = 0;
|
|
fNewUndoCmd = FALSE;
|
|
}
|
|
|
|
//Check to see if the tile in question is even on the visible map, then
|
|
//if that is true, then check to make sure we don't already have the mapindex
|
|
//saved in the new binary tree (which only holds unique mapindex values).
|
|
if( GridNoOnVisibleWorldTile( (INT16)iMapIndex ) && AddMapIndexToTree( (INT16)iMapIndex ) )
|
|
|
|
{
|
|
if( AddToUndoListCmd( iMapIndex, ++iCount ) )
|
|
return TRUE;
|
|
iCount--;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOLEAN AddToUndoListCmd( INT32 iMapIndex, INT32 iCmdCount )
|
|
{
|
|
undo_stack *pNode;
|
|
undo_struct *pUndoInfo;
|
|
MAP_ELEMENT *pData;
|
|
STRUCTURE *pStructure;
|
|
INT32 iCoveredMapIndex;
|
|
UINT8 ubLoop;
|
|
|
|
if ( (pNode = (undo_stack *)MemAlloc(sizeof( undo_stack )) ) == NULL )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( (pUndoInfo = (undo_struct *)MemAlloc(sizeof( undo_struct )) ) == NULL )
|
|
{
|
|
MemFree( pNode );
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( (pData = (MAP_ELEMENT *)MemAlloc(sizeof( MAP_ELEMENT )) ) == NULL )
|
|
{
|
|
MemFree( pNode );
|
|
MemFree( pUndoInfo );
|
|
return( FALSE );
|
|
}
|
|
|
|
// Init map element struct
|
|
pData->pLandHead = pData->pLandStart = NULL;
|
|
pData->pObjectHead = NULL;
|
|
pData->pStructHead = NULL;
|
|
pData->pShadowHead = NULL;
|
|
pData->pMercHead = NULL;
|
|
pData->pRoofHead = NULL;
|
|
pData->pOnRoofHead = NULL;
|
|
pData->pTopmostHead = NULL;
|
|
pData->pStructureHead = pData->pStructureTail = NULL;
|
|
pData->sHeight = 0;
|
|
|
|
|
|
// Copy the world map's tile
|
|
if ( CopyMapElementFromWorld( pData, iMapIndex ) == FALSE )
|
|
{
|
|
MemFree( pNode );
|
|
MemFree( pUndoInfo );
|
|
MemFree( pData );
|
|
return( FALSE );
|
|
}
|
|
|
|
// copy the room number information (it's not in the mapelement structure)
|
|
pUndoInfo->ubRoomNum = gubWorldRoomInfo[ iMapIndex ];
|
|
|
|
pUndoInfo->fLightSaved = FALSE;
|
|
pUndoInfo->ubLightRadius = 0;
|
|
pUndoInfo->ubLightID = 0;
|
|
pUndoInfo->pMapTile = pData;
|
|
pUndoInfo->iMapIndex = iMapIndex;
|
|
|
|
pNode->pData = pUndoInfo;
|
|
pNode->iCmdCount = iCmdCount;
|
|
pNode->pNext = gpTileUndoStack;
|
|
gpTileUndoStack = pNode;
|
|
|
|
// loop through pData->pStructureHead list
|
|
// for each structure
|
|
// find the base tile
|
|
// reference the db structure
|
|
// if number of tiles > 1
|
|
// add all covered tiles to undo list
|
|
pStructure = pData->pStructureHead;
|
|
while (pStructure)
|
|
{
|
|
for (ubLoop = 1; ubLoop < pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles; ubLoop++)
|
|
{
|
|
// this loop won't execute for single-tile structures; for multi-tile structures, we have to
|
|
// add to the undo list all the other tiles covered by the structure
|
|
iCoveredMapIndex = pStructure->sBaseGridNo + pStructure->pDBStructureRef->ppTile[ubLoop]->sPosRelToBase;
|
|
AddToUndoList( iCoveredMapIndex );
|
|
}
|
|
pStructure = pStructure->pNext;
|
|
}
|
|
|
|
CropStackToMaxLength( MAX_UNDO_COMMAND_LENGTH );
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
void CheckMapIndexForMultiTileStructures( INT16 sMapIndex )
|
|
{
|
|
STRUCTURE * pStructure;
|
|
UINT8 ubLoop;
|
|
INT32 iCoveredMapIndex;
|
|
|
|
pStructure = gpWorldLevelData[sMapIndex].pStructureHead;
|
|
while (pStructure)
|
|
{
|
|
if (pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles > 1)
|
|
{
|
|
for (ubLoop = 0; ubLoop < pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles; ubLoop++)
|
|
{
|
|
// for multi-tile structures we have to add, to the undo list, all the other tiles covered by the structure
|
|
if (pStructure->fFlags & STRUCTURE_BASE_TILE)
|
|
{
|
|
iCoveredMapIndex = sMapIndex + pStructure->pDBStructureRef->ppTile[ubLoop]->sPosRelToBase;
|
|
}
|
|
else
|
|
{
|
|
iCoveredMapIndex = pStructure->sBaseGridNo + pStructure->pDBStructureRef->ppTile[ubLoop]->sPosRelToBase;
|
|
}
|
|
AddToUndoList( iCoveredMapIndex );
|
|
}
|
|
}
|
|
pStructure = pStructure->pNext;
|
|
}
|
|
}
|
|
|
|
void CheckForMultiTilesInTreeAndAddToUndoList( MapIndexBinaryTree *node )
|
|
{
|
|
CheckMapIndexForMultiTileStructures( node->sMapIndex );
|
|
if( node->left )
|
|
CheckForMultiTilesInTreeAndAddToUndoList( node->left );
|
|
if( node->right )
|
|
CheckForMultiTilesInTreeAndAddToUndoList( node->right );
|
|
}
|
|
|
|
BOOLEAN RemoveAllFromUndoList( void )
|
|
{
|
|
ClearUndoMapIndexTree();
|
|
|
|
while ( gpTileUndoStack != NULL )
|
|
DeleteTopStackNode();
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
BOOLEAN ExecuteUndoList( void )
|
|
{
|
|
INT32 iCmdCount, iCurCount;
|
|
INT32 iUndoMapIndex;
|
|
BOOLEAN fExitGrid;
|
|
|
|
if( !gfUndoEnabled )
|
|
return FALSE;
|
|
|
|
// Is there something on the undo stack?
|
|
if ( gpTileUndoStack == NULL )
|
|
return( TRUE );
|
|
|
|
// Get number of stack entries for this command (top node will tell this)
|
|
iCmdCount = gpTileUndoStack->iCmdCount;
|
|
|
|
// Execute each stack node in command, and remove each from stack.
|
|
iCurCount = 0;
|
|
while ( (iCurCount < iCmdCount) && (gpTileUndoStack != NULL) )
|
|
{
|
|
iUndoMapIndex = gpTileUndoStack->pData->iMapIndex;
|
|
|
|
fExitGrid = ExitGridAtGridNo( (INT16)iUndoMapIndex );
|
|
|
|
// Find which map tile we are to "undo"
|
|
if( gpTileUndoStack->pData->fLightSaved )
|
|
{ //We saved a light, so delete that light
|
|
INT16 sX, sY;
|
|
//Turn on this flag so that the following code, when executed, doesn't attempt to
|
|
//add lights to the undo list. That would cause problems...
|
|
gfIgnoreUndoCmdsForLights = TRUE;
|
|
ConvertGridNoToXY( (INT16)iUndoMapIndex, &sX, &sY );
|
|
if( !gpTileUndoStack->pData->ubLightRadius )
|
|
RemoveLight( sX, sY );
|
|
else
|
|
PlaceLight( gpTileUndoStack->pData->ubLightRadius, sX, sY, gpTileUndoStack->pData->ubLightID );
|
|
//Turn off the flag so lights can again be added to the undo list.
|
|
gfIgnoreUndoCmdsForLights = FALSE;
|
|
}
|
|
else
|
|
{ // We execute the undo command node by simply swapping the contents
|
|
// of the undo's MAP_ELEMENT with the world's element.
|
|
SwapMapElementWithWorld( iUndoMapIndex, gpTileUndoStack->pData->pMapTile );
|
|
|
|
// copy the room number information back
|
|
gubWorldRoomInfo[ iUndoMapIndex ] = gpTileUndoStack->pData->ubRoomNum;
|
|
|
|
// Now we smooth out the changes...
|
|
//SmoothUndoMapTileTerrain( iUndoMapIndex, gpTileUndoStack->pData->pMapTile );
|
|
SmoothAllTerrainTypeRadius( iUndoMapIndex, 1, TRUE );
|
|
}
|
|
|
|
// ...trash the top element of the stack...
|
|
DeleteTopStackNode( );
|
|
|
|
// ...and bump the command counter up by 1
|
|
iCurCount++;
|
|
|
|
//Kris:
|
|
//The new cursor system is somehow interfering with the undo stuff. When
|
|
//an undo is called, the item is erased, but a cursor is added! I'm quickly
|
|
//hacking around this by erasing all cursors here.
|
|
RemoveAllTopmostsOfTypeRange( iUndoMapIndex, FIRSTPOINTERS, FIRSTPOINTERS );
|
|
|
|
if( fExitGrid && !ExitGridAtGridNo( (INT16)iUndoMapIndex ) )
|
|
{ //An exitgrid has been removed, so get rid of the associated indicator.
|
|
RemoveTopmost( (UINT16)iUndoMapIndex, FIRSTPOINTERS8 );
|
|
}
|
|
else if( !fExitGrid && ExitGridAtGridNo( (INT16)iUndoMapIndex ) )
|
|
{ //An exitgrid has been added, so add the associated indicator
|
|
AddTopmostToTail( (UINT16)iUndoMapIndex, FIRSTPOINTERS8 );
|
|
}
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
void SmoothUndoMapTileTerrain( INT32 iWorldTile, MAP_ELEMENT *pUndoTile )
|
|
{
|
|
LEVELNODE *pWorldLand;
|
|
LEVELNODE *pUndoLand;
|
|
LEVELNODE *pLand;
|
|
LEVELNODE *pWLand;
|
|
UINT32 uiCheckType, uiWCheckType;
|
|
BOOLEAN fFound;
|
|
|
|
pUndoLand = pUndoTile->pLandHead;
|
|
pWorldLand = gpWorldLevelData[ iWorldTile ].pLandHead;
|
|
|
|
if ( pUndoLand == NULL )
|
|
{
|
|
// nothing in the old tile, so smooth the entire land in world's tile
|
|
pLand = gpWorldLevelData[ iWorldTile ].pLandHead;
|
|
while( pLand != NULL )
|
|
{
|
|
GetTileType( pLand->usIndex, &uiCheckType );
|
|
SmoothTerrainRadius( iWorldTile, uiCheckType, 1, TRUE );
|
|
pLand = pLand->pNext;
|
|
}
|
|
}
|
|
else if ( gpWorldLevelData[ iWorldTile ].pLandHead == NULL )
|
|
{
|
|
// Nothing in world's tile, so smooth out the land in the old tile.
|
|
pLand = pUndoLand;
|
|
while( pLand != NULL )
|
|
{
|
|
GetTileType( pLand->usIndex, &uiCheckType );
|
|
SmoothTerrainRadius( iWorldTile, uiCheckType, 1, TRUE );
|
|
pLand = pLand->pNext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pLand = pUndoLand;
|
|
while( pLand != NULL )
|
|
{
|
|
GetTileType( pLand->usIndex, &uiCheckType);
|
|
|
|
fFound = FALSE;
|
|
pWLand = pWorldLand;
|
|
while( pWLand != NULL && !fFound )
|
|
{
|
|
GetTileType( pWLand->usIndex, &uiWCheckType);
|
|
|
|
if ( uiCheckType == uiWCheckType )
|
|
fFound = TRUE;
|
|
|
|
pWLand = pWLand->pNext;
|
|
}
|
|
|
|
if ( !fFound )
|
|
SmoothTerrainRadius( iWorldTile, uiCheckType, 1, TRUE );
|
|
|
|
pLand = pLand->pNext;
|
|
}
|
|
|
|
pWLand = pWorldLand;
|
|
while( pWLand != NULL )
|
|
{
|
|
GetTileType( pWLand->usIndex, &uiWCheckType);
|
|
|
|
fFound = FALSE;
|
|
pLand = pUndoLand;
|
|
while( pLand != NULL && !fFound )
|
|
{
|
|
GetTileType( pLand->usIndex, &uiCheckType);
|
|
|
|
if ( uiCheckType == uiWCheckType )
|
|
fFound = TRUE;
|
|
|
|
pLand = pLand->pNext;
|
|
}
|
|
|
|
if ( !fFound )
|
|
SmoothTerrainRadius( iWorldTile, uiWCheckType, 1, TRUE );
|
|
|
|
pWLand = pWLand->pNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Because of the potentially huge amounts of memory that can be allocated due to the inefficient
|
|
//undo methods coded by Bret, it is feasible that it could fail. Instead of using assertions to
|
|
//terminate the program, destroy the memory allocated thusfar.
|
|
void DeleteMapElementContentsAfterCreationFail( MAP_ELEMENT *pNewMapElement )
|
|
{
|
|
LEVELNODE *pLevelNode;
|
|
STRUCTURE *pStructure;
|
|
INT32 x;
|
|
for( x = 0; x < 9; x++ )
|
|
{
|
|
if( x == 1 )
|
|
continue;
|
|
pLevelNode = pNewMapElement->pLevelNodes[ x ];
|
|
while( pLevelNode )
|
|
{
|
|
LEVELNODE *temp;
|
|
temp = pLevelNode;
|
|
pLevelNode = pLevelNode->pNext;
|
|
MemFree( temp );
|
|
}
|
|
}
|
|
pStructure = pNewMapElement->pStructureHead;
|
|
while( pStructure )
|
|
{
|
|
STRUCTURE *temp;
|
|
temp = pStructure;
|
|
pStructure = pStructure->pNext;
|
|
MemFree( temp );
|
|
}
|
|
}
|
|
|
|
/*
|
|
union
|
|
{
|
|
struct TAG_level_node *pPrevNode; // FOR LAND, GOING BACKWARDS POINTER
|
|
ITEM_POOL *pItemPool; // ITEM POOLS
|
|
STRUCTURE *pStructureData; // STRUCTURE DATA
|
|
INT32 iPhysicsObjectID; // ID FOR PHYSICS ITEM
|
|
INT32 uiAPCost; // FOR AP DISPLAY
|
|
}; // ( 4 byte union )
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
UINT16 usIndex; // TILE DATABASE INDEX
|
|
INT16 sCurrentFrame; // Stuff for animated tiles for a given tile location ( doors, etc )
|
|
};
|
|
struct
|
|
{
|
|
SOLDIERTYPE *pSoldier; // POINTER TO SOLDIER
|
|
};
|
|
}; // ( 4 byte union )
|
|
*/
|
|
BOOLEAN CopyMapElementFromWorld( MAP_ELEMENT *pNewMapElement, INT32 iMapIndex )
|
|
{
|
|
MAP_ELEMENT *pOldMapElement;
|
|
LEVELNODE *pOldLevelNode;
|
|
LEVELNODE *pLevelNode;
|
|
LEVELNODE *pNewLevelNode;
|
|
LEVELNODE *tail;
|
|
INT32 x;
|
|
|
|
STRUCTURE *pOldStructure;
|
|
|
|
//Get a pointer to the current map index
|
|
pOldMapElement = &gpWorldLevelData[ iMapIndex ];
|
|
|
|
//Save the structure information from the mapelement
|
|
pOldStructure = pOldMapElement->pStructureHead;
|
|
if( pOldStructure )
|
|
{
|
|
STRUCTURE *pNewStructure;
|
|
STRUCTURE *pStructure;
|
|
STRUCTURE *tail;
|
|
tail = NULL;
|
|
pNewStructure = NULL;
|
|
while( pOldStructure )
|
|
{
|
|
pStructure = (STRUCTURE*)MemAlloc( sizeof( STRUCTURE ) );
|
|
if( !pStructure )
|
|
{
|
|
DeleteMapElementContentsAfterCreationFail( pNewMapElement );
|
|
return FALSE;
|
|
}
|
|
if( !tail )
|
|
{ //first node in structure list
|
|
tail = pStructure;
|
|
*tail = *pOldStructure;
|
|
tail->pPrev = NULL;
|
|
tail->pNext = NULL;
|
|
}
|
|
else
|
|
{ //add to the end of the levelnode list
|
|
tail->pNext = pStructure;
|
|
*pStructure = *pOldStructure;
|
|
pStructure->pPrev = tail;
|
|
pStructure->pNext = NULL;
|
|
tail = tail->pNext;
|
|
}
|
|
//place the new node inside of the new map element
|
|
if( !pNewStructure )
|
|
{
|
|
pNewMapElement->pStructureHead = pStructure;
|
|
pNewStructure = pStructure;
|
|
}
|
|
else
|
|
{
|
|
pNewStructure->pNext = pStructure;
|
|
pNewStructure = pNewStructure->pNext;
|
|
}
|
|
pOldStructure = pOldStructure->pNext;
|
|
}
|
|
if( tail )
|
|
{
|
|
pNewMapElement->pStructureTail = tail;
|
|
}
|
|
}
|
|
|
|
//For each of the 9 levelnodes, save each one
|
|
//except for levelnode[1] which is a pointer to the first land to render.
|
|
for( x = 0; x < 9; x++ )
|
|
{
|
|
if( x == 1 || x == 5 ) //skip the pLandStart and pMercLevel LEVELNODES
|
|
continue;
|
|
tail = NULL;
|
|
pOldLevelNode = pOldMapElement->pLevelNodes[ x ];
|
|
pNewLevelNode = NULL;
|
|
while( pOldLevelNode )
|
|
{
|
|
//copy the level node
|
|
pLevelNode = ( LEVELNODE* )MemAlloc( sizeof( LEVELNODE ) );
|
|
if( !pLevelNode )
|
|
{
|
|
DeleteMapElementContentsAfterCreationFail( pNewMapElement );
|
|
return FALSE;
|
|
}
|
|
if( !tail )
|
|
{ //first node in levelnode list
|
|
tail = pLevelNode;
|
|
*tail = *pOldLevelNode;
|
|
if( !x ) //land layer only
|
|
tail->pPrevNode = NULL;
|
|
tail->pNext = NULL;
|
|
}
|
|
else
|
|
{ //add to the end of the levelnode list
|
|
tail->pNext = pLevelNode;
|
|
*pLevelNode = *pOldLevelNode;
|
|
if( !x ) //land layer only
|
|
pLevelNode->pPrevNode = tail;
|
|
pLevelNode->pNext = NULL;
|
|
tail = tail->pNext;
|
|
}
|
|
//place the new node inside of the new map element
|
|
if( !pNewLevelNode )
|
|
{
|
|
pNewMapElement->pLevelNodes[ x ] = pLevelNode;
|
|
pNewLevelNode = pLevelNode;
|
|
}
|
|
else
|
|
{
|
|
pNewLevelNode->pNext = pLevelNode;
|
|
pNewLevelNode = pNewLevelNode->pNext;
|
|
}
|
|
//Handle levelnode layer specific stuff
|
|
switch( x )
|
|
{
|
|
case 0: //LAND LAYER
|
|
if( pOldLevelNode == pOldMapElement->pLandStart )
|
|
{ //set the new landstart to point to the new levelnode.
|
|
pNewMapElement->pLandStart = pNewLevelNode;
|
|
}
|
|
break;
|
|
case 2: //OBJECT LAYER
|
|
if( pOldLevelNode->pItemPool )
|
|
{ //save the item pool?
|
|
//pNewLevelNode->pItemPool = (ITEM_POOL*)MemAlloc( sizeof( ITEM_POOL ) );
|
|
}
|
|
break;
|
|
case 3: //STRUCT LAYER
|
|
case 6: //ROOF LAYER
|
|
case 7: //ON ROOF LAYER
|
|
if( pOldLevelNode->pStructureData )
|
|
{ //make sure the structuredata pointer points to the parallel structure
|
|
STRUCTURE *pOld, *pNew;
|
|
//both lists are exactly the same size and contain the same information,
|
|
//but the addresses are different. We will traverse the old list until
|
|
//we find the match, then
|
|
pOld = pOldMapElement->pStructureHead;
|
|
pNew = pNewMapElement->pStructureHead;
|
|
while( pOld )
|
|
{
|
|
Assert( pNew );
|
|
if( pOld == pOldLevelNode->pStructureData )
|
|
{
|
|
pNewLevelNode->pStructureData = pNew;
|
|
break;
|
|
}
|
|
pOld = pOld->pNext;
|
|
pNew = pNew->pNext;
|
|
}
|
|
//Kris:
|
|
//If this assert should fail, that means there is something wrong with
|
|
//the preservation of the structure data within the mapelement.
|
|
if( pOld != pOldLevelNode->pStructureData )
|
|
{
|
|
//OUCH!!! THIS IS HAPPENING. DISABLED IT FOR LINDA'S SAKE
|
|
Assert( 1 );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
//Done, go to next node in this level
|
|
pOldLevelNode = pOldLevelNode->pNext;
|
|
}
|
|
//Done, go to next level
|
|
}
|
|
|
|
//Save the rest of the information in the mapelement.
|
|
pNewMapElement->uiFlags = pOldMapElement->uiFlags;
|
|
pNewMapElement->sSumRealLights[0] = pOldMapElement->sSumRealLights[0];
|
|
pNewMapElement->sSumRealLights[1] = pOldMapElement->sSumRealLights[1];
|
|
pNewMapElement->sHeight = pOldMapElement->sHeight;
|
|
pNewMapElement->ubTerrainID = pOldMapElement->ubTerrainID;
|
|
pNewMapElement->ubReservedSoldierID = pOldMapElement->ubReservedSoldierID;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN SwapMapElementWithWorld( INT32 iMapIndex, MAP_ELEMENT *pUndoMapElement )
|
|
{
|
|
MAP_ELEMENT *pCurrentMapElement;
|
|
MAP_ELEMENT TempMapElement;
|
|
|
|
pCurrentMapElement = &gpWorldLevelData[ iMapIndex ];
|
|
|
|
//Transfer the merc level node from the current world to the undo mapelement
|
|
//that will replace it. We do this, because mercs aren't associated with
|
|
//undo commands.
|
|
pUndoMapElement->pMercHead = gpWorldLevelData[ iMapIndex ].pMercHead;
|
|
gpWorldLevelData[ iMapIndex ].pMercHead = NULL;
|
|
|
|
//Swap the mapelements
|
|
TempMapElement = *pCurrentMapElement;
|
|
*pCurrentMapElement = *pUndoMapElement;
|
|
*pUndoMapElement = TempMapElement;
|
|
|
|
return ( TRUE );
|
|
}
|
|
|
|
void DetermineUndoState()
|
|
{
|
|
// Reset the undo command mode if we released the left button.
|
|
if( !fNewUndoCmd )
|
|
{
|
|
if( !gfLeftButtonState && !gfCurrentSelectionWithRightButton ||
|
|
!gfRightButtonState && gfCurrentSelectionWithRightButton )
|
|
{
|
|
//Clear the mapindex binary tree list, and set up flag for new undo command.
|
|
fNewUndoCmd = TRUE;
|
|
ClearUndoMapIndexTree();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|