502 lines
16 KiB
Plaintext
502 lines
16 KiB
Plaintext
/*
|
|
* Copyright (c) 2016-2022 Vera Visions LLC.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
/* 8 bytes */
|
|
int type1;
|
|
int type2;
|
|
|
|
int versionNumber;
|
|
int extraFlags;
|
|
int numNodes;
|
|
|
|
/* 32 bytes */
|
|
int mapname1;
|
|
int mapname2;
|
|
int mapname3;
|
|
int mapname4;
|
|
int mapname5;
|
|
int mapname6;
|
|
int mapname7;
|
|
int mapname8;
|
|
} fb_header;
|
|
|
|
#define FBFL_TEAM1 0x00000001
|
|
#define FBFL_TEAM2 0x00000002
|
|
#define FBFL_TEAMSPECIFIC 0x00000004
|
|
#define FBFL_CROUCH 0x00000008
|
|
#define FBFL_LADDER 0x00000010
|
|
#define FBFL_LIFT 0x00000020
|
|
#define FBFL_WALK 0x00000040
|
|
#define FBFL_HEALTH 0x00000080
|
|
#define FBFL_ARMOR 0x00000100
|
|
#define FBFL_AMMO 0x00000200
|
|
#define FBFL_SNIPER 0x00000400
|
|
#define FBFL_GOALITEM 0x00000800
|
|
#define FBFL_GOAL 0x00001000
|
|
#define FBFL_SENTRY 0x00002000
|
|
#define FBFL_AIMING 0x00004000
|
|
#define FBFL_JUMP 0x00008000
|
|
#define FBFL_DETPACKSEAL 0x00010000
|
|
#define FBFL_PATHCHECK 0x00020000
|
|
#define FBFL_UNUSED1 0x00040000
|
|
#define FBFL_UNUSED2 0x00080000
|
|
#define FBFL_UNUSED3 0x00100000
|
|
#define FBFL_UNUSED4 0x00200000
|
|
#define FBFL_UNUSED5 0x00400000
|
|
#define ITEM_UNUSED6 0x00800000
|
|
#define FBFL_PIPETRAP 0x01000000
|
|
#define FBFL_DETPACKCLEAR 0x02000000
|
|
#define FBFL_TELEENTER 0x04000000
|
|
#define FBFL_CONCJUMP 0x08000000
|
|
#define FBFL_SENTRY_180 0x10000000
|
|
#define FBFL_DEFEND 0x20000000
|
|
#define FBFL_TELEEXIT 0x40000000
|
|
#define FBFL_DELETED 0x80000000
|
|
|
|
int
|
|
Way_FBFlagsToNC(int inputFlags)
|
|
{
|
|
int outFlags = 0i;
|
|
|
|
if (inputFlags & FBFL_JUMP)
|
|
outFlags |= LF_JUMP;
|
|
|
|
if (inputFlags & FBFL_WALK)
|
|
outFlags |= LF_WALK;
|
|
|
|
if (inputFlags & FBFL_CROUCH)
|
|
outFlags |= LF_CROUCH;
|
|
|
|
if (inputFlags & FBFL_AIMING)
|
|
outFlags |= LF_AIM;
|
|
|
|
if (inputFlags & FBFL_LADDER)
|
|
outFlags |= LF_AIM;
|
|
|
|
return outFlags;
|
|
}
|
|
|
|
/* Reads a FB waypoint file.
|
|
|
|
These are binary files and contain a lot of type sizes that we don't
|
|
have access to in QC. So we cannot just fread() into a struct.
|
|
|
|
The only flag between nodes is seemingly JUMP (1), however the
|
|
individual nodes themselves have defining info flags too including
|
|
ones for using buttons and ladders. Ideally we'd want to re-interpret
|
|
those into linkflags.
|
|
*/
|
|
void
|
|
Way_ReadFBFile(string strFile, bool flush)
|
|
{
|
|
int startId = 0i;
|
|
int offSet = 0i;
|
|
fb_header fbHeader;
|
|
int *nodeFlags;
|
|
|
|
filestream file = fopen(strFile, FILE_READ);
|
|
if (file < 0) {
|
|
print("Way_ReadFile: unable to open ", strFile, "\n");
|
|
return;
|
|
}
|
|
|
|
fread(file, (void*)&fbHeader, sizeof(fb_header));
|
|
|
|
if (flush) {
|
|
Way_WipeWaypoints();
|
|
g_iWaypoints = fbHeader.numNodes;
|
|
g_pWaypoints = memalloc(sizeof(*g_pWaypoints) * g_iWaypoints);
|
|
} else {
|
|
int oldSize = g_iWaypoints;
|
|
int newSize = g_iWaypoints + fbHeader.numNodes;
|
|
g_pWaypoints = (waypoint_t *)memrealloc(g_pWaypoints, sizeof(waypoint_t), oldSize, newSize);
|
|
g_iWaypoints = newSize;
|
|
startId = oldSize;
|
|
offSet = oldSize;
|
|
}
|
|
|
|
print(sprintf("FB Waypoints Version %i, %i nodes\n", fbHeader.versionNumber, fbHeader.numNodes));
|
|
|
|
nodeFlags = (int *)memalloc(sizeof(int) * fbHeader.numNodes);
|
|
|
|
/* create the nodes */
|
|
for (int i = startId; i < g_iWaypoints; i++) {
|
|
int iData = 0i;
|
|
float flData = 0.0f;
|
|
|
|
fread(file, (void*)&nodeFlags[i], 4); /* flags */
|
|
fread(file, (void*)&iData, 4); /* script flags, should be 1 byte but it's padded. */
|
|
|
|
fread(file, (void*)&flData, 4); /* origin x */
|
|
g_pWaypoints[i].m_vecOrigin[0] = flData;
|
|
fread(file, (void*)&flData, 4); /* origin y */
|
|
g_pWaypoints[i].m_vecOrigin[1] = flData;
|
|
fread(file, (void*)&flData, 4); /* origin z */
|
|
g_pWaypoints[i].m_vecOrigin[2] = flData;
|
|
}
|
|
|
|
/* create the links */
|
|
for (int i = startId; i < g_iWaypoints; i++) {
|
|
int iData = 0i;
|
|
fread(file, (void*)&iData, 2); /* neighbour count */
|
|
//print(sprintf("%i neighbours\n", iData));
|
|
|
|
g_pWaypoints[i].m_numNeighbours = iData;
|
|
g_pWaypoints[i].m_pNeighbour = memalloc(sizeof(*g_pWaypoints[i].m_pNeighbour) * g_pWaypoints[i].m_numNeighbours);
|
|
|
|
for (int j = 0i; j < g_pWaypoints[i].m_numNeighbours; j++) {
|
|
fread(file, (void*)&iData, 2); /* neighbour index */
|
|
g_pWaypoints[i].m_pNeighbour[j].m_iNode = offSet + iData;
|
|
g_pWaypoints[i].m_pNeighbour[j].m_flCost = -1;
|
|
g_pWaypoints[i].m_pNeighbour[j].m_iFlags = Way_FBFlagsToNC(nodeFlags[offSet + iData]);
|
|
}
|
|
}
|
|
|
|
memfree(nodeFlags);
|
|
fclose(file);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
/* 8 bytes */
|
|
int type1;
|
|
int type2;
|
|
int versionNumber;
|
|
int numNodes;
|
|
/* 32 bytes */
|
|
int mapname1;
|
|
int mapname2;
|
|
int mapname3;
|
|
int mapname4;
|
|
int mapname5;
|
|
int mapname6;
|
|
int mapname7;
|
|
int mapname8;
|
|
/* 32 bytes */
|
|
int author1;
|
|
int author2;
|
|
int author3;
|
|
int author4;
|
|
int author5;
|
|
int author6;
|
|
int author7;
|
|
int author8;
|
|
} pb_header;
|
|
|
|
#define PBFL_USEBUTTON 0x00000001i
|
|
#define PBFL_ELEVATOR 0x00000002i
|
|
#define PBFL_CROUCH 0x00000004i
|
|
#define PBFL_CROSSING 0x00000008i
|
|
#define PBFL_GOAL 0x00000010i
|
|
#define PBFL_LADDER 0x00000020i
|
|
#define PBFL_RESCUE 0x00000040i
|
|
#define PBFL_CAMP 0x00000080i
|
|
|
|
int
|
|
Way_PBFlagsToNC(int inputFlags)
|
|
{
|
|
int outFlags = 0i;
|
|
|
|
if (inputFlags & 1)
|
|
outFlags |= LF_JUMP;
|
|
|
|
return outFlags;
|
|
}
|
|
|
|
/* Reads a PB version 5, 6, 7 file.
|
|
|
|
These are binary files and contain a lot of type sizes that we don't
|
|
have access to in QC. So we cannot just fread() into a struct.
|
|
|
|
The only flag between nodes is seemingly JUMP (1), however the
|
|
individual nodes themselves have defining info flags too including
|
|
ones for using buttons and ladders. Ideally we'd want to re-interpret
|
|
those into linkflags.
|
|
*/
|
|
void
|
|
Way_ReadPBFile(string strFile, bool flush)
|
|
{
|
|
int startId = 0i;
|
|
int offSet = 0i;
|
|
pb_header pbHeader;
|
|
|
|
filestream file = fopen(strFile, FILE_READ);
|
|
if (file < 0) {
|
|
print("Way_ReadFile: unable to open ", strFile, "\n");
|
|
return;
|
|
}
|
|
|
|
fread(file, (void*)&pbHeader, sizeof(pb_header));
|
|
|
|
if (flush) {
|
|
Way_WipeWaypoints();
|
|
g_iWaypoints = pbHeader.numNodes;
|
|
g_pWaypoints = memalloc(sizeof(*g_pWaypoints) * g_iWaypoints);
|
|
} else {
|
|
int oldSize = g_iWaypoints;
|
|
int newSize = g_iWaypoints + pbHeader.numNodes;
|
|
g_pWaypoints = (waypoint_t *)memrealloc(g_pWaypoints, sizeof(waypoint_t), oldSize, newSize);
|
|
g_iWaypoints = newSize;
|
|
startId = oldSize;
|
|
offSet = oldSize;
|
|
}
|
|
|
|
print(sprintf("PB Waypoints Version %i, %i nodes\n", pbHeader.versionNumber, pbHeader.numNodes));
|
|
|
|
for (int i = startId; i < g_iWaypoints; i++) {
|
|
int neighbours = 0i;
|
|
int index[8];
|
|
float cost[8];
|
|
int nFlags[8];
|
|
int iData = 0i;
|
|
float flData = 0.0f;
|
|
|
|
/* make them all invalid */
|
|
for (int n = 0i; n < 8i; n++) {
|
|
index[n] = 65535;
|
|
}
|
|
|
|
if (pbHeader.versionNumber == 7) {
|
|
fread(file, (void*)&iData, 4); /* number */
|
|
fread(file, (void*)&iData, 4); /* flags */
|
|
fread(file, (void*)&flData, 4); /* origin x */
|
|
g_pWaypoints[i].m_vecOrigin[0] = flData;
|
|
fread(file, (void*)&flData, 4); /* origin y */
|
|
g_pWaypoints[i].m_vecOrigin[1] = flData;
|
|
fread(file, (void*)&flData, 4); /* origin z */
|
|
g_pWaypoints[i].m_vecOrigin[2] = flData;
|
|
fread(file, (void*)&flData, 4); /* radius */
|
|
g_pWaypoints[i].m_flRadius = flData;
|
|
fread(file, (void*)&flData, 4); /* camp start x */
|
|
fread(file, (void*)&flData, 4); /* camp start y */
|
|
fread(file, (void*)&flData, 4); /* camp end x */
|
|
fread(file, (void*)&flData, 4); /* camp end y */
|
|
fread(file, (void*)&index[0], 2); /* index 1 */
|
|
fread(file, (void*)&index[1], 2); /* index 2 */
|
|
fread(file, (void*)&index[2], 2); /* index 3 */
|
|
fread(file, (void*)&index[3], 2); /* index 4 */
|
|
fread(file, (void*)&index[4], 2); /* index 5 */
|
|
fread(file, (void*)&index[5], 2); /* index 6 */
|
|
fread(file, (void*)&index[6], 2); /* index 7 */
|
|
fread(file, (void*)&index[7], 2); /* index 8 */
|
|
fread(file, (void*)&nFlags[0], 2); /* nFlags 1 */
|
|
fread(file, (void*)&nFlags[1], 2); /* nFlags 2 */
|
|
fread(file, (void*)&nFlags[2], 2); /* nFlags 3 */
|
|
fread(file, (void*)&nFlags[3], 2); /* nFlags 4 */
|
|
fread(file, (void*)&nFlags[4], 2); /* nFlags 5 */
|
|
fread(file, (void*)&nFlags[5], 2); /* nFlags 6 */
|
|
fread(file, (void*)&nFlags[6], 2); /* nFlags 7 */
|
|
fread(file, (void*)&nFlags[7], 2); /* nFlags 8 */
|
|
fread(file, (void*)&flData, 4); /* velocity 1 x */
|
|
fread(file, (void*)&flData, 4); /* velocity 1 y */
|
|
fread(file, (void*)&flData, 4); /* velocity 1 z */
|
|
fread(file, (void*)&flData, 4); /* velocity 2 x */
|
|
fread(file, (void*)&flData, 4); /* velocity 2 y */
|
|
fread(file, (void*)&flData, 4); /* velocity 2 z */
|
|
fread(file, (void*)&flData, 4); /* velocity 3 x */
|
|
fread(file, (void*)&flData, 4); /* velocity 3 y */
|
|
fread(file, (void*)&flData, 4); /* velocity 3 z */
|
|
fread(file, (void*)&flData, 4); /* velocity 4 x */
|
|
fread(file, (void*)&flData, 4); /* velocity 4 y */
|
|
fread(file, (void*)&flData, 4); /* velocity 4 z */
|
|
fread(file, (void*)&flData, 4); /* velocity 5 x */
|
|
fread(file, (void*)&flData, 4); /* velocity 5 y */
|
|
fread(file, (void*)&flData, 4); /* velocity 5 z */
|
|
fread(file, (void*)&flData, 4); /* velocity 6 x */
|
|
fread(file, (void*)&flData, 4); /* velocity 6 y */
|
|
fread(file, (void*)&flData, 4); /* velocity 6 z */
|
|
fread(file, (void*)&flData, 4); /* velocity 7 x */
|
|
fread(file, (void*)&flData, 4); /* velocity 7 y */
|
|
fread(file, (void*)&flData, 4); /* velocity 7 z */
|
|
fread(file, (void*)&flData, 4); /* velocity 8 x */
|
|
fread(file, (void*)&flData, 4); /* velocity 8 y */
|
|
fread(file, (void*)&flData, 4); /* velocity 8 z */
|
|
fread(file, (void*)&iData, 4); /* distance 1 */
|
|
cost[0] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 2 */
|
|
cost[1] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 3 */
|
|
cost[2] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 4 */
|
|
cost[3] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 5 */
|
|
cost[4] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 6 */
|
|
cost[5] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 7 */
|
|
cost[6] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 8 */
|
|
cost[7] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* pointer */
|
|
} else if (pbHeader.versionNumber == 6) {
|
|
fread(file, (void*)&iData, 4); /* number */
|
|
fread(file, (void*)&iData, 4); /* flags */
|
|
fread(file, (void*)&flData, 4); /* origin x */
|
|
g_pWaypoints[i].m_vecOrigin[0] = flData;
|
|
fread(file, (void*)&flData, 4); /* origin y */
|
|
g_pWaypoints[i].m_vecOrigin[1] = flData;
|
|
fread(file, (void*)&flData, 4); /* origin z */
|
|
g_pWaypoints[i].m_vecOrigin[2] = flData;
|
|
fread(file, (void*)&flData, 4); /* radius */
|
|
g_pWaypoints[i].m_flRadius = flData;
|
|
fread(file, (void*)&flData, 4); /* camp start x */
|
|
fread(file, (void*)&flData, 4); /* camp start y */
|
|
fread(file, (void*)&flData, 4); /* camp end x */
|
|
fread(file, (void*)&flData, 4); /* camp end y */
|
|
fread(file, (void*)&index[0], 2); /* index 1 */
|
|
fread(file, (void*)&index[1], 2); /* index 2 */
|
|
fread(file, (void*)&index[2], 2); /* index 3 */
|
|
fread(file, (void*)&index[3], 2); /* index 4 */
|
|
fread(file, (void*)&index[4], 2); /* index 5 */
|
|
fread(file, (void*)&index[5], 2); /* index 6 */
|
|
fread(file, (void*)&index[6], 2); /* index 7 */
|
|
fread(file, (void*)&index[7], 2); /* index 8 */
|
|
fread(file, (void*)&iData, 4); /* distance 1 */
|
|
cost[0] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 2 */
|
|
cost[1] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 3 */
|
|
cost[2] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 4 */
|
|
cost[3] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 5 */
|
|
cost[4] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 6 */
|
|
cost[5] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 7 */
|
|
cost[6] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 8 */
|
|
cost[7] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* pointer */
|
|
} else if (pbHeader.versionNumber == 5) {
|
|
fread(file, (void*)&iData, 4); /* number */
|
|
fread(file, (void*)&iData, 4); /* flags */
|
|
fread(file, (void*)&flData, 4); /* origin x */
|
|
g_pWaypoints[i].m_vecOrigin[0] = flData;
|
|
fread(file, (void*)&flData, 4); /* origin y */
|
|
g_pWaypoints[i].m_vecOrigin[1] = flData;
|
|
fread(file, (void*)&flData, 4); /* origin z */
|
|
g_pWaypoints[i].m_vecOrigin[2] = flData;
|
|
fread(file, (void*)&flData, 4); /* radius */
|
|
g_pWaypoints[i].m_flRadius = flData;
|
|
fread(file, (void*)&flData, 4); /* camp start x */
|
|
fread(file, (void*)&flData, 4); /* camp start y */
|
|
fread(file, (void*)&flData, 4); /* camp end x */
|
|
fread(file, (void*)&flData, 4); /* camp end y */
|
|
fread(file, (void*)&index[0], 2); /* index 1 */
|
|
fread(file, (void*)&index[1], 2); /* index 2 */
|
|
fread(file, (void*)&index[2], 2); /* index 3 */
|
|
fread(file, (void*)&index[3], 2); /* index 4 */
|
|
fread(file, (void*)&iData, 4); /* distance 1 */
|
|
cost[0] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 2 */
|
|
cost[1] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 3 */
|
|
cost[2] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* distance 4 */
|
|
cost[3] = (float)iData;
|
|
fread(file, (void*)&iData, 4); /* pointer */
|
|
}
|
|
|
|
/* check for valid neighbours */
|
|
for (int n = 0i; n < 8i; n++) {
|
|
if (index[n] != 65535) /* -1 */
|
|
neighbours++;
|
|
}
|
|
|
|
g_pWaypoints[i].m_numNeighbours = neighbours;
|
|
g_pWaypoints[i].m_pNeighbour = memalloc(sizeof(*g_pWaypoints[i].m_pNeighbour) * g_pWaypoints[i].m_numNeighbours);
|
|
|
|
for (int j = 0i; j < g_pWaypoints[i].m_numNeighbours; j++) {
|
|
g_pWaypoints[i].m_pNeighbour[j].m_iNode = offSet + index[j];
|
|
g_pWaypoints[i].m_pNeighbour[j].m_flCost = cost[j];
|
|
g_pWaypoints[i].m_pNeighbour[j].m_iFlags = Way_PBFlagsToNC(nFlags[j]);
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
|
|
|
|
/* Reads a Jumbot file.
|
|
|
|
Plaintext that's simple to parse, however does not store any node
|
|
connections. That's because Jumbot was specific to Half-Life and
|
|
the AI was smart enough to know how to navigate using only those
|
|
helper points.
|
|
|
|
We count all lines until we hit the end character '$' and then parse
|
|
each line - which contains the info: [id] [flag] ([position])
|
|
|
|
We then link those in linear order. There's probably better ways of
|
|
doing that so that'll be an area of research.
|
|
*/
|
|
void
|
|
Way_ReadJumbotFile(string strFile, bool flush)
|
|
{
|
|
int startId = 0i;
|
|
int offSet = 0i;
|
|
int nodeCount = 0i;
|
|
string line;
|
|
|
|
filestream file = fopen(strFile, FILE_READ);
|
|
if (file < 0) {
|
|
print("Way_ReadFile: unable to open ", strFile, "\n");
|
|
return;
|
|
}
|
|
|
|
/* read the number of waypoints */
|
|
while ((line = fgets(file))) {
|
|
if (line != "$") {
|
|
nodeCount++;
|
|
}
|
|
}
|
|
fseek(file, 0);
|
|
|
|
print(sprintf("Jumpbot Waypoints Version %i, %i nodes\n", 1i, nodeCount));
|
|
|
|
if (flush) {
|
|
Way_WipeWaypoints();
|
|
g_iWaypoints = nodeCount;
|
|
g_pWaypoints = memalloc(sizeof(*g_pWaypoints) * g_iWaypoints);
|
|
} else {
|
|
int oldSize = g_iWaypoints;
|
|
int newSize = g_iWaypoints + nodeCount;
|
|
g_pWaypoints = (waypoint_t *)memrealloc(g_pWaypoints, sizeof(waypoint_t), oldSize, newSize);
|
|
g_iWaypoints = newSize;
|
|
startId = oldSize;
|
|
offSet = oldSize;
|
|
}
|
|
|
|
for (int i = startId; i < g_iWaypoints; i++) {
|
|
float length = strlen(argv(5));
|
|
line = fgets(file);
|
|
tokenize(line);
|
|
g_pWaypoints[i].m_vecOrigin[0] = stof(argv(3));
|
|
g_pWaypoints[i].m_vecOrigin[1] = stof(argv(4));
|
|
g_pWaypoints[i].m_vecOrigin[2] = stof(argv(5));
|
|
g_pWaypoints[i].m_flRadius = 32.0f;
|
|
|
|
if (i > startId) {
|
|
Way_LinkNodes(&g_pWaypoints[i], &g_pWaypoints[i-1]);
|
|
Way_LinkNodes(&g_pWaypoints[i-1], &g_pWaypoints[i]);
|
|
}
|
|
}
|
|
|
|
fclose(file);
|
|
} |