mirror of
https://github.com/kevinbentley/Descent3.git
synced 2025-01-22 19:55:23 +00:00
6f70379476
Fixed out-of-scope variables usage.
848 lines
26 KiB
C++
848 lines
26 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 (int 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;
|
|
}
|