new feature: dynamic flashlights allows usable flashlights

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@6061 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Flugente
2013-05-03 23:31:28 +00:00
parent 3bc5ff75cc
commit fadd849359
12 changed files with 333 additions and 18 deletions
+2 -1
View File
@@ -21,6 +21,7 @@ extern CHAR16 zTrackingNumber[16];
// Keeps track of the saved game version. Increment the saved game version whenever
// you will invalidate the saved game file
#define DYNAMIC_FLASHLIGHTS 142 // Flugente: had to add variables to LIGHTEFFECT-struct
#define ENLARGED_OPINIONS 141 // Flugente: increased numberof merc opinions to 255, thus need to change savegame reading
#define MODULARIZED_AI 140 // Flugente: new savegame version due to modularized AI
#define MILITIA_EQUIPMENT 139 // Flugente: militia can equip from sector inventory
@@ -60,7 +61,7 @@ extern CHAR16 zTrackingNumber[16];
#define AP100_SAVEGAME_DATATYPE_CHANGE 105 // Before this, we didn't have the 100AP structure changes
#define NIV_SAVEGAME_DATATYPE_CHANGE 102 // Before this, we used the old structure system
#define SAVE_GAME_VERSION ENLARGED_OPINIONS
#define SAVE_GAME_VERSION DYNAMIC_FLASHLIGHTS
//#define RUSSIANGOLD
#ifdef __cplusplus
+3
View File
@@ -96,6 +96,9 @@ void HandleDoorChangeFromGridNo( SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEAN f
{
pDoorStatus->ubFlags |= DOOR_BUSY;
}
// Flugente: we have to redo flashlights
pSoldier->HandleFlashLights();
}
UINT16 GetAnimStateForInteraction( SOLDIERTYPE *pSoldier, BOOLEAN fDoor, UINT16 usAnimState )
+21
View File
@@ -111,6 +111,25 @@ void HandleTacticalPanelSwitch( )
}
}
// Flugente: handle flashlights
void HandleFlashLights()
{
if ( !fInterfacePanelDirty )
return;
SOLDIERTYPE* pSoldier = NULL;
for (UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop )
{
pSoldier = MercSlots[ uiLoop ];
if ( pSoldier != NULL && pSoldier->bSoldierFlagMask & SOLDIER_REDOFLASHLIGHT )
{
pSoldier->HandleFlashLights();
}
}
}
void RenderTacticalInterface( )
{
@@ -143,6 +162,8 @@ void RenderTacticalInterface( )
if( !(guiTacticalInterfaceFlags & INTERFACE_SHOPKEEP_INTERFACE ) )
HandleAutoFaces( );
// Flugente: handle flashlights
HandleFlashLights();
}
+3
View File
@@ -17,6 +17,9 @@ void ResetInterface( );
void RenderTopmostTacticalInterface( );
void RenderTacticalInterface( );
// Flugente: handle flashlights
void HandleFlashLights();
void StartViewportOverlays( );
void EndViewportOverlays( );
+4
View File
@@ -4089,6 +4089,10 @@ void SMInvClickCallback( MOUSE_REGION * pRegion, INT32 iReason )
*/
}
}
// Flugente: we have to recheck our flashlights, as we changed items
//gpSMCurrentMerc->bSoldierFlagMask |= SOLDIER_REDOFLASHLIGHT;
gpSMCurrentMerc->HandleFlashLights();
}
else if (iReason & MSYS_CALLBACK_REASON_RBUTTON_DWN)
{
+3
View File
@@ -1150,6 +1150,9 @@ typedef struct
UINT16 randomitem;
INT8 randomitemcoolnessmodificator; // alters the allowed maximum coolness a random item can have
// Flugente: range of a flashlight (an item with usFlashLightRange > 0 is deemed a flashlight)
UINT8 usFlashLightRange;
} INVTYPE;
+9
View File
@@ -8665,6 +8665,9 @@ void SwapHandItems( SOLDIERTYPE * pSoldier )
SwapObjs( pSoldier, HANDPOS, SECONDHANDPOS, TRUE );
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
}
// Flugente: we have to recheck our flashlights
pSoldier->HandleFlashLights();
}
void SwapOutHandItem( SOLDIERTYPE * pSoldier )
@@ -8694,6 +8697,9 @@ void SwapOutHandItem( SOLDIERTYPE * pSoldier )
// otherwise there's no room for the item anywhere!
}
}
// Flugente: we have to recheck our flashlights
pSoldier->HandleFlashLights();
}
void WaterDamage( SOLDIERTYPE *pSoldier )
@@ -14407,6 +14413,9 @@ BOOLEAN OBJECTTYPE::TransformObject( SOLDIERTYPE * pSoldier, UINT8 ubStatusIndex
}
}
// Flugente: we have to recheck our flashlights
pSoldier->HandleFlashLights();
// Signal a successful transformation.
return TRUE;
}
+194 -1
View File
@@ -108,6 +108,7 @@
#include "IMP Skill Trait.h" // added by Flugente
#include "Food.h" // added by Flugente
#include "Tactical Save.h" // added by Flugente for AddItemsToUnLoadedSector()
#include "LightEffects.h" // added by Flugente for CreatePersonalLight()
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
@@ -4613,6 +4614,8 @@ void SOLDIERTYPE::SetSoldierGridNo( INT32 sNewGridNo, BOOLEAN fForceRemove )
}
}
this->HandleFlashLights();
///HandlePlacingRoofMarker( this, this->sGridNo, TRUE, FALSE );
this->HandleAnimationProfile( this->usAnimState, FALSE );
@@ -7008,9 +7011,11 @@ void SOLDIERTYPE::ChangeSoldierStance( UINT8 ubDesiredStance )
// Now change to appropriate animation
this->EVENT_InitNewSoldierAnim( usNewState, 0 , FALSE );
}
}
this->bSoldierFlagMask |= SOLDIER_REDOFLASHLIGHT;
}
void SOLDIERTYPE::EVENT_InternalSetSoldierDestination( UINT16 usNewDirection, BOOLEAN fFromMove, UINT16 usAnimState )
{
INT32 sNewGridNo;
@@ -7277,6 +7282,9 @@ void SOLDIERTYPE::EVENT_SetSoldierDirection( UINT16 usNewDirection )
// Remove old location data
this->HandleAnimationProfile( this->usAnimState, TRUE );
// Flugente
BOOLEAN fNew = (this->ubDirection != (INT8)usNewDirection);
this->ubDirection = (INT8)usNewDirection;
// Updated extended direction.....
@@ -7307,6 +7315,9 @@ void SOLDIERTYPE::EVENT_SetSoldierDirection( UINT16 usNewDirection )
// Change values!
SetSoldierLocatorOffsets( this );
// Flugente: only update flashlights if we changed our direction
if ( fNew )
this->HandleFlashLights();
}
@@ -13674,6 +13685,52 @@ BOOLEAN SOLDIERTYPE::SoldierCarriesTwoHandedWeapon( void )
// Flugente: Cool down/decay all items in inventory
void SOLDIERTYPE::SoldierInventoryCoolDown(void)
{
// if we have any active flashlights (in our hands for simplicity), drain their batteries
// do this check for both hands
UINT16 firstslot = HANDPOS;
UINT16 lastslot = VESTPOCKPOS;
for (UINT16 invpos = firstslot; invpos < lastslot; ++invpos)
{
OBJECTTYPE* pObj = &(this->inv[invpos]);
if ( !pObj || !(pObj->exists()) )
// can't use this, end
continue;
OBJECTTYPE* pBattery = FindAttachedBatteries(pObj);
if ( !pBattery )
continue;
BOOLEAN flashlightfound = FALSE;
if ( Item [ pObj->usItem ].usFlashLightRange )
flashlightfound = TRUE;
if ( !flashlightfound )
{
attachmentList::iterator iterend = (*pObj)[0]->attachments.end();
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter)
flashlightfound = TRUE;
}
if ( flashlightfound )
{
// 5% chance to lose 1 point
if ( pBattery && Chance(5) )
(*pBattery)[0]->data.objectStatus -= 1;
if ( (*pBattery)[0]->data.objectStatus <= 0 )
{
// destroy batteries
pBattery->RemoveObjectsFromStack(1);
if (pBattery->exists() == false)
this->inv[HANDPOS].RemoveAttachment(pBattery);
}
}
}
// handle flashlight. This is necessary in this location, as we need to do this at least once per turn
this->HandleFlashLights();
if ( !gGameOptions.fWeaponOverheating && !gGameExternalOptions.fDirtSystem && !gGameOptions.fFoodSystem )
return;
@@ -15975,6 +16032,9 @@ void SOLDIERTYPE::SwitchWeapons( BOOLEAN fKnife, BOOLEAN fSideArm )
}
fCharacterInfoPanelDirty = TRUE;
fInterfacePanelDirty = DIRTYLEVEL2;
// Flugente: we have to recheck our flashlights
this->HandleFlashLights();
}
UINT8 tmpuser = 0;
@@ -16185,6 +16245,139 @@ void SOLDIERTYPE::AddDrugValues(UINT8 uDrugType, UINT8 usEffect, UINT8 usTravelR
this->bSoldierFlagMask |= SOLDIER_DRUGGED;
}
void SOLDIERTYPE::HandleFlashLights()
{
// no more need to redo this check
this->bSoldierFlagMask &= ~SOLDIER_REDOFLASHLIGHT;
// we must be active and in a sector (not travelling) in a valid position
if ( !bActive || !bInSector || TileIsOutOfBounds(this->sGridNo) )
return;
// no flashlight stuff if it isn't night, and we aren't underground
if ( !NightTime() && !gbWorldSectorZ )
return;
// remove existing lights we 'own'
if ( this->bSoldierFlagMask & SOLDIER_LIGHT_OWNER )
{
RemovePersonalLights( this->ubID );
this->bSoldierFlagMask &= ~SOLDIER_LIGHT_OWNER;
}
// not possible to get this bonus on a roof, due to our lighting system
if ( !this->pathing.bLevel )
{
UINT8 flashlightrange = this->GetBestEquippedFlashLightRange();
// the range at which we create additional light sources to the side
UINT8 firstexpand = 8;
UINT8 secondexpand = 12;
// depending on our direction, alter range
if ( this->ubDirection == NORTHEAST || this->ubDirection == NORTHWEST || this->ubDirection == SOUTHEAST || this->ubDirection == SOUTHWEST )
{
flashlightrange = std::sqrt( (FLOAT)flashlightrange*(FLOAT)flashlightrange / 2.0f );
firstexpand = std::sqrt( (FLOAT)firstexpand*(FLOAT)firstexpand / 2.0f );
secondexpand = std::sqrt( (FLOAT)secondexpand*(FLOAT)secondexpand / 2.0f );
}
// if no flashlight is found, this will be 0
if ( flashlightrange )
{
// we determine the height of the next tile in our direction. Because of the way structures are handled, we sometimes have to take the very tile we're occupying right now
INT32 nextGridNoinSight = this->sGridNo;
for(UINT8 i = 0; i < flashlightrange; ++i)
{
nextGridNoinSight = NewGridNo( nextGridNoinSight, DirectionInc( this->ubDirection ) );
if ( SoldierToVirtualSoldierLineOfSightTest( this, nextGridNoinSight, this->pathing.bLevel, gAnimControl[ this->usAnimState ].ubEndHeight, FALSE ) )
CreatePersonalLight( nextGridNoinSight, this->ubID );
// after a certain range, add new lights to the side to simulate a light cone
if ( i > firstexpand )
{
INT8 sidedir1 = (this->ubDirection + 2) % NUM_WORLD_DIRECTIONS;
INT8 sidedir2 = (this->ubDirection - 2) % NUM_WORLD_DIRECTIONS;
INT32 sideGridNo1 = NewGridNo( nextGridNoinSight, DirectionInc( sidedir1 ) );
sideGridNo1 = NewGridNo( sideGridNo1, DirectionInc( sidedir1 ) );
if ( SoldierToVirtualSoldierLineOfSightTest( this, sideGridNo1, this->pathing.bLevel, gAnimControl[ this->usAnimState ].ubEndHeight, FALSE, NO_DISTANCE_LIMIT ) )
CreatePersonalLight( sideGridNo1, this->ubID );
if ( i > secondexpand )
{
sideGridNo1 = NewGridNo( sideGridNo1, DirectionInc( sidedir1 ) );
if ( SoldierToVirtualSoldierLineOfSightTest( this, sideGridNo1, this->pathing.bLevel, gAnimControl[ this->usAnimState ].ubEndHeight, FALSE, NO_DISTANCE_LIMIT ) )
CreatePersonalLight( sideGridNo1, this->ubID );
}
INT32 sideGridNo2 = NewGridNo( nextGridNoinSight, DirectionInc( sidedir2 ) );
sideGridNo2 = NewGridNo( sideGridNo2, DirectionInc( sidedir2 ) );
if ( SoldierToVirtualSoldierLineOfSightTest( this, sideGridNo2, this->pathing.bLevel, gAnimControl[ this->usAnimState ].ubEndHeight, FALSE, NO_DISTANCE_LIMIT ) )
CreatePersonalLight( sideGridNo2, this->ubID );
if ( i > secondexpand )
{
sideGridNo2 = NewGridNo( sideGridNo2, DirectionInc( sidedir2 ) );
if ( SoldierToVirtualSoldierLineOfSightTest( this, sideGridNo2, this->pathing.bLevel, gAnimControl[ this->usAnimState ].ubEndHeight, FALSE, NO_DISTANCE_LIMIT ) )
CreatePersonalLight( sideGridNo2, this->ubID );
}
}
}
// take note: we own a light source
this->bSoldierFlagMask |= SOLDIER_LIGHT_OWNER;
}
}
// refresh sight for everybody
AllTeamsLookForAll( TRUE );
SetRenderFlags(RENDER_FLAG_FULL);
}
UINT8 SOLDIERTYPE::GetBestEquippedFlashLightRange()
{
UINT8 bestrange = 0;
// do this check for both hands
UINT16 firstslot = HANDPOS;
UINT16 lastslot = VESTPOCKPOS;
for (UINT16 invpos = firstslot; invpos < lastslot; ++invpos)
{
OBJECTTYPE* pObj = &(this->inv[invpos]);
if ( !pObj || !(pObj->exists()) )
// can't use this, end
continue;
// due to our attachment system, flashlights on guns do not require the batteries to be attached to the flashlight itself - anywhere will do
if ( !FindAttachedBatteries(pObj) )
continue;
if ( Item [ pObj->usItem ].usFlashLightRange )
{
bestrange = max(bestrange, Item [ pObj->usItem ].usFlashLightRange);
}
attachmentList::iterator iterend = (*pObj)[0]->attachments.end();
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter)
{
if(iter->exists() && Item [ iter->usItem ].usFlashLightRange )
bestrange = max(bestrange, Item [ iter->usItem ].usFlashLightRange);
}
}
return( bestrange );
}
INT32 CheckBleeding( SOLDIERTYPE *pSoldier )
{
INT8 bBandaged; //,savedOurTurn;
+6 -3
View File
@@ -367,10 +367,10 @@ enum
#define SOLDIER_POW_PRISON 0x00001000 //4096 // this guy is a prisoner of war in a prison sector. SOLDIER_POW refers to people we capture, this refers to people we hold captive
#define SOLDIER_EQUIPMENT_DROPPED 0x00002000 //8192 // under certain circumstances, militia can be ordered to drop their gear twice. Thus we set a marker to avoid that.
#define SOLDIER_ACCESSTEAMMEMBER 0x00004000 //16384 // this merc is accessing another team member'S inventory (via abusing the stealing mechanic)
/*#define ENEMY_NET_4_LVL_4 0x00008000 //32768
#define SOLDIER_REDOFLASHLIGHT 0x00008000 //32768 // this flag signifies that we somehow interacted with the items in our hands. Thus we have to possible redo lighting from flashlights
#define PLAYER_NET_1_LVL_1 0x00010000 //65536
#define PLAYER_NET_2_LVL_1 0x00020000 //131072
#define SOLDIER_LIGHT_OWNER 0x00010000 //65536 // we 'own' at least one light source (via flashlights)
/*#define PLAYER_NET_2_LVL_1 0x00020000 //131072
#define PLAYER_NET_3_LVL_1 0x00040000 //262144
#define PLAYER_NET_4_LVL_1 0x00080000 //524288
@@ -1560,6 +1560,9 @@ public:
INT8 GetTraitCTHModifier( UINT16 usItem, INT16 ubAimTime, UINT8 ubTargetProfile );
void AddDrugValues(UINT8 uDrugType, UINT8 usEffect, UINT8 usTravelRate, UINT8 usSideEffect );
void HandleFlashLights();
UINT8 GetBestEquippedFlashLightRange();
//////////////////////////////////////////////////////////////////////////////
}; // SOLDIERTYPE;
+68 -9
View File
@@ -26,6 +26,7 @@
#endif
#include "SaveLoadGame.h"
#include "GameVersion.h" // added by Flugente
//forward declarations of common classes to eliminate includes
@@ -33,7 +34,7 @@ class OBJECTTYPE;
class SOLDIERTYPE;
#define NUM_LIGHT_EFFECT_SLOTS 25
#define NUM_LIGHT_EFFECT_SLOTS 500 // 25 // Flugente: raised to 500 - lets see what happens
// GLOBAL FOR LIGHT LISTING
@@ -102,8 +103,8 @@ void UpdateLightingSprite( LIGHTEFFECT *pLight )
LightSpritePosition( pLight->iLight, (INT16)( CenterX( pLight->sGridNo ) / CELL_X_SIZE ), (INT16)( CenterY( pLight->sGridNo ) / CELL_Y_SIZE ) );
}
INT32 NewLightEffect( INT32 sGridNo, UINT8 ubDuration, UINT8 ubStartRadius )
// Flugente: create a pure light, worry about updating sight in other functions
INT32 NewLightEffectInternal( INT32 sGridNo, UINT8 ubDuration, UINT8 ubStartRadius )
{
LIGHTEFFECT *pLight;
INT32 iLightIndex;
@@ -127,13 +128,27 @@ INT32 NewLightEffect( INT32 sGridNo, UINT8 ubDuration, UINT8 ubStartRadius )
UpdateLightingSprite( pLight );
return iLightIndex;
}
INT32 NewLightEffect( INT32 sGridNo, UINT8 ubDuration, UINT8 ubStartRadius )
{
INT32 iLightIndex = NewLightEffectInternal( sGridNo, ubDuration, ubStartRadius );
// Handle sight here....
AllTeamsLookForAll( FALSE );
//Play the breaklight sound
// PlayJA2Sample( BREAK_LIGHT_IGNITING, RATE_11025, SoundVolume( LOWVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
// MAdd: for some reason this crashes the game!
return( pLight->iLight );
if ( iLightIndex > -1 )
{
LIGHTEFFECT *pLight = &gLightEffectData[ iLightIndex ];
return( pLight->iLight );
}
return -1;
}
@@ -468,12 +483,25 @@ BOOLEAN LoadLightEffectsFromMapTempFile( INT16 sMapX, INT16 sMapY, INT8 bMapZ )
//loop through and load the list
for( uiCnt=0; uiCnt<guiNumLightEffects;uiCnt++)
{
//Load the Light effect Data
FileRead( hFile, &gLightEffectData[ uiCnt ], sizeof( LIGHTEFFECT ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( LIGHTEFFECT ) )
if(guiCurrentSaveGameVersion >= DYNAMIC_FLASHLIGHTS)
{
FileClose( hFile );
return( FALSE );
//Load the Light effect Data
FileRead( hFile, &gLightEffectData[ uiCnt ], sizeof( LIGHTEFFECT ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( LIGHTEFFECT ) )
{
return( FALSE );
}
}
else
{
UINT16 bla = sizeof( LIGHTEFFECT );
//Load the Light effect Data
FileRead( hFile, &gLightEffectData[ uiCnt ], LIGHTEFFECT_OLDSIZE, &uiNumBytesRead );
if( uiNumBytesRead != LIGHTEFFECT_OLDSIZE )
{
return( FALSE );
}
}
}
@@ -497,3 +525,34 @@ void ResetLightEffects()
memset( gLightEffectData, 0, sizeof( LIGHTEFFECT ) * NUM_LIGHT_EFFECT_SLOTS );
guiNumLightEffects = 0;
}
// Flugente: create and destroy light sources tied to a person
void CreatePersonalLight( INT32 sGridNo, UINT8 ubID )
{
INT32 iLightIndex = NewLightEffectInternal( sGridNo, 0, 1 );
if ( iLightIndex > -1 )
{
gLightEffectData[ iLightIndex ].ubOwner = ubID;
gLightEffectData[ iLightIndex ].flags |= LIGHTEFFECT_FLASHLIGHT;
}
}
void RemovePersonalLights( UINT8 ubID )
{
LIGHTEFFECT *pLight;
// Set to unallocated....
for ( UINT32 cnt = 0; cnt < guiNumLightEffects; ++cnt )
{
pLight = &gLightEffectData[ cnt ];
if ( pLight->iLight != (-1) && pLight->flags & LIGHTEFFECT_FLASHLIGHT && pLight->ubOwner == ubID && pLight->fAllocated )
{
pLight->fAllocated = FALSE;
pLight->flags &= ~LIGHTEFFECT_FLASHLIGHT;
LightSpriteDestroy( pLight->iLight );
}
}
}
+10
View File
@@ -23,8 +23,14 @@ typedef struct
INT32 iLight;
UINT32 uiTimeOfLastUpdate;
// Flugente: added variables necessary for dynamic (flash-)lighting
INT32 flags;
UINT8 ubOwner;
} LIGHTEFFECT;
#define LIGHTEFFECT_OLDSIZE 20 // Flugente: the old size of LIGHTEFFECT (before Gameversion DYNAMIC_FLASHLIGHTS)
#define LIGHTEFFECT_FLASHLIGHT 0x00000001
// Decays all light effects...
@@ -48,4 +54,8 @@ void ResetLightEffects();
BOOLEAN IsLightEffectAtTile( INT32 sGridNo );
// Flugente: create and destroy light sources tied to a person
void CreatePersonalLight( INT32 sGridNo, UINT8 ubID );
void RemovePersonalLights( UINT8 ubID );
#endif
+10 -4
View File
@@ -272,7 +272,8 @@ itemStartElementHandle(void *userData, const XML_Char *name, const XML_Char **at
strcmp(name, "usActionItemFlag") == 0 ||
strcmp(name, "clothestype") == 0 ||
strcmp(name, "randomitem") == 0 ||
strcmp(name, "randomitemcoolnessmodificator") == 0 ))
strcmp(name, "randomitemcoolnessmodificator") == 0 ||
strcmp(name, "FlashLightRange") == 0 ))
{
pData->curElement = ELEMENT_PROPERTY;
//DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("itemStartElementHandle: going into element, name = %s",name) );
@@ -1406,8 +1407,12 @@ itemEndElementHandle(void *userData, const XML_Char *name)
// no nonsense, only values between -20 and + 20
pData->curItem.randomitemcoolnessmodificator = min(20, max(-20, pData->curItem.randomitemcoolnessmodificator) );
}
else if(strcmp(name, "FlashLightRange") == 0)
{
pData->curElement = ELEMENT;
pData->curItem.usFlashLightRange = (UINT8) atol(pData->szCharData);
}
pData->maxReadDepth--;
}
@@ -2044,7 +2049,8 @@ BOOLEAN WriteItemStats()
FilePrintf(hFile,"\t\t<clothestype>%d</clothestype>\r\n", Item[cnt].clothestype );
FilePrintf(hFile,"\t\t<randomitem>%d</randomitem>\r\n", Item[cnt].randomitem );
FilePrintf(hFile,"\t\t<randomitemcoolnessmodificator>%d</randomitemcoolnessmodificator>\r\n", Item[cnt].randomitemcoolnessmodificator );
FilePrintf(hFile,"\t\t<FlashLightRange>%d</FlashLightRange>\r\n", Item[cnt].usFlashLightRange );
FilePrintf(hFile,"\t</ITEM>\r\n");
}
FilePrintf(hFile,"</ITEMLIST>\r\n");