DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

GameEdit.cpp (27693B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
     25 
     26 ===========================================================================
     27 */
     28 
     29 #include "../idlib/precompiled.h"
     30 #pragma hdrstop
     31 
     32 #include "Game_local.h"
     33 
     34 
     35 /*
     36 ===============================================================================
     37 
     38 	Ingame cursor.
     39 
     40 ===============================================================================
     41 */
     42 
     43 CLASS_DECLARATION( idEntity, idCursor3D )
     44 END_CLASS
     45 
     46 /*
     47 ===============
     48 idCursor3D::idCursor3D
     49 ===============
     50 */
     51 idCursor3D::idCursor3D() {
     52 	draggedPosition.Zero();
     53 }
     54 
     55 /*
     56 ===============
     57 idCursor3D::~idCursor3D
     58 ===============
     59 */
     60 idCursor3D::~idCursor3D() {
     61 }
     62 
     63 /*
     64 ===============
     65 idCursor3D::Spawn
     66 ===============
     67 */
     68 void idCursor3D::Spawn() {
     69 }
     70 
     71 /*
     72 ===============
     73 idCursor3D::Present
     74 ===============
     75 */
     76 void idCursor3D::Present() {
     77 	// don't present to the renderer if the entity hasn't changed
     78 	if ( !( thinkFlags & TH_UPDATEVISUALS ) ) {
     79 		return;
     80 	}
     81 	BecomeInactive( TH_UPDATEVISUALS );
     82 
     83 	const idVec3 &origin = GetPhysics()->GetOrigin();
     84 	const idMat3 &axis = GetPhysics()->GetAxis();
     85 	gameRenderWorld->DebugArrow( colorYellow, origin + axis[1] * -5.0f + axis[2] * 5.0f, origin, 2 );
     86 	gameRenderWorld->DebugArrow( colorRed, origin, draggedPosition, 2 );
     87 }
     88 
     89 /*
     90 ===============
     91 idCursor3D::Think
     92 ===============
     93 */
     94 void idCursor3D::Think() {
     95 	if ( thinkFlags & TH_THINK ) {
     96 		drag.Evaluate( gameLocal.time );
     97 	}
     98 	Present();
     99 }
    100 
    101 
    102 /*
    103 ===============================================================================
    104 
    105 	Allows entities to be dragged through the world with physics.
    106 
    107 ===============================================================================
    108 */
    109 
    110 #define MAX_DRAG_TRACE_DISTANCE			2048.0f
    111 
    112 /*
    113 ==============
    114 idDragEntity::idDragEntity
    115 ==============
    116 */
    117 idDragEntity::idDragEntity() {
    118 	cursor = NULL;
    119 	Clear();
    120 }
    121 
    122 /*
    123 ==============
    124 idDragEntity::~idDragEntity
    125 ==============
    126 */
    127 idDragEntity::~idDragEntity() {
    128 	StopDrag();
    129 	selected = NULL;
    130 	delete cursor;
    131 	cursor = NULL;
    132 }
    133 
    134 
    135 /*
    136 ==============
    137 idDragEntity::Clear
    138 ==============
    139 */
    140 void idDragEntity::Clear() {
    141 	dragEnt			= NULL;
    142 	joint			= INVALID_JOINT;
    143 	id				= 0;
    144 	localEntityPoint.Zero();
    145 	localPlayerPoint.Zero();
    146 	bodyName.Clear();
    147 	selected		= NULL;
    148 }
    149 
    150 /*
    151 ==============
    152 idDragEntity::StopDrag
    153 ==============
    154 */
    155 void idDragEntity::StopDrag() {
    156 	dragEnt = NULL;
    157 	if ( cursor ) {
    158 		cursor->BecomeInactive( TH_THINK );
    159 	}
    160 }
    161 
    162 /*
    163 ==============
    164 idDragEntity::Update
    165 ==============
    166 */
    167 void idDragEntity::Update( idPlayer *player ) {
    168 	idVec3 viewPoint, origin;
    169 	idMat3 viewAxis, axis;
    170 	trace_t trace;
    171 	idEntity *newEnt = NULL;
    172 	idAngles angles;
    173 	jointHandle_t newJoint = INVALID_JOINT;
    174 	idStr newBodyName;
    175 
    176 	player->GetViewPos( viewPoint, viewAxis );
    177 
    178 	// if no entity selected for dragging
    179     if ( !dragEnt.GetEntity() ) {
    180 
    181 		if ( player->usercmd.buttons & BUTTON_ATTACK ) {
    182 
    183 			gameLocal.clip.TracePoint( trace, viewPoint, viewPoint + viewAxis[0] * MAX_DRAG_TRACE_DISTANCE, (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_BODY), player );
    184 			if ( trace.fraction < 1.0f ) {
    185 
    186 				newEnt = gameLocal.entities[ trace.c.entityNum ];
    187 				if ( newEnt ) {
    188 
    189 					if ( newEnt->GetBindMaster() ) {
    190 						if ( newEnt->GetBindJoint() ) {
    191 							trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( newEnt->GetBindJoint() );
    192 						} else {
    193 							trace.c.id = newEnt->GetBindBody();
    194 						}
    195 						newEnt = newEnt->GetBindMaster();
    196 					}
    197 
    198 					if ( newEnt->IsType( idAFEntity_Base::Type ) && static_cast<idAFEntity_Base *>(newEnt)->IsActiveAF() ) {
    199 						idAFEntity_Base *af = static_cast<idAFEntity_Base *>(newEnt);
    200 
    201 						// joint being dragged
    202 						newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id );
    203 						// get the body id from the trace model id which might be a joint handle
    204 						trace.c.id = af->BodyForClipModelId( trace.c.id );
    205 						// get the name of the body being dragged
    206 						newBodyName = af->GetAFPhysics()->GetBody( trace.c.id )->GetName();
    207 
    208 					} else if ( !newEnt->IsType( idWorldspawn::Type ) ) {
    209 
    210 						if ( trace.c.id < 0 ) {
    211 							newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id );
    212 						} else {
    213 							newJoint = INVALID_JOINT;
    214 						}
    215 						newBodyName = "";
    216 
    217 					} else {
    218 
    219 						newJoint = INVALID_JOINT;
    220 						newEnt = NULL;
    221 					}
    222 				}
    223 				if ( newEnt ) {
    224 					dragEnt = newEnt;
    225 					selected = newEnt;
    226 					joint = newJoint;
    227 					id = trace.c.id;
    228 					bodyName = newBodyName;
    229 
    230 					if ( !cursor ) {
    231 						cursor = ( idCursor3D * )gameLocal.SpawnEntityType( idCursor3D::Type );
    232 					}
    233 
    234 					idPhysics *phys = dragEnt.GetEntity()->GetPhysics();
    235 					localPlayerPoint = ( trace.c.point - viewPoint ) * viewAxis.Transpose();
    236 					origin = phys->GetOrigin( id );
    237 					axis = phys->GetAxis( id );
    238 					localEntityPoint = ( trace.c.point - origin ) * axis.Transpose();
    239 
    240 					cursor->drag.Init( g_dragDamping.GetFloat() );
    241 					cursor->drag.SetPhysics( phys, id, localEntityPoint );
    242 					cursor->Show();
    243 
    244 					if ( phys->IsType( idPhysics_AF::Type ) ||
    245 							phys->IsType( idPhysics_RigidBody::Type ) ||
    246 								phys->IsType( idPhysics_Monster::Type ) ) {
    247 						cursor->BecomeActive( TH_THINK );
    248 					}
    249 				}
    250 			}
    251 		}
    252 	}
    253 
    254 	// if there is an entity selected for dragging
    255 	idEntity *drag = dragEnt.GetEntity();
    256 	if ( drag ) {
    257 
    258 		if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) {
    259 			StopDrag();
    260 			return;
    261 		}
    262 
    263 		cursor->SetOrigin( viewPoint + localPlayerPoint * viewAxis );
    264 		cursor->SetAxis( viewAxis );
    265 
    266 		cursor->drag.SetDragPosition( cursor->GetPhysics()->GetOrigin() );
    267 
    268 		renderEntity_t *renderEntity = drag->GetRenderEntity();
    269 		idAnimator *dragAnimator = drag->GetAnimator();
    270 
    271 		if ( joint != INVALID_JOINT && renderEntity != NULL && dragAnimator != NULL ) {
    272 			dragAnimator->GetJointTransform( joint, gameLocal.time, cursor->draggedPosition, axis );
    273 			cursor->draggedPosition = renderEntity->origin + cursor->draggedPosition * renderEntity->axis;
    274 			gameRenderWorld->DrawText( va( "%s\n%s\n%s, %s", drag->GetName(), drag->GetType()->classname, dragAnimator->GetJointName( joint ), bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 );
    275 		} else {
    276 			cursor->draggedPosition = cursor->GetPhysics()->GetOrigin();
    277 			gameRenderWorld->DrawText( va( "%s\n%s\n%s", drag->GetName(), drag->GetType()->classname, bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 );
    278 		}
    279 	}
    280 
    281 	// if there is a selected entity
    282 	if ( selected.GetEntity() && g_dragShowSelection.GetBool() ) {
    283 		// draw the bbox of the selected entity
    284 		renderEntity_t *renderEntity = selected.GetEntity()->GetRenderEntity();
    285 		if ( renderEntity ) {
    286 			gameRenderWorld->DebugBox( colorYellow, idBox( renderEntity->bounds, renderEntity->origin, renderEntity->axis ) );
    287 		}
    288 	}
    289 }
    290 
    291 /*
    292 ==============
    293 idDragEntity::SetSelected
    294 ==============
    295 */
    296 void idDragEntity::SetSelected( idEntity *ent ) {
    297 	selected = ent;
    298 	StopDrag();
    299 }
    300 
    301 /*
    302 ==============
    303 idDragEntity::DeleteSelected
    304 ==============
    305 */
    306 void idDragEntity::DeleteSelected() {
    307 	delete selected.GetEntity();
    308 	selected = NULL;
    309 	StopDrag();
    310 }
    311 
    312 /*
    313 ==============
    314 idDragEntity::BindSelected
    315 ==============
    316 */
    317 void idDragEntity::BindSelected() {
    318 	int num, largestNum;
    319 	idLexer lexer;
    320 	idToken type, bodyName;
    321 	idStr key, value, bindBodyName;
    322 	const idKeyValue *kv;
    323 	idAFEntity_Base *af;
    324 
    325 	af = static_cast<idAFEntity_Base *>(dragEnt.GetEntity());
    326 
    327 	if ( !af || !af->IsType( idAFEntity_Base::Type ) || !af->IsActiveAF() ) {
    328 		return;
    329 	}
    330 
    331 	bindBodyName = af->GetAFPhysics()->GetBody( id )->GetName();
    332 	largestNum = 1;
    333 
    334 	// parse all the bind constraints
    335 	kv = af->spawnArgs.MatchPrefix( "bindConstraint ", NULL );
    336 	while ( kv ) {
    337 		key = kv->GetKey();
    338 		key.Strip( "bindConstraint " );
    339 		if ( sscanf( key, "bind%d", &num ) ) {
    340 			if ( num >= largestNum ) {
    341 				largestNum = num + 1;
    342 			}
    343 		}
    344 
    345 		lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() );
    346 		lexer.ReadToken( &type );
    347 		lexer.ReadToken( &bodyName );
    348 		lexer.FreeSource();
    349 
    350 		// if there already exists a bind constraint for this body
    351 		if ( bodyName.Icmp( bindBodyName ) == 0 ) {
    352 			// delete the bind constraint
    353 			af->spawnArgs.Delete( kv->GetKey() );
    354 			kv = NULL;
    355 		}
    356 
    357 		kv = af->spawnArgs.MatchPrefix( "bindConstraint ", kv );
    358 	}
    359 
    360 	sprintf( key, "bindConstraint bind%d", largestNum );
    361 	sprintf( value, "ballAndSocket %s %s", bindBodyName.c_str(), af->GetAnimator()->GetJointName( joint ) );
    362 
    363 	af->spawnArgs.Set( key, value );
    364 	af->spawnArgs.Set( "bind", "worldspawn" );
    365 	af->Bind( gameLocal.world, true );
    366 }
    367 
    368 /*
    369 ==============
    370 idDragEntity::UnbindSelected
    371 ==============
    372 */
    373 void idDragEntity::UnbindSelected() {
    374 	const idKeyValue *kv;
    375 	idAFEntity_Base *af;
    376 
    377 	af = static_cast<idAFEntity_Base *>(selected.GetEntity());
    378 
    379 	if ( !af || !af->IsType( idAFEntity_Base::Type ) || !af->IsActiveAF() ) {
    380 		return;
    381 	}
    382 
    383 	// unbind the selected entity
    384 	af->Unbind();
    385 
    386 	// delete all the bind constraints
    387 	kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL );
    388 	while ( kv ) {
    389 		selected.GetEntity()->spawnArgs.Delete( kv->GetKey() );
    390 		kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL );
    391 	}
    392 
    393 	// delete any bind information
    394 	af->spawnArgs.Delete( "bind" );
    395 	af->spawnArgs.Delete( "bindToJoint" );
    396 	af->spawnArgs.Delete( "bindToBody" );
    397 }
    398 
    399 
    400 /*
    401 ===============================================================================
    402 
    403 	Handles ingame entity editing.
    404 
    405 ===============================================================================
    406 */
    407 
    408 /*
    409 ==============
    410 idEditEntities::idEditEntities
    411 ==============
    412 */
    413 idEditEntities::idEditEntities() {
    414 	selectableEntityClasses.Clear();
    415 	nextSelectTime = 0;
    416 }
    417 
    418 /*
    419 =============
    420 idEditEntities::SelectEntity
    421 =============
    422 */
    423 bool idEditEntities::SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ) {
    424 	idVec3		end;
    425 	idEntity	*ent;
    426 
    427 	if ( !g_editEntityMode.GetInteger() || selectableEntityClasses.Num() == 0 ) {
    428 		return false;
    429 	}
    430 
    431 	if ( gameLocal.time < nextSelectTime ) {
    432 		return true;
    433 	}
    434 	nextSelectTime = gameLocal.time + 300;
    435 
    436 	end = origin + dir * 4096.0f;
    437 
    438 	ent = NULL;
    439 	for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) {
    440 		ent = gameLocal.FindTraceEntity( origin, end, *selectableEntityClasses[i].typeInfo, skip );
    441 		if ( ent ) {
    442 			break;
    443 		}
    444 	}
    445 	if ( ent ) {
    446 		ClearSelectedEntities();
    447 		if ( EntityIsSelectable( ent ) ) {
    448 			AddSelectedEntity( ent );
    449 			gameLocal.Printf( "entity #%d: %s '%s'\n", ent->entityNumber, ent->GetClassname(), ent->name.c_str() );
    450 			ent->ShowEditingDialog();
    451 			return true;
    452 		}
    453 	}
    454 	return false;
    455 }
    456 
    457 /*
    458 =============
    459 idEditEntities::AddSelectedEntity
    460 =============
    461 */
    462 void idEditEntities::AddSelectedEntity(idEntity *ent) {
    463 	ent->fl.selected = true;
    464 	selectedEntities.AddUnique(ent);
    465 }
    466 
    467 /*
    468 ==============
    469 idEditEntities::RemoveSelectedEntity
    470 ==============
    471 */
    472 void idEditEntities::RemoveSelectedEntity( idEntity *ent ) {
    473     if ( selectedEntities.Find( ent ) ) {
    474 		selectedEntities.Remove( ent );
    475 	}
    476 }
    477 
    478 /*
    479 =============
    480 idEditEntities::ClearSelectedEntities
    481 =============
    482 */
    483 void idEditEntities::ClearSelectedEntities() {
    484 	int i, count;
    485 
    486 	count = selectedEntities.Num();
    487 	for ( i = 0; i < count; i++ ) {
    488 		selectedEntities[i]->fl.selected = false;
    489 	}
    490 	selectedEntities.Clear();
    491 }
    492 
    493 
    494 /*
    495 =============
    496 idEditEntities::EntityIsSelectable
    497 =============
    498 */
    499 bool idEditEntities::EntityIsSelectable( idEntity *ent, idVec4 *color, idStr *text ) {
    500 	for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) {
    501 		if ( ent->GetType() == selectableEntityClasses[i].typeInfo ) {
    502 			if ( text ) {
    503 				*text = selectableEntityClasses[i].textKey;
    504 			}
    505 			if ( color ) {
    506 				if ( ent->fl.selected ) {
    507 					*color = colorRed;
    508 				} else {
    509 					switch( i ) {
    510 					case 1 :
    511 						*color = colorYellow;
    512 						break;
    513 					case 2 :
    514 						*color = colorBlue;
    515 						break;
    516 					default:
    517 						*color = colorGreen;
    518 					}
    519 				}
    520 			}
    521 			return true;
    522 		}
    523 	}
    524 	return false;
    525 }
    526 
    527 /*
    528 =============
    529 idEditEntities::DisplayEntities
    530 =============
    531 */
    532 void idEditEntities::DisplayEntities() {
    533 	idEntity *ent;
    534 
    535 	if ( !gameLocal.GetLocalPlayer() ) {
    536 		return;
    537 	}
    538 
    539 	selectableEntityClasses.Clear();
    540 	selectedTypeInfo_t sit;
    541 
    542 	switch( g_editEntityMode.GetInteger() ) {
    543 		case 1:
    544 			sit.typeInfo = &idLight::Type;
    545 			sit.textKey = "texture";
    546 			selectableEntityClasses.Append( sit );
    547 			break;
    548 		case 2:
    549 			sit.typeInfo = &idSound::Type;
    550 			sit.textKey = "s_shader";
    551 			selectableEntityClasses.Append( sit );
    552 			sit.typeInfo = &idLight::Type;
    553 			sit.textKey = "texture";
    554 			selectableEntityClasses.Append( sit );
    555 			break;
    556 		case 3:
    557 			sit.typeInfo = &idAFEntity_Base::Type;
    558 			sit.textKey = "articulatedFigure";
    559 			selectableEntityClasses.Append( sit );
    560 			break;
    561 		case 4:
    562 			sit.typeInfo = &idFuncEmitter::Type;
    563 			sit.textKey = "model";
    564 			selectableEntityClasses.Append( sit );
    565 			break;
    566 		case 5:
    567 			sit.typeInfo = &idAI::Type;
    568 			sit.textKey = "name";
    569 			selectableEntityClasses.Append( sit );
    570 			break;
    571 		case 6:
    572 			sit.typeInfo = &idEntity::Type;
    573 			sit.textKey = "name";
    574 			selectableEntityClasses.Append( sit );
    575 			break;
    576 		case 7:
    577 			sit.typeInfo = &idEntity::Type;
    578 			sit.textKey = "model";
    579 			selectableEntityClasses.Append( sit );
    580 			break;
    581 		default:
    582 			return;
    583 	}
    584 
    585 	idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() );
    586 	idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() );
    587 	idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3();
    588 
    589 	viewBounds.ExpandSelf( 512 );
    590 	viewTextBounds.ExpandSelf( 128 );
    591 
    592 	idStr textKey;
    593 
    594 	for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
    595 
    596 		idVec4 color;
    597 
    598 		textKey = "";
    599 		if ( !EntityIsSelectable( ent, &color, &textKey ) ) {
    600 			continue;
    601 		}
    602 
    603 		bool drawArrows = false;
    604 		if ( ent->GetType() == &idAFEntity_Base::Type ) {
    605 			if ( !static_cast<idAFEntity_Base *>(ent)->IsActiveAF() ) {
    606 				continue;
    607 			}
    608 		} else if ( ent->GetType() == &idSound::Type ) {
    609 			if ( ent->fl.selected ) {
    610 				drawArrows = true;
    611 			}
    612 			const idSoundShader * ss = declManager->FindSound( ent->spawnArgs.GetString( textKey ) );
    613 			if ( ss->HasDefaultSound() || ss->base->GetState() == DS_DEFAULTED ) {
    614 				color.Set( 1.0f, 0.0f, 1.0f, 1.0f );
    615 			}
    616 		} else if ( ent->GetType() == &idFuncEmitter::Type ) {
    617 			if ( ent->fl.selected ) {
    618 				drawArrows = true;
    619 			}
    620 		}
    621 
    622 		if ( !viewBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) {
    623 			continue;
    624 		}
    625 
    626 		gameRenderWorld->DebugBounds( color, idBounds( ent->GetPhysics()->GetOrigin() ).Expand( 8 ) );
    627 		if ( drawArrows ) {
    628 			idVec3 start = ent->GetPhysics()->GetOrigin();
    629 			idVec3 end = start + idVec3( 1, 0, 0 ) * 20.0f;
    630 			gameRenderWorld->DebugArrow( colorWhite, start, end, 2 );
    631 			gameRenderWorld->DrawText( "x+", end + idVec3( 4, 0, 0 ), 0.15f, colorWhite, axis );
    632 			end = start + idVec3( 1, 0, 0 ) * -20.0f;
    633 			gameRenderWorld->DebugArrow( colorWhite, start, end, 2 );
    634 			gameRenderWorld->DrawText( "x-", end + idVec3( -4, 0, 0 ), 0.15f, colorWhite, axis );
    635 			end = start + idVec3( 0, 1, 0 ) * +20.0f;
    636 			gameRenderWorld->DebugArrow( colorGreen, start, end, 2 );
    637 			gameRenderWorld->DrawText( "y+", end + idVec3( 0, 4, 0 ), 0.15f, colorWhite, axis );
    638 			end = start + idVec3( 0, 1, 0 ) * -20.0f;
    639 			gameRenderWorld->DebugArrow( colorGreen, start, end, 2 );
    640 			gameRenderWorld->DrawText( "y-", end + idVec3( 0, -4, 0 ), 0.15f, colorWhite, axis );
    641 			end = start + idVec3( 0, 0, 1 ) * +20.0f;
    642 			gameRenderWorld->DebugArrow( colorBlue, start, end, 2 );
    643 			gameRenderWorld->DrawText( "z+", end + idVec3( 0, 0, 4 ), 0.15f, colorWhite, axis );
    644 			end = start + idVec3( 0, 0, 1 ) * -20.0f;
    645 			gameRenderWorld->DebugArrow( colorBlue, start, end, 2 );
    646 			gameRenderWorld->DrawText( "z-", end + idVec3( 0, 0, -4 ), 0.15f, colorWhite, axis );
    647 		}
    648 
    649 		if ( textKey.Length() ) {
    650 			const char *text = ent->spawnArgs.GetString( textKey );
    651 			if ( viewTextBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) {
    652 				gameRenderWorld->DrawText( text, ent->GetPhysics()->GetOrigin() + idVec3(0, 0, 12), 0.25, colorWhite, axis, 1 );
    653 			}
    654 		}
    655 	}
    656 }
    657 
    658 
    659 /*
    660 ===============================================================================
    661 
    662 	idGameEdit
    663 
    664 ===============================================================================
    665 */
    666 
    667 idGameEdit			gameEditLocal;
    668 idGameEdit *		gameEdit = &gameEditLocal;
    669 
    670 
    671 /*
    672 =============
    673 idGameEdit::GetSelectedEntities
    674 =============
    675 */
    676 int idGameEdit::GetSelectedEntities( idEntity *list[], int max ) {
    677 	int num = 0;
    678 	idEntity *ent;
    679 
    680 	for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
    681 		if ( ent->fl.selected ) {
    682 			list[num++] = ent;
    683 			if ( num >= max ) {
    684 				break;
    685 			}
    686 		}
    687 	}
    688 	return num;
    689 }
    690 
    691 /*
    692 =============
    693 idGameEdit::TriggerSelected
    694 =============
    695 */
    696 void idGameEdit::TriggerSelected() {
    697 	idEntity *ent;
    698 	for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
    699 		if ( ent->fl.selected ) {
    700 			ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() );
    701 		}
    702 	}
    703 }
    704 
    705 /*
    706 ================
    707 idGameEdit::ClearEntitySelection
    708 ================
    709 */
    710 void idGameEdit::ClearEntitySelection() {
    711 	idEntity *ent;
    712 
    713 	for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
    714 		ent->fl.selected = false;
    715 	}
    716 	gameLocal.editEntities->ClearSelectedEntities();
    717 }
    718 
    719 /*
    720 ================
    721 idGameEdit::AddSelectedEntity
    722 ================
    723 */
    724 void idGameEdit::AddSelectedEntity( idEntity *ent ) {
    725 	if ( ent ) {
    726 		gameLocal.editEntities->AddSelectedEntity( ent );
    727 	}
    728 }
    729 
    730 /*
    731 ================
    732 idGameEdit::FindEntityDefDict
    733 ================
    734 */
    735 const idDict *idGameEdit::FindEntityDefDict( const char *name, bool makeDefault ) const {
    736 	return gameLocal.FindEntityDefDict( name, makeDefault );
    737 }
    738 
    739 /*
    740 ================
    741 idGameEdit::SpawnEntityDef
    742 ================
    743 */
    744 void idGameEdit::SpawnEntityDef( const idDict &args, idEntity **ent ) {
    745 	gameLocal.SpawnEntityDef( args, ent );
    746 }
    747 
    748 /*
    749 ================
    750 idGameEdit::FindEntity
    751 ================
    752 */
    753 idEntity *idGameEdit::FindEntity( const char *name ) const {
    754 	return gameLocal.FindEntity( name ); 
    755 }
    756 
    757 /*
    758 =============
    759 idGameEdit::GetUniqueEntityName
    760 
    761 generates a unique name for a given classname
    762 =============
    763 */
    764 const char *idGameEdit::GetUniqueEntityName( const char *classname ) const {
    765 	int			id;
    766 	static char	name[1024];
    767 
    768 	// can only have MAX_GENTITIES, so if we have a spot available, we're guaranteed to find one
    769 	for( id = 0; id < MAX_GENTITIES; id++ ) {
    770 		idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id );
    771 		if ( !gameLocal.FindEntity( name ) ) {
    772 			return name;
    773 		}
    774 	}
    775 
    776 	// id == MAX_GENTITIES + 1, which can't be in use if we get here
    777 	idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id );
    778 	return name;
    779 }
    780 
    781 /*
    782 ================
    783 idGameEdit::EntityGetOrigin
    784 ================
    785 */
    786 void  idGameEdit::EntityGetOrigin( idEntity *ent, idVec3 &org ) const {
    787 	if ( ent ) {
    788 		org = ent->GetPhysics()->GetOrigin();
    789 	}
    790 }
    791 
    792 /*
    793 ================
    794 idGameEdit::EntityGetAxis
    795 ================
    796 */
    797 void idGameEdit::EntityGetAxis( idEntity *ent, idMat3 &axis ) const {
    798 	if ( ent ) {
    799 		axis = ent->GetPhysics()->GetAxis();
    800 	}
    801 }
    802 
    803 /*
    804 ================
    805 idGameEdit::EntitySetOrigin
    806 ================
    807 */
    808 void idGameEdit::EntitySetOrigin( idEntity *ent, const idVec3 &org ) {
    809 	if ( ent ) {
    810 		ent->SetOrigin( org );
    811 	}
    812 }
    813 
    814 /*
    815 ================
    816 idGameEdit::EntitySetAxis
    817 ================
    818 */
    819 void idGameEdit::EntitySetAxis( idEntity *ent, const idMat3 &axis ) {
    820 	if ( ent ) {
    821 		ent->SetAxis( axis );
    822 	}
    823 }
    824 
    825 /*
    826 ================
    827 idGameEdit::EntitySetColor
    828 ================
    829 */
    830 void idGameEdit::EntitySetColor( idEntity *ent, const idVec3 color ) {
    831 	if ( ent ) {
    832 		ent->SetColor( color );
    833 	}
    834 }
    835 
    836 /*
    837 ================
    838 idGameEdit::EntityTranslate
    839 ================
    840 */
    841 void idGameEdit::EntityTranslate( idEntity *ent, const idVec3 &org ) {
    842 	if ( ent ) {
    843 		ent->GetPhysics()->Translate( org );
    844 	}
    845 }
    846 
    847 /*
    848 ================
    849 idGameEdit::EntityGetSpawnArgs
    850 ================
    851 */
    852 const idDict *idGameEdit::EntityGetSpawnArgs( idEntity *ent ) const {
    853 	if ( ent ) {
    854 		return &ent->spawnArgs;
    855 	}
    856 	return NULL;
    857 }
    858 
    859 /*
    860 ================
    861 idGameEdit::EntityUpdateChangeableSpawnArgs
    862 ================
    863 */
    864 void idGameEdit::EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ) {
    865 	if ( ent ) {
    866 		ent->UpdateChangeableSpawnArgs( dict );
    867 	}
    868 }
    869 
    870 /*
    871 ================
    872 idGameEdit::EntityChangeSpawnArgs
    873 ================
    874 */
    875 void idGameEdit::EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ) {
    876 	if ( ent ) {
    877 		for ( int i = 0 ; i < newArgs->GetNumKeyVals () ; i ++ ) {
    878 			const idKeyValue *kv = newArgs->GetKeyVal( i );
    879 	        
    880 			if ( kv->GetValue().Length() > 0 ) {
    881 				ent->spawnArgs.Set ( kv->GetKey() ,kv->GetValue() );
    882 			} else {
    883 				ent->spawnArgs.Delete ( kv->GetKey() );
    884 			}
    885 		}
    886 	}
    887 }
    888 
    889 /*
    890 ================
    891 idGameEdit::EntityUpdateVisuals
    892 ================
    893 */
    894 void idGameEdit::EntityUpdateVisuals( idEntity *ent ) {
    895 	if ( ent ) {
    896 		ent->UpdateVisuals();
    897 	}
    898 }
    899 
    900 /*
    901 ================
    902 idGameEdit::EntitySetModel
    903 ================
    904 */
    905 void idGameEdit::EntitySetModel( idEntity *ent, const char *val ) {
    906 	if ( ent ) {
    907 		ent->spawnArgs.Set( "model", val );
    908 		ent->SetModel( val );
    909 	}
    910 }
    911 
    912 /*
    913 ================
    914 idGameEdit::EntityStopSound
    915 ================
    916 */
    917 void idGameEdit::EntityStopSound( idEntity *ent ) {
    918 	if ( ent ) {
    919 		ent->StopSound( SND_CHANNEL_ANY, false );
    920 	}
    921 }
    922 
    923 /*
    924 ================
    925 idGameEdit::EntityDelete
    926 ================
    927 */
    928 void idGameEdit::EntityDelete( idEntity *ent ) {
    929 	delete ent;
    930 }
    931 
    932 /*
    933 ================
    934 idGameEdit::PlayerIsValid
    935 ================
    936 */
    937 bool idGameEdit::PlayerIsValid() const {
    938 	return ( gameLocal.GetLocalPlayer() != NULL );
    939 }
    940 
    941 /*
    942 ================
    943 idGameEdit::PlayerGetOrigin
    944 ================
    945 */
    946 void idGameEdit::PlayerGetOrigin( idVec3 &org ) const {
    947 	org = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin();
    948 }
    949 
    950 /*
    951 ================
    952 idGameEdit::PlayerGetAxis
    953 ================
    954 */
    955 void idGameEdit::PlayerGetAxis( idMat3 &axis ) const {
    956 	axis = gameLocal.GetLocalPlayer()->GetPhysics()->GetAxis();
    957 }
    958 
    959 /*
    960 ================
    961 idGameEdit::PlayerGetViewAngles
    962 ================
    963 */
    964 void idGameEdit::PlayerGetViewAngles( idAngles &angles ) const {
    965 	angles = gameLocal.GetLocalPlayer()->viewAngles;
    966 }
    967 
    968 /*
    969 ================
    970 idGameEdit::PlayerGetEyePosition
    971 ================
    972 */
    973 void idGameEdit::PlayerGetEyePosition( idVec3 &org ) const {
    974 	org = gameLocal.GetLocalPlayer()->GetEyePosition();
    975 }
    976 
    977 
    978 /*
    979 ================
    980 idGameEdit::MapGetEntityDict
    981 ================
    982 */
    983 const idDict *idGameEdit::MapGetEntityDict( const char *name ) const {
    984 	idMapFile *mapFile = gameLocal.GetLevelMap();
    985 	if ( mapFile && name && *name ) {
    986 		idMapEntity *mapent = mapFile->FindEntity( name );
    987 		if ( mapent ) {
    988 			return &mapent->epairs;
    989 		}
    990 	}
    991 	return NULL;
    992 }
    993 
    994 /*
    995 ================
    996 idGameEdit::MapSave
    997 ================
    998 */
    999 void idGameEdit::MapSave( const char *path ) const {
   1000 	idMapFile *mapFile = gameLocal.GetLevelMap();
   1001 	if (mapFile) {
   1002 		mapFile->Write( (path) ? path : mapFile->GetName(), ".map");
   1003 	}
   1004 }
   1005 
   1006 /*
   1007 ================
   1008 idGameEdit::MapSetEntityKeyVal
   1009 ================
   1010 */
   1011 void idGameEdit::MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const {
   1012 	idMapFile *mapFile = gameLocal.GetLevelMap();
   1013 	if ( mapFile && name && *name ) {
   1014 		idMapEntity *mapent = mapFile->FindEntity( name );
   1015 		if ( mapent ) {
   1016 			mapent->epairs.Set( key, val );
   1017 		}
   1018 	}
   1019 }
   1020 
   1021 /*
   1022 ================
   1023 idGameEdit::MapCopyDictToEntity
   1024 ================
   1025 */
   1026 void idGameEdit::MapCopyDictToEntity( const char *name, const idDict *dict ) const {
   1027 	idMapFile *mapFile = gameLocal.GetLevelMap();
   1028 	if ( mapFile && name && *name ) {
   1029 		idMapEntity *mapent = mapFile->FindEntity( name );
   1030 		if ( mapent ) {
   1031 			for ( int i = 0; i < dict->GetNumKeyVals(); i++ ) {
   1032 				const idKeyValue *kv = dict->GetKeyVal( i );
   1033 				const char *key = kv->GetKey();
   1034 				const char *val = kv->GetValue();
   1035 				mapent->epairs.Set( key, val );
   1036 			}
   1037 		}
   1038 	}
   1039 }
   1040 
   1041 
   1042 
   1043 /*
   1044 ================
   1045 idGameEdit::MapGetUniqueMatchingKeyVals
   1046 ================
   1047 */
   1048 int idGameEdit::MapGetUniqueMatchingKeyVals( const char *key, const char *list[], int max ) const {
   1049 	idMapFile *mapFile = gameLocal.GetLevelMap();
   1050 	int count = 0;
   1051 	if ( mapFile ) {
   1052 		for ( int i = 0; i < mapFile->GetNumEntities(); i++ ) {
   1053 			idMapEntity *ent = mapFile->GetEntity( i );
   1054 			if ( ent ) {
   1055 				const char *k = ent->epairs.GetString( key );
   1056 				if ( k != NULL && *k != NULL && count < max ) {
   1057 					list[count++] = k;
   1058 				}
   1059 			}
   1060 		}
   1061 	}
   1062 	return count;
   1063 }
   1064 
   1065 /*
   1066 ================
   1067 idGameEdit::MapAddEntity
   1068 ================
   1069 */
   1070 void idGameEdit::MapAddEntity( const idDict *dict ) const {
   1071 	idMapFile *mapFile = gameLocal.GetLevelMap();
   1072 	if ( mapFile ) {
   1073 		idMapEntity *ent = new (TAG_GAME) idMapEntity();
   1074 		ent->epairs = *dict;
   1075 		mapFile->AddEntity( ent );
   1076 	}
   1077 }
   1078 
   1079 /*
   1080 ================
   1081 idGameEdit::MapRemoveEntity
   1082 ================
   1083 */
   1084 void idGameEdit::MapRemoveEntity( const char *name ) const {
   1085 	idMapFile *mapFile = gameLocal.GetLevelMap();
   1086 	if ( mapFile ) {
   1087 		idMapEntity *ent = mapFile->FindEntity( name );
   1088 		if ( ent ) {
   1089 			mapFile->RemoveEntity( ent );
   1090 		}
   1091 	}
   1092 }
   1093 
   1094 
   1095 /*
   1096 ================
   1097 idGameEdit::MapGetEntitiesMatchignClassWithString
   1098 ================
   1099 */
   1100 int idGameEdit::MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const {
   1101 	idMapFile *mapFile = gameLocal.GetLevelMap();
   1102 	int count = 0;
   1103 	if ( mapFile ) {
   1104 		int entCount = mapFile->GetNumEntities();
   1105 		for ( int i = 0 ; i < entCount; i++ ) {
   1106 			idMapEntity *ent = mapFile->GetEntity(i);
   1107 			if (ent) {
   1108 				idStr work = ent->epairs.GetString("classname");
   1109 				if ( work.Icmp( classname ) == 0 ) {
   1110 					if ( match && *match ) { 
   1111 						work = ent->epairs.GetString( "soundgroup" );
   1112 						if ( count < max && work.Icmp( match ) == 0 ) {
   1113 							list[count++] = ent->epairs.GetString( "name" );
   1114 						}
   1115 					} else if ( count < max ) {
   1116 						list[count++] = ent->epairs.GetString( "name" );
   1117 					}
   1118 				}
   1119 			}
   1120 		}
   1121 	}
   1122 	return count;
   1123 }
   1124 
   1125 
   1126 /*
   1127 ================
   1128 idGameEdit::MapEntityTranslate
   1129 ================
   1130 */
   1131 void idGameEdit::MapEntityTranslate( const char *name, const idVec3 &v ) const {
   1132 	idMapFile *mapFile = gameLocal.GetLevelMap();
   1133 	if ( mapFile && name && *name ) {
   1134 		idMapEntity *mapent = mapFile->FindEntity( name );
   1135 		if ( mapent ) {
   1136 			idVec3 origin;
   1137 			mapent->epairs.GetVector( "origin", "", origin );
   1138 			origin += v;
   1139 			mapent->epairs.SetVector( "origin", origin );
   1140 		}
   1141 	}
   1142 }