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