quakeforge/tools/Forge/Bundles/MapEdit/QuakeEd.m

1027 lines
18 KiB
Objective-C

#import "qedefs.h"
id quakeed_i;
id entclasses_i;
id g_cmd_out_i;
BOOL autodirty;
BOOL filter_light, filter_path, filter_entities;
BOOL filter_clip_brushes, filter_water_brushes, filter_world;
BOOL running;
int bsppid;
#if 0
// example command strings
char *fullviscmd = "rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2 ; /LocalApps/vis $2\"";
char *fastviscmd = "rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2 ; /LocalApps/vis -fast $2\"";
char *noviscmd = "rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2\"";
char *relightcmd = "rsh satan \"/LocalApps/light $2\"";
char *leakcmd = "rsh satan \"/LocalApps/qbsp -mark -notjunc $1 $2\"";
#endif
void NopSound (void)
{
NSBeep ();
}
UserPath *upath;
void My_Malloc_Error (int code)
{
// recursive toast Error ("Malloc error: %i\n", code);
write (1, "malloc error!\n", strlen("malloc error!\n")+1);
}
/*
===============
AutoSave
Every five minutes, save a modified map
===============
*/
void AutoSave(DPSTimedEntry tag, double now, void *userData)
{
// automatic backup
if (autodirty)
{
autodirty = NO;
[map_i writeMapFile: FN_AUTOSAVE useRegion: NO];
}
[map_i writeStats];
}
void DisplayCmdOutput (void)
{
char *buffer;
LoadFile (FN_CMDOUT, (void **)&buffer);
unlink (FN_CMDOUT);
[project_i addToOutput:buffer];
free (buffer);
if ([preferences_i getShowBSP])
[inspcontrol_i changeInspectorTo:i_output];
[preferences_i playBspSound];
NSPing ();
}
/*
===============
CheckCmdDone
See if the BSP is done
===============
*/
DPSTimedEntry cmdte;
void CheckCmdDone(DPSTimedEntry tag, double now, void *userData)
{
union wait statusp;
struct rusage rusage;
if (!wait4(bsppid, &statusp, WNOHANG, &rusage))
return;
DisplayCmdOutput ();
bsppid = 0;
DPSRemoveTimedEntry( cmdte );
}
//============================================================================
@implementation QuakeEd
/*
===============
init
===============
*/
- initContent:(const NSRect *)contentRect
style:(int)aStyle
backing:(int)backingType
buttonMask:(int)mask
defer:(BOOL)flag
{
[super initContent:contentRect
style:aStyle
backing:backingType
buttonMask:mask
defer:flag];
[self addToEventMask:
NS_RMOUSEDRAGGEDMASK|NS_LMOUSEDRAGGEDMASK];
malloc_error(My_Malloc_Error);
quakeed_i = self;
dirty = autodirty = NO;
DPSAddTimedEntry(5*60, AutoSave, self, NS_BASETHRESHOLD);
upath = newUserPath ();
return self;
}
- setDefaultFilename
{
strcpy (filename, FN_TEMPSAVE);
[self setTitleAsFilename:filename];
return self;
}
- (BOOL)dirty
{
return dirty;
}
/*
===============================================================================
DISPLAY UPDATING (handles both camera and XYView)
===============================================================================
*/
BOOL updateinflight;
BOOL clearinstance;
BOOL updatexy;
BOOL updatez;
BOOL updatecamera;
void postappdefined (void)
{
NSEvent ev;
if (updateinflight)
return;
// post an event at the end of the que
ev.type = NS_APPDEFINED;
if (DPSPostEvent(&ev, 0) == -1)
printf ("WARNING: DPSPostEvent: full\n");
//printf ("posted\n");
updateinflight = YES;
}
int c_updateall;
- updateAll // when a model has been changed
{
updatecamera = updatexy = updatez = YES;
c_updateall++;
postappdefined ();
return self;
}
- updateAll:sender
{
[self updateAll];
return self;
}
- updateCamera // when the camera has moved
{
updatecamera = YES;
clearinstance = YES;
postappdefined ();
return self;
}
- updateXY
{
updatexy = YES;
postappdefined ();
return self;
}
- updateZ
{
updatez = YES;
postappdefined ();
return self;
}
- newinstance
{
clearinstance = YES;
return self;
}
- redrawInstance
{
clearinstance = YES;
[self flushWindow];
return self;
}
/*
===============
flushWindow
instance draw the brush after each flush
===============
*/
-flushWindow
{
[super flushWindow];
if (!running || in_error)
return self; // don't lock focus before nib is finished loading
if (_flushDisabled)
return self;
[cameraview_i lockFocus];
if (clearinstance)
{
PSnewinstance ();
clearinstance = NO;
}
PSsetinstance (1);
linestart (0,0,0);
[map_i makeSelectedPerform: @selector(CameraDrawSelf)];
[clipper_i cameraDrawSelf];
lineflush ();
PSsetinstance (0);
[cameraview_i unlockFocus];
[xyview_i lockFocus];
PSsetinstance (1);
linestart (0,0,0);
[map_i makeSelectedPerform: @selector(XYDrawSelf)];
lineflush ();
[cameraview_i XYDrawSelf];
[zview_i XYDrawSelf];
[clipper_i XYDrawSelf];
PSsetinstance (0);
[xyview_i unlockFocus];
[zview_i lockFocus];
PSsetinstance (1);
[map_i makeSelectedPerform: @selector(ZDrawSelf)];
[cameraview_i ZDrawSelf];
[clipper_i ZDrawSelf];
PSsetinstance (0);
[zview_i unlockFocus];
return self;
}
/*
==============================================================================
App delegate methods
==============================================================================
*/
- applicationDefined:(NSEvent *)theEvent
{
NSEvent ev, *evp;
updateinflight = NO;
//printf ("serviced\n");
// update screen
evp = [NSApp peekNextEvent:-1 into:&ev];
if (evp)
{
postappdefined();
return self;
}
[self disableFlushWindow];
if ([map_i count] != [entitycount_i intValue])
[entitycount_i setIntValue: [map_i count]];
if ([[map_i currentEntity] count] != [brushcount_i intValue])
[brushcount_i setIntValue: [[map_i currentEntity] count]];
if (updatecamera)
[cameraview_i display];
if (updatexy)
[xyview_i display];
if (updatez)
[zview_i display];
updatecamera = updatexy = updatez = NO;
[self reenableFlushWindow];
[self flushWindow];
// NSPing ();
return self;
}
- appDidInit:sender
{
NSScreen const *screens;
int screencount;
running = YES;
g_cmd_out_i = cmd_out_i; // for qprintf
[preferences_i readDefaults];
[project_i initProject];
[xyview_i setModeRadio: xy_drawmode_i]; // because xy view is inside
// scrollview and can't be
// connected directly in IB
[self setFrameAutosaveName:"EditorWinFrame"];
[self clear: self];
// go to my second monitor
[NSApp getScreens:&screens count:&screencount];
if (screencount == 2)
[self moveTopLeftTo:0 : screens[1].screenBounds.size.height
screen:screens+1];
[self makeKeyAndOrderFront: self];
//[self doOpen: "/raid/quake/id1_/maps/amlev1.map"]; // DEBUG
[map_i newMap];
qprintf ("ready.");
//malloc_debug(-1); // DEBUG
return self;
}
- appWillTerminate:sender
{
// FIXME: save dialog if dirty
return self;
}
//===========================================================================
- textCommand: sender
{
char const *t;
t = [sender stringValue];
if (!strcmp (t, "texname"))
{
texturedef_t *td;
id b;
b = [map_i selectedBrush];
if (!b)
{
qprintf ("nothing selected");
return self;
}
td = [b texturedef];
qprintf (td->texture);
return self;
}
else
qprintf ("Unknown command\n");
return self;
}
- openProject:sender
{
[project_i openProject];
return self;
}
- clear: sender
{
[map_i newMap];
[self updateAll];
[regionbutton_i setIntValue: 0];
[self setDefaultFilename];
return self;
}
- centerCamera: sender
{
NSRect sbounds;
[[xyview_i _super_view] getBounds: &sbounds];
sbounds.origin.x += sbounds.size.width/2;
sbounds.origin.y += sbounds.size.height/2;
[cameraview_i setXYOrigin: &sbounds.origin];
[self updateAll];
return self;
}
- centerZChecker: sender
{
NSRect sbounds;
[[xyview_i _super_view] getBounds: &sbounds];
sbounds.origin.x += sbounds.size.width/2;
sbounds.origin.y += sbounds.size.height/2;
[zview_i setPoint: &sbounds.origin];
[self updateAll];
return self;
}
- changeXYLookUp: sender
{
if ([sender intValue])
{
xy_viewnormal[2] = 1;
}
else
{
xy_viewnormal[2] = -1;
}
[self updateAll];
return self;
}
/*
==============================================================================
REGION MODIFICATION
==============================================================================
*/
/*
==================
applyRegion:
==================
*/
- applyRegion: sender
{
filter_clip_brushes = [filter_clip_i intValue];
filter_water_brushes = [filter_water_i intValue];
filter_light = [filter_light_i intValue];
filter_path = [filter_path_i intValue];
filter_entities = [filter_entities_i intValue];
filter_world = [filter_world_i intValue];
if (![regionbutton_i intValue])
{
region_min[0] = region_min[1] = region_min[2] = -9999;
region_max[0] = region_max[1] = region_max[2] = 9999;
}
[map_i makeGlobalPerform: @selector(newRegion)];
[self updateAll];
return self;
}
- setBrushRegion: sender
{
id b;
// get the bounds of the current selection
if ([map_i numSelected] != 1)
{
qprintf ("must have a single brush selected");
return self;
}
b = [map_i selectedBrush];
[b getMins: region_min maxs: region_max];
[b remove];
// turn region on
[regionbutton_i setIntValue: 1];
[self applyRegion: self];
return self;
}
- setXYRegion: sender
{
NSRect bounds;
// get xy size
[[xyview_i _super_view] getBounds: &bounds];
region_min[0] = bounds.origin.x;
region_min[1] = bounds.origin.y;
region_min[2] = -99999;
region_max[0] = bounds.origin.x + bounds.size.width;
region_max[1] = bounds.origin.y + bounds.size.height;
region_max[2] = 99999;
// turn region on
[regionbutton_i setIntValue: 1];
[self applyRegion: self];
return self;
}
//
// UI querie for other objects
//
- (BOOL)showCoordinates
{
return [show_coordinates_i intValue];
}
- (BOOL)showNames
{
return [show_names_i intValue];
}
/*
==============================================================================
BSP PROCESSING
==============================================================================
*/
void ExpandCommand (char *in, char *out, char *src, char *dest)
{
while (*in)
{
if (in[0] == '$')
{
if (in[1] == '1')
{
strcpy (out, src);
out += strlen(src);
}
else if (in[1] == '2')
{
strcpy (out, dest);
out += strlen(dest);
}
in += 2;
continue;
}
*out++ = *in++;
}
*out = 0;
}
/*
=============
saveBSP
=============
*/
- saveBSP:(char *)cmdline dialog:(BOOL)wt
{
char expandedcmd[1024];
char mappath[1024];
char bsppath[1024];
int oldLightFilter;
int oldPathFilter;
char *destdir;
if (bsppid)
{
NSBeep();
return self;
}
//
// turn off the filters so all entities get saved
//
oldLightFilter = [filter_light_i intValue];
oldPathFilter = [filter_path_i intValue];
[filter_light_i setIntValue:0];
[filter_path_i setIntValue:0];
[self applyRegion: self];
if ([regionbutton_i intValue])
{
strcpy (mappath, filename);
StripExtension (mappath);
strcat (mappath, ".reg");
[map_i writeMapFile: mappath useRegion: YES];
wt = YES; // allways pop the dialog on region ops
}
else
strcpy (mappath, filename);
// save the entire thing, just in case there is a problem
[self save: self];
[filter_light_i setIntValue:oldLightFilter];
[filter_path_i setIntValue:oldPathFilter];
[self applyRegion: self];
//
// write the command to the bsp host
//
destdir = [project_i getFinalMapDirectory];
strcpy (bsppath, destdir);
strcat (bsppath, "/");
ExtractFileBase (mappath, bsppath + strlen(bsppath));
strcat (bsppath, ".bsp");
ExpandCommand (cmdline, expandedcmd, mappath, bsppath);
strcat (expandedcmd, " > ");
strcat (expandedcmd, FN_CMDOUT);
strcat (expandedcmd, "\n");
printf ("system: %s", expandedcmd);
[project_i addToOutput: "\n\n========= BUSY =========\n\n"];
[project_i addToOutput: expandedcmd];
if ([preferences_i getShowBSP])
[inspcontrol_i changeInspectorTo:i_output];
if (wt)
{
id panel;
panel = NSGetAlertPanel("BSP In Progress",expandedcmd,NULL,NULL,NULL);
[panel makeKeyAndOrderFront:NULL];
system(expandedcmd);
NSFreeAlertPanel(panel);
[self makeKeyAndOrderFront:NULL];
DisplayCmdOutput ();
}
else
{
cmdte = DPSAddTimedEntry(1, CheckCmdDone, self, NS_BASETHRESHOLD);
if (! (bsppid = fork ()) )
{
system (expandedcmd);
exit (0);
}
}
return self;
}
- BSP_Full: sender
{
[self saveBSP:[project_i getFullVisCmd] dialog: NO];
return self;
}
- BSP_FastVis: sender
{
[self saveBSP:[project_i getFastVisCmd] dialog: NO];
return self;
}
- BSP_NoVis: sender
{
[self saveBSP:[project_i getNoVisCmd] dialog: NO];
return self;
}
- BSP_relight: sender
{
[self saveBSP:[project_i getRelightCmd] dialog: NO];
return self;
}
- BSP_entities: sender
{
[self saveBSP:[project_i getEntitiesCmd] dialog: NO];
return self;
}
- BSP_stop: sender
{
if (!bsppid)
{
NSBeep();
return self;
}
kill (bsppid, 9);
CheckCmdDone (cmdte, 0, NULL);
[project_i addToOutput: "\n\n========= STOPPED =========\n\n"];
return self;
}
/*
==============
doOpen:
Called by open or the project panel
==============
*/
- doOpen: (char *)fname;
{
strcpy (filename, fname);
[map_i readMapFile:filename];
[regionbutton_i setIntValue: 0];
[self setTitleAsFilename:fname];
[self updateAll];
qprintf ("%s loaded\n", fname);
return self;
}
/*
==============
open
==============
*/
- open: sender;
{
id openpanel;
static char *suffixlist[] = {"map", 0};
openpanel = [OpenPanel new];
if ( [openpanel
runModalForDirectory: [project_i getMapDirectory]
file: ""
types: suffixlist] != NS_OKTAG)
return self;
[self doOpen: (char *)[openpanel filename]];
return self;
}
/*
==============
save:
==============
*/
- save: sender;
{
char backup[1024];
// force a name change if using tempname
if (!strcmp (filename, FN_TEMPSAVE) )
return [self saveAs: self];
dirty = autodirty = NO;
strcpy (backup, filename);
StripExtension (backup);
strcat (backup, ".bak");
rename (filename, backup); // copy old to .bak
[map_i writeMapFile: filename useRegion: NO];
return self;
}
/*
==============
saveAs
==============
*/
- saveAs: sender;
{
id panel_i;
char dir[1024];
panel_i = [SavePanel new];
ExtractFileBase (filename, dir);
[panel_i setRequiredFileType: "map"];
if ( [panel_i runModalForDirectory:[project_i getMapDirectory] file: dir] != NS_OKTAG)
return self;
strcpy (filename, [panel_i filename]);
[self setTitleAsFilename:filename];
[self save: self];
return self;
}
/*
===============================================================================
OTHER METHODS
===============================================================================
*/
//
// AJR - added this for Project info
//
- (char *)currentFilename
{
return filename;
}
- deselect: sender
{
if ([clipper_i hide]) // first click hides clipper only
return [self updateAll];
[map_i setCurrentEntity: [map_i objectAt: 0]]; // make world selected
[map_i makeSelectedPerform: @selector(deselect)];
[self updateAll];
return self;
}
/*
===============
keyDown
===============
*/
#define KEY_RIGHTARROW 0xae
#define KEY_LEFTARROW 0xac
#define KEY_UPARROW 0xad
#define KEY_DOWNARROW 0xaf
- keyDown:(NSEvent *)theEvent
{
int ch;
// function keys
switch (theEvent->data.key.keyCode)
{
case 60: // F2
[cameraview_i setDrawMode: dr_wire];
qprintf ("wire draw mode");
return self;
case 61: // F3
[cameraview_i setDrawMode: dr_flat];
qprintf ("flat draw mode");
return self;
case 62: // F4
[cameraview_i setDrawMode: dr_texture];
qprintf ("texture draw mode");
return self;
case 63: // F5
[xyview_i setDrawMode: dr_wire];
qprintf ("wire draw mode");
return self;
case 64: // F6
qprintf ("texture draw mode");
return self;
case 66: // F8
[cameraview_i homeView: self];
return self;
case 88: // F12
[map_i subtractSelection: self];
return self;
case 106: // page up
[cameraview_i upFloor: self];
return self;
case 107: // page down
[cameraview_i downFloor: self];
return self;
case 109: // end
[self deselect: self];
return self;
}
// portable things
ch = tolower(theEvent->data.key.charCode);
switch (ch)
{
case KEY_RIGHTARROW:
case KEY_LEFTARROW:
case KEY_UPARROW:
case KEY_DOWNARROW:
case 'a':
case 'z':
case 'd':
case 'c':
case '.':
case ',':
[cameraview_i _keyDown: theEvent];
break;
case 27: // escape
autodirty = dirty = YES;
[self deselect: self];
return self;
case 127: // delete
autodirty = dirty = YES;
[map_i makeSelectedPerform: @selector(remove)];
[clipper_i hide];
[self updateAll];
break;
case '/':
[clipper_i flipNormal];
[self updateAll];
break;
case 13: // enter
[clipper_i carve];
[self updateAll];
qprintf ("carved brush");
break;
case ' ':
[map_i cloneSelection: self];
break;
//
// move selection keys
//
case '2':
VectorCopy (vec3_origin, sb_translate);
sb_translate[1] = -[xyview_i gridsize];
[map_i makeSelectedPerform: @selector(translate)];
[self updateAll];
break;
case '8':
VectorCopy (vec3_origin, sb_translate);
sb_translate[1] = [xyview_i gridsize];
[map_i makeSelectedPerform: @selector(translate)];
[self updateAll];
break;
case '4':
VectorCopy (vec3_origin, sb_translate);
sb_translate[0] = -[xyview_i gridsize];
[map_i makeSelectedPerform: @selector(translate)];
[self updateAll];
break;
case '6':
VectorCopy (vec3_origin, sb_translate);
sb_translate[0] = [xyview_i gridsize];
[map_i makeSelectedPerform: @selector(translate)];
[self updateAll];
break;
case '-':
VectorCopy (vec3_origin, sb_translate);
sb_translate[2] = -[xyview_i gridsize];
[map_i makeSelectedPerform: @selector(translate)];
[self updateAll];
break;
case '+':
VectorCopy (vec3_origin, sb_translate);
sb_translate[2] = [xyview_i gridsize];
[map_i makeSelectedPerform: @selector(translate)];
[self updateAll];
break;
default:
qprintf ("undefined keypress");
NopSound ();
break;
}
return self;
}
@end