Line data Source code
1 0 : /*
2 :
3 : OOJSMission.m
4 :
5 :
6 : Oolite
7 : Copyright (C) 2004-2013 Giles C Williams and contributors
8 :
9 : This program is free software; you can redistribute it and/or
10 : modify it under the terms of the GNU General Public License
11 : as published by the Free Software Foundation; either version 2
12 : of the License, or (at your option) any later version.
13 :
14 : This program 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 this program; if not, write to the Free Software
21 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 : MA 02110-1301, USA.
23 :
24 : */
25 :
26 : #import "OOJSMission.h"
27 : #import "OOJavaScriptEngine.h"
28 : #import "OOJSScript.h"
29 : #import "OOConstToJSString.h"
30 : #import "OOJSVector.h"
31 :
32 : #import "OOJSPlayer.h"
33 : #import "PlayerEntityScriptMethods.h"
34 : #import "OOStringExpander.h"
35 : #import "OOCollectionExtractors.h"
36 : #import "OOMusicController.h"
37 : #import "GuiDisplayGen.h"
38 : #import "OODebugStandards.h"
39 :
40 : static JSBool MissionGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value);
41 : static JSBool MissionSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value);
42 :
43 : static JSBool MissionMarkSystem(JSContext *context, uintN argc, jsval *vp);
44 : static JSBool MissionUnmarkSystem(JSContext *context, uintN argc, jsval *vp);
45 : static JSBool MissionAddMessageText(JSContext *context, uintN argc, jsval *vp);
46 : static JSBool MissionSetInstructions(JSContext *context, uintN argc, jsval *vp);
47 : static JSBool MissionSetInstructionsKey(JSContext *context, uintN argc, jsval *vp);
48 : static JSBool MissionRunScreen(JSContext *context, uintN argc, jsval *vp);
49 : static JSBool MissionRunShipLibrary(JSContext *context, uintN argc, jsval *vp);
50 :
51 : static JSBool MissionSetInstructionsInternal(JSContext *context, uintN argc, jsval *vp, BOOL isKey);
52 :
53 : // Mission screen callback varibables
54 0 : static jsval sCallbackFunction;
55 0 : static jsval sCallbackThis;
56 0 : static OOJSScript *sCallbackScript = nil;
57 :
58 0 : static JSObject *sMissionObject;
59 :
60 0 : static JSClass sMissionClass =
61 : {
62 : "Mission",
63 : 0,
64 :
65 : JS_PropertyStub,
66 : JS_PropertyStub,
67 : MissionGetProperty,
68 : MissionSetProperty,
69 : JS_EnumerateStub,
70 : JS_ResolveStub,
71 : JS_ConvertStub,
72 : JS_FinalizeStub
73 : };
74 :
75 :
76 0 : enum
77 : {
78 : kMission_markedSystems,
79 : kMission_screenID,
80 : kMission_exitScreen
81 : };
82 :
83 :
84 0 : static JSPropertySpec sMissionProperties[] =
85 : {
86 : // JS name ID flags
87 : { "markedSystems", kMission_markedSystems, OOJS_PROP_READONLY_CB },
88 : { "screenID", kMission_screenID, OOJS_PROP_READONLY_CB },
89 : { "exitScreen", kMission_exitScreen, OOJS_PROP_READWRITE_CB },
90 : { 0 }
91 : };
92 :
93 :
94 0 : static JSFunctionSpec sMissionMethods[] =
95 : {
96 : // JS name Function min args
97 : { "addMessageText", MissionAddMessageText, 1 },
98 : { "markSystem", MissionMarkSystem, 1 },
99 : { "runScreen", MissionRunScreen, 1 }, // the callback function is optional!
100 : { "setInstructions", MissionSetInstructions, 1 },
101 : { "setInstructionsKey", MissionSetInstructionsKey, 1 },
102 : { "unmarkSystem", MissionUnmarkSystem, 1 },
103 : { "runShipLibrary", MissionRunShipLibrary, 0 },
104 : { 0 }
105 : };
106 :
107 :
108 0 : void InitOOJSMission(JSContext *context, JSObject *global)
109 : {
110 : sCallbackFunction = JSVAL_NULL;
111 : sCallbackThis = JSVAL_NULL;
112 :
113 : JSObject *missionPrototype = JS_InitClass(context, global, NULL, &sMissionClass, OOJSUnconstructableConstruct, 0, sMissionProperties, sMissionMethods, NULL, NULL);
114 : sMissionObject = JS_DefineObject(context, global, "mission", &sMissionClass, missionPrototype, OOJS_PROP_READONLY);
115 :
116 : // Ensure JS objects are rooted.
117 : OOJSAddGCValueRoot(context, &sCallbackFunction, "Pending mission callback function");
118 : OOJSAddGCValueRoot(context, &sCallbackThis, "Pending mission callback this");
119 : }
120 :
121 :
122 0 : void MissionRunCallback()
123 : {
124 : // don't do anything if we don't have a function.
125 : if (JSVAL_IS_NULL(sCallbackFunction) || JSVAL_IS_VOID(sCallbackFunction)) return;
126 :
127 : jsval argval = JSVAL_VOID;
128 : jsval argval2 = JSVAL_VOID;
129 : jsval rval = JSVAL_VOID;
130 : PlayerEntity *player = OOPlayerForScripting();
131 : OOJavaScriptEngine *engine = [OOJavaScriptEngine sharedEngine];
132 : JSContext *context = OOJSAcquireContext();
133 :
134 : /* Create temporarily-rooted local copies of sCallbackFunction and
135 : sCallbackThis, then clear the statics. This must be done in advance
136 : since the callback might call runScreen() and clobber the statics.
137 : */
138 : jsval cbFunction = JSVAL_VOID;
139 : JSObject *cbThis = NULL;
140 : OOJSScript *cbScript = sCallbackScript;
141 :
142 : OOJSAddGCValueRoot(context, &cbFunction, "Mission callback function");
143 : OOJSAddGCObjectRoot(context, &cbThis, "Mission callback this");
144 : cbFunction = sCallbackFunction;
145 : cbScript = sCallbackScript;
146 : JS_ValueToObject(context, sCallbackThis, &cbThis);
147 :
148 : sCallbackScript = nil;
149 : sCallbackFunction = JSVAL_NULL;
150 : sCallbackThis = JSVAL_NULL;
151 :
152 : jsval args[2];
153 : argval = OOJSValueFromNativeObject(context, [player missionChoice_string]);
154 : argval2 = OOJSValueFromNativeObject(context, [player missionKeyPress_string]);
155 : args[0] = argval;
156 : args[1] = argval2;
157 :
158 : // now reset the mission choice silently, before calling the callback script.
159 : [player setMissionChoice:nil keyPress:@"" withEvent:NO];
160 :
161 : // Call the callback.
162 : @try
163 : {
164 : [OOJSScript pushScript:cbScript];
165 : [engine callJSFunction:cbFunction
166 : forObject:cbThis
167 : argc:2
168 : argv:args
169 : result:&rval];
170 : }
171 : @catch (NSException *exception)
172 : {
173 : // Squash any exception, allow cleanup to happen and so forth.
174 : OOLog(kOOLogException, @"Ignoring exception %@:%@ during handling of mission screen completion callback.", [exception name], [exception reason]);
175 : }
176 : [OOJSScript popScript:cbScript];
177 :
178 : // Manage that memory.
179 : [cbScript release];
180 : JS_RemoveValueRoot(context, &cbFunction);
181 : JS_RemoveObjectRoot(context, &cbThis);
182 :
183 : OOJSRelinquishContext(context);
184 : }
185 :
186 :
187 0 : static JSBool MissionGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value)
188 : {
189 : if (!JSID_IS_INT(propID)) return YES;
190 :
191 : OOJS_NATIVE_ENTER(context)
192 :
193 : id result = nil;
194 : PlayerEntity *player = OOPlayerForScripting();
195 :
196 : switch (JSID_TO_INT(propID))
197 : {
198 : case kMission_markedSystems:
199 : result = [player getMissionDestinations];
200 : if (result == nil) result = [NSDictionary dictionary];
201 : result = [result allValues];
202 : break;
203 :
204 : case kMission_screenID:
205 : result = [player missionScreenID];
206 : break;
207 :
208 : case kMission_exitScreen:
209 : *value = OOJSValueFromGUIScreenID(context, [player missionExitScreen]);
210 : return YES;
211 :
212 : default:
213 : OOJSReportBadPropertySelector(context, this, propID, sMissionProperties);
214 : return NO;
215 : }
216 :
217 : *value = OOJSValueFromNativeObject(context, result);
218 : return YES;
219 :
220 : OOJS_NATIVE_EXIT
221 : }
222 :
223 :
224 0 : static JSBool MissionSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value)
225 : {
226 : if (!JSID_IS_INT(propID)) return YES;
227 :
228 : OOJS_NATIVE_ENTER(context)
229 :
230 : OOGUIScreenID exitScreen;
231 : PlayerEntity *player = OOPlayerForScripting();
232 :
233 : switch (JSID_TO_INT(propID))
234 : {
235 : case kMission_exitScreen:
236 : exitScreen = OOGUIScreenIDFromJSValue(context, *value);
237 : [player setMissionExitScreen:exitScreen];
238 : return YES;
239 :
240 : default:
241 : OOJSReportBadPropertySelector(context, this, propID, sMissionProperties);
242 : }
243 :
244 : OOJSReportBadPropertyValue(context, this, propID, sMissionProperties, *value);
245 : return NO;
246 :
247 : OOJS_NATIVE_EXIT
248 : }
249 :
250 :
251 :
252 : // *** Methods ***
253 :
254 : // markSystem(integer+)
255 0 : static JSBool MissionMarkSystem(JSContext *context, uintN argc, jsval *vp)
256 : {
257 : OOJS_NATIVE_ENTER(context)
258 :
259 : PlayerEntity *player = OOPlayerForScripting();
260 : unsigned i;
261 : int dest;
262 :
263 : // two pass. Once to validate, once to apply if they validate
264 : for (i=0;i<argc;i++)
265 : {
266 : if (!JS_ValueToInt32(context, OOJS_ARGV[i], &dest))
267 : {
268 : JS_ClearPendingException(context); // or JS_ValueToInt32 exception crashes JS engine
269 : if (!JSVAL_IS_OBJECT(OOJS_ARGV[i]))
270 : {
271 : OOJSReportBadArguments(context, @"Mission", @"markSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"numbers or objects");
272 : return NO;
273 : }
274 : }
275 : }
276 :
277 : for (i=0;i<argc;i++)
278 : {
279 : if (JS_ValueToInt32(context, OOJS_ARGV[i], &dest))
280 : {
281 : OOStandardsDeprecated(@"Use of numbers for mission.markSystem is deprecated");
282 : if (!OOEnforceStandards())
283 : {
284 : [player addMissionDestinationMarker:[player defaultMarker:dest]];
285 : }
286 : }
287 : else // must be object, from above
288 : {
289 : JS_ClearPendingException(context); // or JS_ValueToInt32 exception crashes JS engine
290 : NSDictionary *marker = OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(OOJS_ARGV[i]));
291 : OOSystemID system = [marker oo_intForKey:@"system" defaultValue:-1];
292 : if (system >= 0)
293 : {
294 : [player addMissionDestinationMarker:marker];
295 : }
296 : }
297 : }
298 : OOJS_RETURN_VOID;
299 :
300 : OOJS_NATIVE_EXIT
301 : }
302 :
303 :
304 : // unmarkSystem(integer+)
305 0 : static JSBool MissionUnmarkSystem(JSContext *context, uintN argc, jsval *vp)
306 : {
307 : OOJS_NATIVE_ENTER(context)
308 :
309 : PlayerEntity *player = OOPlayerForScripting();
310 : unsigned i;
311 : int dest;
312 :
313 : // two pass. Once to validate, once to apply if they validate
314 : for (i=0;i<argc;i++)
315 : {
316 : if (!JS_ValueToInt32(context, OOJS_ARGV[i], &dest))
317 : {
318 : JS_ClearPendingException(context); // or JS_ValueToInt32 exception crashes JS engine
319 : if (!JSVAL_IS_OBJECT(OOJS_ARGV[i]))
320 : {
321 : OOJSReportBadArguments(context, @"Mission", @"unmarkSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"numbers or objects");
322 : return NO;
323 : }
324 : }
325 : }
326 :
327 : BOOL result = YES;
328 : for (i=0;i<argc;i++)
329 : {
330 : if (JS_ValueToInt32(context, OOJS_ARGV[i], &dest))
331 : {
332 : OOStandardsDeprecated(@"Use of numbers for mission.unmarkSystem is deprecated");
333 : if (!OOEnforceStandards())
334 : {
335 : if (![player removeMissionDestinationMarker:[player defaultMarker:dest]]) {
336 : result = NO;
337 : }
338 : }
339 : }
340 : else // must be object, from above
341 : {
342 : JS_ClearPendingException(context); // or JS_ValueToInt32 exception crashes JS engine
343 : NSDictionary *marker = OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(OOJS_ARGV[i]));
344 : OOSystemID system = [marker oo_intForKey:@"system" defaultValue:-1];
345 : if (system >= 0)
346 : {
347 : if (![player removeMissionDestinationMarker:marker]) {
348 : result = NO;
349 : }
350 : }
351 : }
352 : }
353 :
354 : OOJS_RETURN_BOOL(result);
355 :
356 : OOJS_NATIVE_EXIT
357 : }
358 :
359 :
360 : // addMessageText(text : String)
361 0 : static JSBool MissionAddMessageText(JSContext *context, uintN argc, jsval *vp)
362 : {
363 : OOJS_NATIVE_ENTER(context)
364 :
365 : PlayerEntity *player = OOPlayerForScripting();
366 : NSString *text = nil;
367 :
368 : if (EXPECT_NOT(argc == 0))
369 : {
370 : OOJS_RETURN_VOID;
371 : }
372 :
373 : // Found "FIXME: warning if no mission screen running.",,,
374 : // However: used routinely by the Constrictor mission in F7, without mission screens.
375 : text = OOStringFromJSValue(context, OOJS_ARGV[0]);
376 : [player addLiteralMissionText:text];
377 :
378 : OOJS_RETURN_VOID;
379 :
380 : OOJS_NATIVE_EXIT
381 : }
382 :
383 :
384 : // setInstructionsKey(instructionsKey: String [, missionKey : String])
385 0 : static JSBool MissionSetInstructionsKey(JSContext *context, uintN argc, jsval *vp)
386 : {
387 : return MissionSetInstructionsInternal(context, argc, vp, YES);
388 : }
389 :
390 :
391 : // setInstructions(instructions: String [, missionKey : String])
392 0 : static JSBool MissionSetInstructions(JSContext *context, uintN argc, jsval *vp)
393 : {
394 : return MissionSetInstructionsInternal(context, argc, vp, NO);
395 : }
396 :
397 :
398 0 : static JSBool MissionSetInstructionsInternal(JSContext *context, uintN argc, jsval *vp, BOOL isKey)
399 : {
400 : OOJS_NATIVE_ENTER(context)
401 :
402 : PlayerEntity *player = OOPlayerForScripting();
403 : NSString *text = nil;
404 : NSArray *texts = nil;
405 : NSString *missionKey = nil;
406 :
407 : if (EXPECT_NOT(argc == 0))
408 : {
409 : OOJSReportWarning(context, @"Usage error: mission.%@() called with no arguments. Treating as Mission.%@(null). This call may fail in a future version of Oolite.", isKey ? @"setInstructionsKey" : @"setInstructions", isKey ? @"setInstructionsKey" : @"setInstructions");
410 : }
411 : else if (EXPECT_NOT(JSVAL_IS_VOID(OOJS_ARGV[0])))
412 : {
413 : OOJSReportBadArguments(context, @"Mission", isKey ? @"setInstructionsKey" : @"setInstructions", 1, OOJS_ARGV, NULL, @"string or null");
414 : return NO;
415 : }
416 : else if (!JSVAL_IS_NULL(OOJS_ARGV[0]) && JSVAL_IS_OBJECT(OOJS_ARGV[0]))
417 : {
418 : texts = OOJSNativeObjectFromJSValue(context, OOJS_ARGV[0]);
419 : }
420 : else
421 : {
422 : text = OOStringFromJSValue(context, OOJS_ARGV[0]);
423 : }
424 :
425 : if (argc > 1)
426 : {
427 : missionKey = OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[1]);
428 : }
429 : else
430 : {
431 : missionKey = [[OOJSScript currentlyRunningScript] name];
432 : }
433 :
434 : if (text != nil)
435 : {
436 : if (isKey)
437 : {
438 : [player setMissionDescription:text forMission:missionKey];
439 : }
440 : else
441 : {
442 : [player setMissionInstructions:text forMission:missionKey];
443 : }
444 : }
445 : else if (texts != nil && !isKey)
446 : {
447 : [player setMissionInstructionsList:texts forMission:missionKey];
448 : }
449 : else
450 : {
451 : [player clearMissionDescriptionForMission:missionKey];
452 : }
453 :
454 : OOJS_RETURN_VOID;
455 :
456 : OOJS_NATIVE_EXIT
457 : }
458 :
459 :
460 0 : static NSDictionary *GetParameterDictionary(JSContext *context, JSObject *object, const char *key)
461 : {
462 : jsval value = JSVAL_NULL;
463 : if (JS_GetProperty(context, object, key, &value))
464 : {
465 : if (JSVAL_IS_OBJECT(value))
466 : {
467 : return OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(value));
468 : }
469 : }
470 : return nil;
471 : }
472 :
473 :
474 0 : static NSString *GetParameterString(JSContext *context, JSObject *object, const char *key)
475 : {
476 : jsval value = JSVAL_NULL;
477 : if (JS_GetProperty(context, object, key, &value))
478 : {
479 : return OOStringFromJSValue(context, value);
480 : }
481 : return nil;
482 : }
483 :
484 :
485 0 : static NSDictionary *GetParameterImageDescriptor(JSContext *context, JSObject *object, const char *key)
486 : {
487 : jsval value = JSVAL_NULL;
488 : if (JS_GetProperty(context, object, key, &value))
489 : {
490 : return [[UNIVERSE gui] textureDescriptorFromJSValue:value inContext:context callerDescription:@"mission.runScreen()"];
491 : }
492 : else
493 : {
494 : return nil;
495 : }
496 : }
497 :
498 :
499 : // runScreen(params: dict, callBack:function) - if the callback function is null, emulate the old style runMissionScreen
500 0 : static JSBool MissionRunScreen(JSContext *context, uintN argc, jsval *vp)
501 : {
502 : OOJS_NATIVE_ENTER(context)
503 :
504 : PlayerEntity *player = OOPlayerForScripting();
505 : jsval function = JSVAL_NULL;
506 : jsval value = JSVAL_NULL;
507 : JSObject *params = NULL;
508 :
509 : // No mission screens during intro.
510 : if ([player status] == STATUS_START_GAME)
511 : {
512 : // (though no JS should be loaded at this stage, so this
513 : // check may be obsolete - CIM)
514 : OOJS_RETURN_BOOL(NO);
515 : }
516 :
517 : // Validate arguments.
518 : if (argc < 1 || !JS_ValueToObject(context, OOJS_ARGV[0], ¶ms))
519 : {
520 : OOJSReportBadArguments(context, @"mission", @"runScreen", MIN(argc, 1U), &OOJS_ARGV[0], nil, @"parameter object");
521 : return NO;
522 : }
523 :
524 : if (argc > 1) function = OOJS_ARGV[1];
525 : if (!JSVAL_IS_NULL(function) && !OOJSValueIsFunction(context, function))
526 : {
527 : OOJSReportBadArguments(context, @"mission", @"runScreen", 1, &OOJS_ARGV[1], nil, @"function");
528 : return NO;
529 : }
530 :
531 : // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused.
532 : OOJSPauseTimeLimiter();
533 :
534 : if (!JSVAL_IS_NULL(function))
535 : {
536 :
537 : /* CIM 30/12/12: This following line causes problems in certain
538 : * cases, but has to be kept for backward
539 : * compatibility. Documenting the third argument of
540 : * mission.runScreen will at least help people get around it in
541 : * multi-world-script mission screens. (Though, since no-one has
542 : * complained yet, perhaps I'm the only one who uses them?) */
543 :
544 : sCallbackScript = [[[OOJSScript currentlyRunningScript] weakRefUnderlyingObject] retain];
545 : if (argc > 2)
546 : {
547 : sCallbackThis = OOJS_ARGV[2];
548 : }
549 : else
550 : {
551 : sCallbackThis = OOJSValueFromNativeObject(context, sCallbackScript);
552 : }
553 : }
554 :
555 : // Apply settings.
556 : if (JS_GetProperty(context, params, "title", &value) && !JSVAL_IS_VOID(value))
557 : {
558 : [player setMissionTitle:OOStringFromJSValue(context, value)];
559 : }
560 : else
561 : {
562 : NSString *titleKey = GetParameterString(context, params, "titleKey");
563 : if (titleKey != nil)
564 : {
565 : NSString *message = [[UNIVERSE missiontext] oo_stringForKey:titleKey];
566 : if (message != nil)
567 : {
568 : [player setMissionTitle:OOExpand(message)];
569 : }
570 : else
571 : {
572 : OOJSReportWarning(context, @"Mission.runScreen: titleKey '%@' has no entry in missiontext.plist.", titleKey);
573 : }
574 : }
575 : }
576 :
577 : [[OOMusicController sharedController] setMissionMusic:GetParameterString(context, params, "music")];
578 : [player setMissionOverlayDescriptor:GetParameterImageDescriptor(context, params, "overlay")];
579 : [player setMissionBackgroundDescriptor:GetParameterImageDescriptor(context, params, "background")];
580 : [player setMissionBackgroundSpecial:GetParameterString(context, params, "backgroundSpecial")];
581 :
582 : if (JS_GetProperty(context, params, "customChartZoom", &value) && !JSVAL_IS_VOID(value))
583 : {
584 : jsdouble zoom;
585 : if (JS_ValueToNumber(context, value, &zoom))
586 : {
587 : if (zoom >= 1 && zoom <= CHART_MAX_ZOOM)
588 : {
589 : [player setCustomChartZoom:zoom];
590 : }
591 : else
592 : {
593 : OOJSReportWarning(context, @"Mission.runScreen: invalid customChartZoom value specified.");
594 : [player setCustomChartZoom:1];
595 : }
596 : }
597 : }
598 : if (JS_GetProperty(context, params, "customChartCentre", &value) && !JSVAL_IS_VOID(value))
599 : {
600 : Vector vValue;
601 : if (JSValueToVector(context, value, &vValue))
602 : {
603 : NSPoint coords = { vValue.x, vValue.y };
604 : [player setCustomChartCentre:coords];
605 : }
606 : else
607 : {
608 : [player setCustomChartCentre:[player galaxy_coordinates]];
609 : OOJSReportWarning(context, @"Mission.runScreen: invalid value for customChartCentre. Must be valid vector. Defaulting to current location.");
610 : }
611 : }
612 : if (JS_GetProperty(context, params, "customChartCentreInLY", &value) && !JSVAL_IS_VOID(value))
613 : {
614 : Vector vValue;
615 : if (JSValueToVector(context, value, &vValue))
616 : {
617 : NSPoint coords = OOInternalCoordinatesFromGalactic(vValue);
618 : [player setCustomChartCentre:coords];
619 : }
620 : else
621 : {
622 : [player setCustomChartCentre:[player galaxy_coordinates]];
623 : OOJSReportWarning(context, @"Mission.runScreen: invalid value for customChartCentreInLY. Must be valid vector. Defaulting to current location.");
624 : }
625 : }
626 :
627 : [UNIVERSE removeDemoShips]; // remove any demoship or miniature planet that may be remaining from previous screens
628 :
629 : ShipEntity *demoShip = nil;
630 : if (JS_GetProperty(context, params, "model", &value) && !JSVAL_IS_VOID(value))
631 : {
632 : if ([player status] == STATUS_IN_FLIGHT && JSVAL_IS_STRING(value))
633 : {
634 : OOJSReportWarning(context, @"Mission.runScreen: model cannot be displayed while in flight.");
635 : }
636 : else
637 : {
638 : NSString *role = OOStringFromJSValue(context, value);
639 :
640 : JSBool spinning = YES;
641 : if (JS_GetProperty(context, params, "spinModel", &value) && !JSVAL_IS_VOID(value))
642 : {
643 : JS_ValueToBoolean(context, value, &spinning);
644 : }
645 :
646 : // [player showShipModel:OOStringFromJSValue(context, value)];
647 : demoShip = [UNIVERSE makeDemoShipWithRole:role spinning:spinning];
648 : }
649 : }
650 : if (demoShip != nil)
651 : {
652 : if (JS_GetProperty(context, params, "modelPersonality", &value) && !JSVAL_IS_VOID(value))
653 : {
654 : int personality = 0;
655 : JS_ValueToInt32(context,value,&personality);
656 : [demoShip setEntityPersonalityInt:personality];
657 : }
658 : jsval demoShipVal = [demoShip oo_jsValueInContext:context];
659 : JS_SetProperty(context, sMissionObject, "displayModel", &demoShipVal);
660 : }
661 : else
662 : {
663 : JS_DeleteProperty(context, sMissionObject, "displayModel");
664 : }
665 :
666 : JSBool allowInterrupt = NO;
667 : // force the allowInterrupt to be YES while in flight
668 : if ([player status] == STATUS_IN_FLIGHT)
669 : {
670 : allowInterrupt = YES;
671 : }
672 : else
673 : {
674 : if (JS_GetProperty(context, params, "allowInterrupt", &value) && !JSVAL_IS_VOID(value))
675 : {
676 : JS_ValueToBoolean(context, value, &allowInterrupt);
677 : }
678 : }
679 :
680 : if (JS_GetProperty(context, params, "exitScreen", &value) && !JSVAL_IS_VOID(value))
681 : {
682 : [player setMissionExitScreen:OOGUIScreenIDFromJSValue(context, value)];
683 : }
684 : else
685 : {
686 : [player setMissionExitScreen:GUI_SCREEN_STATUS];
687 : }
688 :
689 : if (JS_GetProperty(context, params, "screenID", &value) && !JSVAL_IS_VOID(value))
690 : {
691 : [player setMissionScreenID:OOStringFromJSValue(context, value)];
692 : }
693 : else
694 : {
695 : [player clearMissionScreenID];
696 : }
697 :
698 : [player clearExtraMissionKeys];
699 : if (JS_GetProperty(context, params, "registerKeys", &value) && !JSVAL_IS_VOID(value))
700 : {
701 : [player setExtraMissionKeys:GetParameterDictionary(context, params, "registerKeys")];
702 : }
703 :
704 : JSBool textEntry = NO;
705 : if (JS_GetProperty(context, params, "textEntry", &value) && !JSVAL_IS_VOID(value))
706 : {
707 : JS_ValueToBoolean(context, value, &textEntry);
708 : }
709 : if (textEntry)
710 : {
711 : [player setMissionChoiceByTextEntry:YES];
712 : }
713 : else
714 : {
715 : [player setMissionChoiceByTextEntry:NO];
716 : }
717 :
718 : // Start the mission screen.
719 : sCallbackFunction = function;
720 : [player setGuiToMissionScreenWithCallback:!JSVAL_IS_NULL(sCallbackFunction)];
721 :
722 : // Apply more settings. (These must be done after starting the screen for legacy reasons.)
723 : if (allowInterrupt)
724 : {
725 : [player allowMissionInterrupt];
726 : }
727 : NSString *message = GetParameterString(context, params, "message");
728 : if (message != nil)
729 : {
730 : [player addLiteralMissionText:message];
731 : }
732 : else
733 : {
734 : NSString *messageKey = GetParameterString(context, params, "messageKey");
735 : if (messageKey != nil) [player addMissionText:messageKey];
736 : }
737 :
738 : if (!textEntry)
739 : {
740 : NSDictionary *choices = GetParameterDictionary(context, params, "choices");
741 : if (choices == nil)
742 : {
743 : [player setMissionChoices:GetParameterString(context, params, "choicesKey")];
744 : }
745 : else
746 : {
747 : [player setMissionChoicesDictionary:choices];
748 : }
749 : }
750 :
751 : NSString *firstKey = GetParameterString(context, params, "initialChoicesKey");
752 : if (firstKey != nil)
753 : {
754 : OOGUIRow row = [[UNIVERSE gui] rowForKey:firstKey];
755 : if (row != -1)
756 : {
757 : [[UNIVERSE gui] setSelectedRow:row];
758 : }
759 : }
760 :
761 : // now clean up!
762 : [player setMissionOverlayDescriptor:nil];
763 : [player setMissionBackgroundDescriptor:nil];
764 : [player setMissionTitle:nil];
765 : [player setMissionMusic:nil];
766 :
767 : OOJSResumeTimeLimiter();
768 :
769 : OOJS_RETURN_BOOL(YES);
770 :
771 : OOJS_NATIVE_EXIT
772 : }
773 :
774 :
775 0 : static JSBool MissionRunShipLibrary(JSContext *context, uintN argc, jsval *vp)
776 : {
777 : OOJS_NATIVE_ENTER(context)
778 :
779 : PlayerEntity *player = OOPlayerForScripting();
780 : BOOL OK = YES;
781 : if ([player status] != STATUS_DOCKED)
782 : {
783 : OOJSReportWarning(context, @"Mission.runShipLibrary: must be docked.");
784 : OK = NO;
785 : }
786 : else
787 : {
788 : [PLAYER setGuiToIntroFirstGo:NO];
789 : }
790 :
791 : OOJS_RETURN_BOOL(OK);
792 :
793 : OOJS_NATIVE_EXIT
794 : }
795 :
796 :
|