mirror of
https://github.com/TTimo/GtkRadiant.git
synced 2025-01-10 12:01:10 +00:00
fcefc8b9de
The following sequence of events and conditions will cause a segfault to occur: 1. Delete a misc_model 2. Undo the delete 3. Delete the same model again 4. Undo the delete A segfault will occur in CEntityMiscModel::Draw when m_model is dereferenced as it has been deleted and cleared. When cloning the misc_model entity, a new CEntityMiscModel is created and the name is set. However, CEntityMiscModel::BuildCacheRequestString relies on all epairs to be present before being able to generate the *correct* request string for the misc_model. Not doing so will cause incorrect behaviour - in this case, the reference count for the CEntityMiscModel is decremented one too many times and causes the object to be deleted. The misc_model object is then used later on after it has been freed. This commit fixes the problem by copying all epairs, before firing the OnKeyValueChanged events. This way, all epairs will be available when BuildCacheRequestString is called.
368 lines
8.5 KiB
C++
368 lines
8.5 KiB
C++
/*
|
|
Copyright (C) 1999-2007 id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "plugin.h"
|
|
#include "entity.h"
|
|
#include "entity_entitymodel.h"
|
|
#include "light.h"
|
|
|
|
int g_entityId = 1;
|
|
|
|
|
|
|
|
// internal
|
|
|
|
static void Entity_FreeEpairs( entity_t *e );
|
|
|
|
static void SetKeyValue( epair_t *&e, const char *key, const char *value );
|
|
static void DeleteKey( epair_t *&e, const char *key );
|
|
static const char *ValueForKey( epair_t *&e, const char *key );
|
|
|
|
static void Entity_OnKeyValueChanged( entity_t *e, const char* key, const char* value );
|
|
|
|
// constructor
|
|
entity_t *Entity_Alloc(){
|
|
entity_t *e;
|
|
e = (entity_t*)malloc( sizeof( *e ) );
|
|
e->entityId = g_entityId++;
|
|
VectorSet( e->origin, 0, 0, 0 );
|
|
VectorSet( e->color, 1, 1, 1 );
|
|
e->redoId = 0;
|
|
e->undoId = 0;
|
|
e->next = e->prev = NULL;
|
|
e->brushes.onext = e->brushes.oprev = &e->brushes;
|
|
e->epairs = NULL;
|
|
e->eclass = NULL;
|
|
e->model.pRender = NULL;
|
|
e->model.pSelect = NULL;
|
|
e->model.pEdit = NULL;
|
|
return e;
|
|
}
|
|
|
|
// destructor
|
|
void Entity_Free( entity_t *e ){
|
|
while ( e->brushes.onext != &e->brushes )
|
|
Brush_Free( e->brushes.onext, true );
|
|
|
|
if ( e->next ) {
|
|
e->next->prev = e->prev;
|
|
e->prev->next = e->next;
|
|
}
|
|
|
|
Entity_FreeEpairs( e );
|
|
|
|
if ( e->model.pRender ) {
|
|
e->model.pRender->DecRef();
|
|
e->model.pRender = NULL;
|
|
}
|
|
if ( e->model.pSelect ) {
|
|
e->model.pSelect->DecRef();
|
|
e->model.pSelect = NULL;
|
|
}
|
|
if ( e->model.pEdit ) {
|
|
e->model.pEdit->DecRef();
|
|
e->model.pEdit = NULL;
|
|
}
|
|
|
|
free( e );
|
|
}
|
|
|
|
// construct from entity
|
|
entity_t *Entity_Clone( entity_t *e ){
|
|
entity_t *n;
|
|
epair_t *ep;
|
|
|
|
n = Entity_Alloc();
|
|
n->eclass = e->eclass;
|
|
|
|
for ( ep = e->epairs ; ep ; ep = ep->next ){
|
|
if ( !ep->key || !ep->key[0] ) {
|
|
Sys_FPrintf( SYS_ERR, "ERROR: Entity_Clone: NULL or zero-length key\n" );
|
|
return 0;
|
|
}
|
|
|
|
SetKeyValue( n->epairs, ep->key, ep->value );
|
|
}
|
|
|
|
for ( ep = n->epairs ; ep ; ep = ep->next ){
|
|
/*!
|
|
\todo TODO broadcast this through a clean messaging API ;-)
|
|
*/
|
|
Entity_OnKeyValueChanged( n, ep->key, ep->value );
|
|
}
|
|
|
|
// copy some misc stuff as well
|
|
VectorCopy( e->origin, n->origin );
|
|
// VectorCopy( e->vRotation, n->vRotation );
|
|
// VectorCopy( e->vScale, n->vScale );
|
|
|
|
// n->bDirty = true;
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char *ValueForKey( epair_t *&e, const char *key ){
|
|
epair_t *ep;
|
|
for ( ep = e ; ep ; ep = ep->next )
|
|
{
|
|
if ( !strcmp( ep->key, key ) ) {
|
|
return ep->value;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
const char *ValueForKey( entity_t *ent, const char *key ){
|
|
return ValueForKey( ent->epairs, key );
|
|
}
|
|
|
|
void SetKeyValue( epair_t *&e, const char *key, const char *value ){
|
|
epair_t *ep;
|
|
for ( ep = e ; ep ; ep = ep->next )
|
|
{
|
|
if ( !strcmp( ep->key, key ) ) {
|
|
free( ep->value );
|
|
ep->value = (char*)malloc( strlen( value ) + 1 );
|
|
strcpy( ep->value, value );
|
|
return;
|
|
}
|
|
}
|
|
ep = (epair_t*)malloc( sizeof( *ep ) );
|
|
ep->next = e;
|
|
e = ep;
|
|
ep->key = (char*)malloc( strlen( key ) + 1 );
|
|
strcpy( ep->key, key );
|
|
ep->value = (char*)malloc( strlen( value ) + 1 );
|
|
strcpy( ep->value, value );
|
|
|
|
}
|
|
|
|
void SetKeyValue( entity_t *ent, const char *key, const char *value ){
|
|
if ( ent == NULL ) {
|
|
Sys_FPrintf( SYS_ERR, "ERROR: SetKeyValue: NULL entity \n" );
|
|
return;
|
|
}
|
|
|
|
if ( !key || !key[0] ) {
|
|
Sys_FPrintf( SYS_ERR, "ERROR: SetKeyValue: NULL or zero-length key\n" );
|
|
return;
|
|
}
|
|
|
|
SetKeyValue( ent->epairs, key, value );
|
|
/*!
|
|
\todo TODO broadcast this through a clean messaging API ;-)
|
|
*/
|
|
Entity_OnKeyValueChanged( ent, key, value );
|
|
}
|
|
|
|
void DeleteKey( epair_t *&e, const char *key ){
|
|
epair_t **ep, *next;
|
|
|
|
ep = &e;
|
|
while ( *ep )
|
|
{
|
|
next = *ep;
|
|
if ( !strcmp( next->key, key ) ) {
|
|
*ep = next->next;
|
|
free( next->key );
|
|
free( next->value );
|
|
free( next );
|
|
return;
|
|
}
|
|
ep = &next->next;
|
|
}
|
|
}
|
|
|
|
void DeleteKey( entity_t *ent, const char *key ){
|
|
DeleteKey( ent->epairs, key );
|
|
Entity_OnKeyValueChanged( ent, key, "" );
|
|
}
|
|
|
|
float FloatForKey( entity_t *ent, const char *key ){
|
|
const char *k;
|
|
|
|
k = ValueForKey( ent, key );
|
|
return (float) atof( k );
|
|
}
|
|
|
|
int IntForKey( entity_t *ent, const char *key ){
|
|
const char *k;
|
|
|
|
k = ValueForKey( ent, key );
|
|
return atoi( k );
|
|
}
|
|
|
|
void GetVectorForKey( entity_t *ent, const char *key, vec3_t vec ){
|
|
const char *k;
|
|
|
|
k = ValueForKey( ent, key );
|
|
sscanf( k, "%f %f %f", &vec[0], &vec[1], &vec[2] );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Entity_FreeEpairs
|
|
|
|
Frees the entity epairs.
|
|
===============
|
|
*/
|
|
void Entity_FreeEpairs( entity_t *e ){
|
|
epair_t *ep, *next;
|
|
|
|
for ( ep = e->epairs; ep; ep = next )
|
|
{
|
|
next = ep->next;
|
|
free( ep->key );
|
|
free( ep->value );
|
|
free( ep );
|
|
}
|
|
e->epairs = NULL;
|
|
}
|
|
|
|
void Entity_AddToList( entity_t *e, entity_t *elist ){
|
|
if ( e->next || e->prev ) {
|
|
Error( "Entity_AddToList: already linked" );
|
|
}
|
|
//e->next = elist->next;
|
|
//elist->next->prev = e;
|
|
//elist->next = e;
|
|
//e->prev = elist;
|
|
e->next = elist;
|
|
e->prev = elist->prev;
|
|
elist->prev->next = e;
|
|
elist->prev = e;
|
|
}
|
|
|
|
void Entity_RemoveFromList( entity_t *e ){
|
|
if ( !e->next || !e->prev ) {
|
|
Error( "Entity_RemoveFromList: not linked" );
|
|
}
|
|
e->next->prev = e->prev;
|
|
e->prev->next = e->next;
|
|
e->next = e->prev = NULL;
|
|
}
|
|
|
|
void Entity_LinkBrush( entity_t *e, brush_t *b ){
|
|
if ( b->oprev || b->onext ) {
|
|
Error( "Entity_LinkBrush: Already linked" );
|
|
}
|
|
b->owner = e;
|
|
|
|
// b->onext = e->brushes.onext;
|
|
// b->oprev = &e->brushes;
|
|
// e->brushes.onext->oprev = b;
|
|
// e->brushes.onext = b;
|
|
/*
|
|
SPoG - changed to add brushes to end of list instead of start - so this can be used by map loader.
|
|
This could concievably cause a problem if someone is traversing e->brushes while calling this function.
|
|
So don't.
|
|
*/
|
|
b->onext = &e->brushes;
|
|
b->oprev = e->brushes.oprev;
|
|
e->brushes.oprev->onext = b;
|
|
e->brushes.oprev = b;
|
|
}
|
|
|
|
void Entity_UnlinkBrush( brush_t *b ){
|
|
if ( !b->onext || !b->oprev ) {
|
|
Error( "Entity_UnlinkBrush: Not currently linked" );
|
|
}
|
|
b->onext->oprev = b->oprev;
|
|
b->oprev->onext = b->onext;
|
|
b->onext = b->oprev = NULL;
|
|
b->owner = NULL;
|
|
}
|
|
|
|
// for undo
|
|
int Entity_MemorySize( entity_t *e ){
|
|
epair_t *ep;
|
|
int size = 0;
|
|
|
|
for ( ep = e->epairs; ep; ep = ep->next )
|
|
{
|
|
size += strlen( ep->key );
|
|
size += strlen( ep->value );
|
|
size += sizeof( epair_t );
|
|
}
|
|
size += sizeof( entity_t );
|
|
return size;
|
|
}
|
|
|
|
epair_t* Entity_AllocateEpair( const char *key, const char *value ){
|
|
epair_t *ep = (epair_t*)malloc( sizeof( *ep ) );
|
|
ep->key = (char*)malloc( strlen( key ) + 1 );
|
|
strcpy( ep->key, key );
|
|
ep->value = (char*)malloc( strlen( value ) + 1 );
|
|
strcpy( ep->value, value );
|
|
ep->next = NULL;
|
|
return ep;
|
|
}
|
|
|
|
epair_t** Entity_GetKeyValList( entity_t *e ){
|
|
return &e->epairs;
|
|
}
|
|
|
|
void Entity_SetKeyValList( entity_t *e, epair_t* ep ){
|
|
if ( e->epairs ) {
|
|
Sys_Printf( "Warning : pe->epairs != NULL in Entity_SetKeyValList, will not set\n" );
|
|
}
|
|
else {
|
|
e->epairs = ep;
|
|
|
|
for ( epair_t *pe_ep = e->epairs; pe_ep; pe_ep = pe_ep->next )
|
|
Entity_OnKeyValueChanged( e, pe_ep->key, pe_ep->value );
|
|
}
|
|
}
|
|
|
|
|
|
/*!
|
|
\todo FIXME TTimo
|
|
this is meant to raise messages instead of calling the IEdit directly
|
|
*/
|
|
static void Entity_OnKeyValueChanged( entity_t *e, const char *key, const char* value ){
|
|
if ( strcmp( key,"classname" ) == 0 ) {
|
|
e->eclass = Eclass_ForName( value, false );
|
|
Entity_UpdateClass( e, value );
|
|
if ( strcmp( value,"light" ) == 0 ) {
|
|
for ( epair_t* ep = e->epairs; ep != NULL; ep = ep->next )
|
|
Light_OnKeyValueChanged( e, ep->key, ep->value );
|
|
}
|
|
if ( e->model.pEdit ) {
|
|
for ( epair_t* ep = e->epairs; ep != NULL; ep = ep->next )
|
|
e->model.pEdit->OnKeyValueChanged( e, ep->key, ep->value );
|
|
}
|
|
}
|
|
else if ( Entity_IsLight( e ) ) {
|
|
Light_OnKeyValueChanged( e, key, value );
|
|
}
|
|
else if ( e->model.pEdit ) {
|
|
e->model.pEdit->OnKeyValueChanged( e, key, value );
|
|
}
|
|
|
|
// update brush mins/maxs for legacy culling system
|
|
if ( e->model.pRender && e->brushes.onext != &e->brushes ) {
|
|
Brush_Build( e->brushes.onext, true, true, false, true );
|
|
}
|
|
}
|