/* * 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 . */ /* * $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 #include "door.h" //Free a group. //Parameters: g - the group to be freed void FreeGroup(group *g) { for (int r=0;rnrooms;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;rrooms[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;rcroom != -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;robjects[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;tntriggers = 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;ttriggers[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;ifaces[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;rnrooms;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;trooms[r]); room_xlate[r] = roomnum; //Rotate verts in the new rooms for (int i=0;inum_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;rnrooms;r++) for (int p=0;prooms[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;rnrooms;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;onobjects;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;tntriggers;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;rnrooms;r++) { room *rp = &Rooms[room_xlate[r]]; for (int f=0;fnum_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((ubyte *) 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;inrooms;i++) WriteRoom(ifile,&g->rooms[i]); //Write objects for (i=0;inobjects;i++) WriteGroupObject(ifile,&g->objects[i]); //Write triggers for (i=0;intriggers;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((ubyte *) 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;inrooms;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;inobjects;i++) ReadGroupObject(ifile,&g->objects[i]); //Read triggers for (i=0;intriggers;i++) ReadGroupTrigger(ifile,&g->triggers[i]); } //Close the file cfclose(ifile); return g; }