Descent3/legacy/editor/Group.cpp
2024-05-23 23:07:26 -04:00

838 lines
23 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/Main/editor/Group.cpp $
* $Revision: 1.1.1.1 $
* $Date: 2003-08-26 03:57:38 $
* $Author: kevinb $
*
* Functions for copying, pasting, etc. of groups
*
* $Log: not supported by cvs2svn $
*
* 35 9/15/99 1:56p Matt
* Added the option to allow rooms or groups placed on the terrain to
* either align with the terrain or with gravity.
*
* 34 8/30/99 1:01p Gwar
* use the "true" center of faces for determining place position of a
* group
*
* 33 8/17/99 6:12p Gwar
* handle NEWEDITOR generic object management in AttachGroup
*
* 32 8/17/99 6:46a Gwar
* added group support to NEWEDITOR
*
* 31 5/08/99 1:39a Matt
* Added a function to delete all objects of a certain type, and support
* for placing and attaching groups to the terrain.
*
* 30 4/21/99 10:19p Matt
* DeleteGroup() wasn't setting the world changed flag
*
* 29 3/15/99 1:29p Matt
* Added objects & triggers to save/load for groups.
*
* 28 1/29/99 12:48p Matt
* Rewrote the doorway system
*
* 27 1/26/99 6:02p Matt
* Fixed (I hope) a problem with pasted object having the wrong
* orientation.
*
* 26 1/21/99 11:15p Jeff
* pulled out some structs and defines from header files and moved them
* into seperate header files so that multiplayer dlls don't require major
* game headers, just those new headers. Side effect is a shorter build
* time. Also cleaned up some header file #includes that weren't needed.
* This affected polymodel.h, object.h, player.h, vecmat.h, room.h,
* manage.h and multi.h
*
* 25 1/21/99 11:34a Matt
* Got rid of portal triggers. Since we don't have multi-face portals, a
* face trigger works fine for a portal. Also fixed a few editor/trigger
* bugs.
*
* 24 1/14/99 11:06a Matt
* Added names to triggers
*
* 23 1/08/99 2:56p Samir
* Ripped out OSIRIS1.
*
* 22 12/01/98 11:17p Matt
* Made copy/paste work with doors.
*
* 21 10/08/98 4:23p Kevin
* Changed code to comply with memory library usage. Always use mem_malloc
* , mem_free and mem_strdup
*
* 20 9/24/98 5:00p Matt
* Improved error checking for running out of rooms.
*
* 19 9/02/98 5:57p Matt
* Added code to make faces match exactly when forming a portal. This
* should get rid of all remaining cracking problems.
*
* 18 9/01/98 12:04p Matt
* Ripped out multi-face portal code
*
* 17 8/15/98 10:50p Matt
* When copying a portal, copy its flags
*
* 16 6/23/98 2:43p Matt
* Changed calls to OutrageMessageBox() & Debug_MessageBox() to deal with
* int return value (instead of bool).
*
* 15 4/21/98 2:43p Matt
* Added system to disable render updates while deleting large numbers of
* rooms, since the mine is not stable until this operation is done.
*
* 14 2/09/98 1:38p Matt
* When pasting a group, ask the user if he also wants to paste objects
* and triggers
*
* 13 2/04/98 6:23p Matt
* Changed object room number to indicate a terrain cell via a flag. Got
* rid of the object flag which used to indicate terrain.
*
* 12 1/29/98 5:50p Matt
* Changed old camera object type to be viewer object (for editor), and
* now camera objects will just be for game cameras.
*
* 11 1/20/98 4:12p Samir
* New script housekeeping system.
*
* 10 1/15/98 7:34p Matt
* Revamped error checking when computing face normals
*
* 9 12/23/97 11:06a Samir
* Added pserror.h
*
* 8 10/13/97 6:20p Matt
* Fixed stupid bug copying objects in group
*
* 7 10/03/97 3:37p Matt
* Fixed bug copying multiple objects in a room, and changed code to not
* copy viewer or player objects
*
* 6 9/24/97 6:18p Samir
* Use script names instead of script id values to identify scripts.
*
* 5 9/22/97 2:31p Matt
* Made group code copy & paste objects & triggers
*
* 4 9/08/97 4:05p Samir
* Fixed some warnings and took out extranneous includes to windows.h in
* important headers.
*
* 3 9/02/97 6:42p Matt
* Got paste & group save/load working
*
* 2 8/29/97 5:45p Matt
* Converted group code to work with rooms (unfinished)
*
* 11 6/16/97 6:40p Matt
* Fixed bug caused when Selected_segments[] was changed while
* DeleteGroup() was indexing through it.
*
* 10 6/12/97 7:17p Matt
* Deleted now-unused RotateWorld() function
*
* 9 5/23/97 5:46p Matt
* Added SetMatrixBasedOnSide() functionality to ExtractMatrixFromSeg().
*
* 8 5/13/97 11:58a Matt
* Made group file code save & load the level file version number to use
* when reading segments
*
* 7 5/08/97 5:07p Matt
* When rotating world, rotate the editor's wireframe window view vars
*
* 6 5/08/97 4:43p Matt
* Fixed bug when rotating objects in RotateWorld()
*
* 5 5/06/97 2:26p Jason
* fixed bug in rotate world
*
* 4 5/05/97 3:57p Matt
* Added code to rotate the mine (& the objects in it) to join with the
* terrain.
*
* 3 3/31/97 5:58p Matt
* Revamped mine update flags
*
* 2 3/12/97 3:25p Matt
* Added funcs for cut, copy, paste, & delete, and to save and load
* groups.
*
* 1 3/11/97 9:34p Matt
*
* $NoKeywords: $
*/
#include "group.h"
#ifndef NEWEDITOR
#include "d3edit.h"
#else
#include "globals.h"
#endif
#include "selectedroom.h"
#include "room.h"
#include "hroom.h"
#include "erooms.h"
#include "loadlevel.h"
#ifndef NEWEDITOR
#include "moveworld.h"
#endif
#include "object.h"
#include "trigger.h"
#include "pserror.h"
#include "doorway.h"
#include "mem.h"
#include <string.h>
#include "door.h"
//Free a group.
//Parameters: g - the group to be freed
void FreeGroup(group *g)
{
for (int r=0;r<g->nrooms;r++)
FreeRoom(&g->rooms[r]);
mem_free(g->rooms);
if (g->nobjects)
mem_free(g->objects);
if (g->ntriggers)
mem_free(g->triggers);
mem_free(g);
}
//Copy the given list of rooms to a group
//Parameters: nrooms - the number of rooms in list
// roomnums - pointer to list of room numbers
// attachroom, attachface - where group attaches when pasted
//Returns: pointer to group
group *CopyGroup(int nrooms,int *roomnums,int attachroom,int attachface)
{
group *g;
int r;
int room_xlate[MAX_ROOMS];
int n_objects = 0, n_triggers = 0, n_doors = 0;
//Allocate group & room arrays
g = (group *) mem_malloc(sizeof(*g)); ASSERT(g != NULL);
g->nrooms = nrooms;
g->rooms = (room *) mem_malloc(sizeof(*g->rooms) * nrooms); ASSERT(g->rooms != NULL);
//Initialize xlate list
for (r=0;r<MAX_ROOMS;r++)
room_xlate[r] = -1;
//Copy the rooms & build xlate list. Count objects while we're at it
for (r=0;r<nrooms;r++) {
CopyRoom(&g->rooms[r],&Rooms[roomnums[r]]);
room_xlate[roomnums[r]] = r;
for(int o=Rooms[roomnums[r]].objects;o!=-1;o=Objects[o].next)
if ((Objects[o].type != OBJ_VIEWER) && (Objects[o].type != OBJ_CAMERA) && (Objects[o].type != OBJ_PLAYER)) {
n_objects++;
if (Objects[o].type == OBJ_DOOR)
n_doors++;
}
}
//Add portals for rooms in the group
for (r=0;r<nrooms;r++)
for (int p=0;p<Rooms[roomnums[r]].num_portals;p++) {
portal *pp = &Rooms[roomnums[r]].portals[p];
if ((pp->croom != -1) && IsRoomSelected(pp->croom)) {
//link the two rooms
if (r < room_xlate[pp->croom]) //only link lower to higher, so we don't double link
LinkRoomsSimple(g->rooms,r,pp->portal_face,room_xlate[pp->croom],Rooms[pp->croom].portals[pp->cportal].portal_face);
//Copy the portal flags
int new_portal_num = g->rooms[r].faces[pp->portal_face].portal_num;
g->rooms[r].portals[new_portal_num].flags = pp->flags;
}
}
//Fill in attach fields
g->attachroom = room_xlate[attachroom];
g->attachface = attachface;
//Copy objects
g->nobjects = n_objects;
g->ndoors = n_doors;
if (n_objects == 0)
g->objects = NULL;
else {
int objnum = 0;
//Get memory for objects
g->objects = (object *) mem_malloc(sizeof(*g->objects) * n_objects);
//Go through list of rooms & copy the objects
for (r=0;r<nrooms;r++) {
for(int o=Rooms[roomnums[r]].objects;o!=-1;o=Objects[o].next) {
object *dest = &g->objects[objnum], *src = &Objects[o];
if ((src->type == OBJ_VIEWER) || (src->type == OBJ_CAMERA) || (src->type == OBJ_PLAYER))
continue; //don't copy cameras
//Copy some selected data
dest->type = src->type;
dest->id = src->id;
dest->flags = src->flags;
dest->pos = src->pos;
dest->orient = src->orient;
dest->lifeleft = src->lifeleft;
dest->contains_type = src->contains_type;
dest->contains_id = src->contains_id;
dest->contains_count = src->contains_count;
if (src->control_type == CT_POWERUP)
dest->ctype.powerup_info.count = src->ctype.powerup_info.count;
if (src->type == OBJ_DOOR)
dest->shields = src->shields;
dest->roomnum = r;
objnum++;
}
}
}
//Count the triggers in the group
for (int t=0;t<Num_triggers;t++)
if (room_xlate[Triggers[t].roomnum] != -1)
n_triggers++;
//Copy triggers
g->ntriggers = n_triggers;
if (n_triggers == 0)
g->triggers = NULL;
else {
int trignum = 0;
//Get memory for triggers
g->triggers = (trigger *) mem_malloc(sizeof(*g->triggers) * n_triggers);
for (t=0;t<Num_triggers;t++)
if (room_xlate[Triggers[t].roomnum] != -1) {
g->triggers[trignum] = Triggers[t];
g->triggers[trignum].roomnum = room_xlate[Triggers[t].roomnum];
trignum++;
}
}
//Return the new group
return g;
}
extern bool Disable_editor_rendering;
//Delete the given list of rooms
//Parameters: nrooms - the number of rooms in list
// roomnums - pointer to list of room numbers
void DeleteGroup(int nrooms,int *roomnums)
{
int i;
int *troomnums;
bool save_disable_flag = Disable_editor_rendering;
Disable_editor_rendering = 1;
//Make copy of list of rooms to delete. We must do this because roomnums may be a pointer
//to the global Selected_list, the contents of which may be changed by DeleteRoomFromMine()
troomnums = (int *) mem_malloc(sizeof(*troomnums) * nrooms);
for (i=0;i<nrooms;i++)
troomnums[i] = roomnums[i];
//Now delete the rooms
for (i=0;i<nrooms;i++)
DeleteRoomFromMine(&Rooms[troomnums[i]]);
//Free up our array
mem_free(troomnums);
//Regenerate external object rooms
CreateRoomObjects();
//Restore old flag
Disable_editor_rendering = save_disable_flag;
//We're done! Update flags and leave
World_changed = 1;
}
//Place the given group at the specified room face
//The function merely causes the group to be drawn in the editor, allowing the user to line it up
//before attaching it. The function AttachGroup() must be called to do the actual attachment.
//Parameters: destroomp, destside - where to place the group
// g - the group to place
void PlaceGroup(room *destroomp,int destface,group *g)
{
//Clear the placed room, if one exists
Placed_room = -1;
ASSERT(destroomp->faces[destface].portal_num == -1);
//Set globals
Placed_group = g;
Placed_room_orient.fvec = destroomp->faces[destface].normal;
Placed_room_angle = 0;
Placed_baseroomp = destroomp;
Placed_baseface = destface;
//Compute attach points on each face
#ifndef NEWEDITOR
ComputeCenterPointOnFace(&Placed_room_attachpoint,destroomp,destface);
ComputeCenterPointOnFace(&Placed_room_origin,&g->rooms[g->attachroom],g->attachface);
#else
float ComputeFaceBoundingCircle(vector *center,room *rp,int facenum);
ComputeFaceBoundingCircle(&Placed_room_attachpoint,destroomp,destface);
ComputeFaceBoundingCircle(&Placed_room_origin,&g->rooms[g->attachroom],g->attachface);
#endif
//Compute initial orientation matrix
ComputePlacedRoomMatrix();
//Set the flag
State_changed = 1;
}
#include "terrain.h"
//Place the given group at the specified terrain cell
//The function merely causes the group to be drawn in the editor, allowing the user to line it up
//before attaching it. The function AttachGroup() must be called to do the actual attachment.
//Parameters: cellnum - where to place the group
// g - the group to place
void PlaceGroupTerrain(int cellnum,group *g,bool align_to_terrain)
{
//Clear the placed room, if one exists
Placed_room = -1;
//Set globals
Placed_group = g;
Placed_room_angle = 0;
Placed_baseroomp = NULL;
if (align_to_terrain)
Placed_room_orient.fvec = TerrainNormals[MAX_TERRAIN_LOD-1][cellnum].normal1;
else
Placed_room_orient.fvec = Identity_matrix.uvec;
//Compute attach point on terrain
ComputeTerrainSegmentCenter(&Placed_room_attachpoint,cellnum);
//Compute attach points on face
ComputeCenterPointOnFace(&Placed_room_origin,&g->rooms[g->attachroom],g->attachface);
//Compute initial orientation matrix
ComputePlacedRoomMatrix();
//Set the flag
State_changed = 1;
}
//Attach the already-placed group
void AttachGroup()
{
vector basecenter,attcenter;
room *baseroomp,*attroomp;
int baseface,attface;
int room_xlate[MAX_ROOMS];
int r;
ASSERT(Placed_group != NULL);
//Set some vars
baseroomp = Placed_baseroomp;
baseface = Placed_baseface;
attroomp = &Placed_group->rooms[Placed_group->attachroom];
attface = Placed_group->attachface;
attcenter = Placed_room_origin;
basecenter = Placed_room_attachpoint;
//Copy the rooms into the main room list, build the xlate table, and rotate the verts
for (r=0;r<Placed_group->nrooms;r++) {
int roomnum;
room *newroomp;
//Get a new room & copy into it
roomnum = GetFreeRoom(0);
if (roomnum == -1) {
OutrageMessageBox("Cannot attach group: Not enough free rooms.");
for (int t=0;t<r;t++) //delete the rooms we just created
DeleteRoomFromMine(&Rooms[room_xlate[t]]);
return;
}
newroomp = &Rooms[roomnum];
CopyRoom(newroomp,&Placed_group->rooms[r]);
room_xlate[r] = roomnum;
//Rotate verts in the new rooms
for (int i=0;i<newroomp->num_verts;i++)
newroomp->verts[i] = ((newroomp->verts[i] - attcenter) * Placed_room_rotmat) + basecenter;
//Recompute normals for the faces
if (! ResetRoomFaceNormals(newroomp))
Int3(); //Get Matt
}
//Add portals for new rooms
for (r=0;r<Placed_group->nrooms;r++)
for (int p=0;p<Placed_group->rooms[r].num_portals;p++) {
portal *pp = &Placed_group->rooms[r].portals[p];
if (r < pp->croom) //only link lower to higher, so we don't double link
LinkRoomsSimple(Rooms,room_xlate[r],pp->portal_face,room_xlate[pp->croom],Placed_group->rooms[pp->croom].portals[pp->cportal].portal_face);
//Copy the portal flags
int new_portal_num = Rooms[room_xlate[r]].faces[pp->portal_face].portal_num;
Rooms[room_xlate[r]].portals[new_portal_num].flags = pp->flags;
}
//Get pointer to the placed attachroom
room *newroomp = &Rooms[room_xlate[Placed_group->attachroom]];
if (baseroomp) {
//Clip the connecting faces against each other
if (!ClipFacePair(newroomp,attface,baseroomp,baseface)) {
OutrageMessageBox("Error making portal -- faces probably don't overlap.");
for (r=0;r<Placed_group->nrooms;r++)
FreeRoom(&Rooms[room_xlate[r]]);
return;
}
//Make the two faces match exactly
MatchPortalFaces(baseroomp,baseface,newroomp,attface);
//Link the attach room to the base room
LinkRoomsSimple(Rooms,ROOMNUM(baseroomp),baseface,ROOMNUM(newroomp),attface);
}
//Paste objects
if (Placed_group->nobjects) {
bool place_all = 0;
//Ask if the user wants the objects pasted
if (Placed_group->nobjects > Placed_group->ndoors)
place_all = (OutrageMessageBox (MBOX_YESNO, "The group you are pasting has %d (non-door) object(s). Do you wish to paste it/them?",Placed_group->nobjects - Placed_group->ndoors) == IDYES);
//Add the objects (either just doors or all objects)
for (int o=0;o<Placed_group->nobjects;o++) {
object *src = &Placed_group->objects[o], *dest;
if (place_all || (src->type == OBJ_DOOR)) {
vector pos;
matrix orient;
int objnum;
pos = ((src->pos - attcenter) * Placed_room_rotmat) + basecenter;
orient = ~Placed_room_rotmat * src->orient;
objnum = ObjCreate(src->type,src->id,room_xlate[src->roomnum],&pos,&orient);
#ifdef NEWEDITOR
if (IS_GENERIC(src->type))
LevelObjIncrementObject(src->id);
#endif
dest = &Objects[objnum];
dest->flags = src->flags;
dest->lifeleft = src->lifeleft;
dest->contains_type = src->contains_type;
dest->contains_id = src->contains_id;
dest->contains_count = src->contains_count;
if (dest->control_type == CT_POWERUP)
dest->ctype.powerup_info.count = src->ctype.powerup_info.count;
if (src->type == OBJ_DOOR)
dest->shields = src->shields;
}
}
}
//Paste triggers
if (Placed_group->ntriggers) {
//Ask if the user wants the triggers
if (OutrageMessageBox (MBOX_YESNO, "The group you are pasting has %d trigger(s). Do you wish to paste it/them?",Placed_group->ntriggers) == IDYES) {
//Add the triggers
for (int t=0;t<Placed_group->ntriggers;t++) {
trigger *tp = &Placed_group->triggers[t];
int trignum;
trignum = AddTrigger("",room_xlate[tp->roomnum],tp->facenum,tp->activator,NULL);
ASSERT(trignum != -1);
Triggers[trignum].flags = tp->flags;
}
}
else { //Triggers not being pasted, so must delete any floating trigger faces
for (r=0;r<Placed_group->nrooms;r++) {
room *rp = &Rooms[room_xlate[r]];
for (int f=0;f<rp->num_faces;f++) {
if (rp->faces[f].flags & FF_FLOATING_TRIG)
DeleteRoomFace(rp,f);
}
}
}
}
//Un-place the now-attached group
Placed_group = NULL;
//We're done! Update flags and leave
World_changed = 1;
}
#define GROUP_FILE_TAG "D3GP"
#define GROUP_FILE_VERSION 3
//Version number changes:
//0 -> 1 Save the level file version to use when reading objects, rooms, etc.
//1 -> 2 Now works for room engine
//2 -> 3 Save objects, doors, & triggers
#define cf_ReadVector(f,v) do {(v)->x=cf_ReadFloat(f); (v)->y=cf_ReadFloat(f); (v)->z=cf_ReadFloat(f); } while (0)
#define cf_WriteVector(f,v) do {cf_WriteFloat((f),(v)->x); cf_WriteFloat((f),(v)->y); cf_WriteFloat((f),(v)->z); } while (0)
#define cf_WriteMatrix(f,m) do {cf_WriteVector((f),&(m)->rvec); cf_WriteVector((f),&(m)->uvec); cf_WriteVector((f),&(m)->fvec); } while (0)
#define cf_ReadMatrix(f,m) do {cf_ReadVector((f),&(m)->rvec); cf_ReadVector((f),&(m)->uvec); cf_ReadVector((f),&(m)->fvec); } while (0)
//Saves an object for a group
void WriteGroupObject(CFILE *ofile,object *objp)
{
cf_WriteByte(ofile,objp->type);
cf_WriteShort(ofile,objp->id);
cf_WriteInt(ofile,objp->flags);
if (objp->type == OBJ_DOOR)
cf_WriteShort(ofile,objp->shields);
cf_WriteInt(ofile,objp->roomnum);
cf_WriteVector(ofile,&objp->pos);
cf_WriteMatrix(ofile,&objp->orient);
cf_WriteByte(ofile,objp->contains_type);
cf_WriteByte(ofile,objp->contains_id);
cf_WriteByte(ofile,objp->contains_count);
cf_WriteFloat(ofile,objp->lifeleft);
}
void WriteGroupTrigger(CFILE *ofile,trigger *tp)
{
cf_WriteShort(ofile,tp->roomnum);
cf_WriteShort(ofile,tp->facenum);
cf_WriteShort(ofile,tp->flags);
cf_WriteShort(ofile,tp->activator);
}
//Saves a group to disk in the given filename
void SaveGroup(char *filename,group *g)
{
CFILE *ifile;
int i;
ifile = cfopen(filename,"wb");
if (!ifile)
return;
//Write tag & version number
cf_WriteBytes((uint8_t *) GROUP_FILE_TAG,4,ifile);
cf_WriteInt(ifile,GROUP_FILE_VERSION);
cf_WriteInt(ifile,LEVEL_FILE_VERSION);
//Write the current list of textures
WriteTextureList(ifile);
//Write group info
cf_WriteInt(ifile,g->nrooms);
cf_WriteInt(ifile,g->attachroom);
cf_WriteInt(ifile,g->attachface);
cf_WriteInt(ifile,g->nobjects);
cf_WriteInt(ifile,g->ndoors);
cf_WriteInt(ifile,g->ntriggers);
//Write rooms
for (i=0;i<g->nrooms;i++)
WriteRoom(ifile,&g->rooms[i]);
//Write objects
for (i=0;i<g->nobjects;i++)
WriteGroupObject(ifile,&g->objects[i]);
//Write triggers
for (i=0;i<g->ntriggers;i++)
WriteGroupTrigger(ifile,&g->triggers[i]);
//Close the file
cfclose(ifile);
}
//Reads an object for a group
void ReadGroupObject(CFILE *ifile,object *objp)
{
objp->type = cf_ReadByte(ifile);
objp->id = cf_ReadShort(ifile);
objp->flags = cf_ReadInt(ifile);
if (objp->type == OBJ_DOOR)
objp->shields = cf_ReadShort(ifile);
objp->roomnum = cf_ReadInt(ifile);
cf_ReadVector(ifile,&objp->pos);
cf_ReadMatrix(ifile,&objp->orient);
objp->contains_type = cf_ReadByte(ifile);
objp->contains_id = cf_ReadByte(ifile);
objp->contains_count = cf_ReadByte(ifile);
objp->lifeleft = cf_ReadFloat(ifile);
}
void ReadGroupTrigger(CFILE *ifile,trigger *tp)
{
tp->roomnum = cf_ReadShort(ifile);
tp->facenum = cf_ReadShort(ifile);
tp->flags = cf_ReadShort(ifile);
tp->activator = cf_ReadShort(ifile);
}
//Loads a group from disk
group *LoadGroup(char *filename)
{
CFILE *ifile;
char tag[4];
int version,level_version;
group *g;
int i;
ifile = cfopen(filename,"rb");
if (!ifile)
return NULL;
//Read & check tag
cf_ReadBytes((uint8_t *) tag,4,ifile);
if (strncmp(tag,GROUP_FILE_TAG,4)) {
cfclose(ifile);
return NULL;
}
//Read & check version number
version = cf_ReadInt(ifile);
if (version > GROUP_FILE_VERSION) {
cfclose(ifile);
return NULL;
}
//Check for out-of-date groups
if (version < 2) {
OutrageMessageBox("Sorry - segment groups no longer supported.");
cfclose(ifile);
return NULL;
}
//Read & check level file version number
level_version = cf_ReadInt(ifile);
if (level_version > LEVEL_FILE_VERSION) {
cfclose(ifile);
return NULL;
}
//Read the textures & build xlate table
ReadTextureList(ifile);
//Allocate group
g = (group *) mem_malloc(sizeof(*g));
//Read group info
g->nrooms = cf_ReadInt(ifile);
g->attachroom = cf_ReadInt(ifile);
g->attachface = cf_ReadInt(ifile);
//Read object, door, & trigger info
if (version >= 3) {
g->nobjects = cf_ReadInt(ifile);
g->ndoors = cf_ReadInt(ifile);
g->ntriggers = cf_ReadInt(ifile);
}
else
g->nobjects = g->ndoors = g->ntriggers = 0;
//Allocate room and vertex arrays
g->rooms = (room *) mem_malloc(sizeof(*g->rooms) * g->nrooms);
//Read rooms
for (i=0;i<g->nrooms;i++) {
g->rooms[i].used = 0; //ReadRoom() wants the used flag to be clear
ReadRoom(ifile,&g->rooms[i],level_version);
}
//Read objects & triggers
g->triggers = NULL;
g->objects = NULL;
if (version >= 3) {
//Allocate objects array
if (g->nobjects)
g->objects = (object *) mem_malloc(sizeof(*g->objects) * g->nobjects);
//Allocate triggers array
if (g->ntriggers)
g->triggers = (trigger *) mem_malloc(sizeof(*g->triggers) * g->ntriggers);
//Read objects
for (i=0;i<g->nobjects;i++)
ReadGroupObject(ifile,&g->objects[i]);
//Read triggers
for (i=0;i<g->ntriggers;i++)
ReadGroupTrigger(ifile,&g->triggers[i]);
}
//Close the file
cfclose(ifile);
return g;
}