Oolite
Loading...
Searching...
No Matches
PlayerEntityLegacyScriptEngine.m
Go to the documentation of this file.
1/*
2
3PlayerEntityLegacyScriptEngine.m
4
5Oolite
6Copyright (C) 2004-2013 Giles C Williams and contributors
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21MA 02110-1301, USA.
22
23*/
24
27#import "PlayerEntitySound.h"
29#import "GuiDisplayGen.h"
30#import "Universe.h"
31#import "ResourceManager.h"
32#import "AI.h"
33#import "ShipEntityAI.h"
35#import "OOScript.h"
36#import "OOMusicController.h"
37#import "OOColor.h"
38#import "OOStringParsing.h"
39#import "OOStringExpander.h"
40#import "OOConstToString.h"
41#import "OOTexture.h"
43#import "OOLoggingExtended.h"
44#import "OOSound.h"
45#import "OOSunEntity.h"
46#import "OOPlanetEntity.h"
47#import "OOPlanetEntity.h"
48#import "StationEntity.h"
49#import "Comparison.h"
52#import "OOEquipmentType.h"
53#import "HeadUpDisplay.h"
56
57
58static NSString * const kOOLogScriptAddShipsFailed = @"script.addShips.failed";
59static NSString * const kOOLogScriptMissionDescNoText = @"script.missionDescription.noMissionText";
60static NSString * const kOOLogScriptMissionDescNoKey = @"script.missionDescription.noMissionKey";
61
62static NSString * const kOOLogDebugOnMetaClass = @"$scriptDebugOn";
63static NSString * const kOOLogDebugMessage = @"script.debug.message";
64static NSString * const kOOLogDebugOnOff = @"script.debug.onOff";
65static NSString * const kOOLogDebugAddPlanet = @"script.debug.addPlanet";
66static NSString * const kOOLogDebugReplaceVariablesInString = @"script.debug.replaceVariablesInString";
67static NSString * const kOOLogDebugProcessSceneStringAddScene = @"script.debug.processSceneString.addScene";
68static NSString * const kOOLogDebugProcessSceneStringAddModel = @"script.debug.processSceneString.addModel";
69static NSString * const kOOLogDebugProcessSceneStringAddMiniPlanet = @"script.debug.processSceneString.addMiniPlanet";
70
71static NSString * const kOOLogNoteRemoveAllCargo = @"script.debug.note.removeAllCargo";
72static NSString * const kOOLogNoteUseSpecialCargo = @"script.debug.note.useSpecialCargo";
73static NSString * const kOOLogNoteAddShips = @"script.debug.note.addShips";
74static NSString * const kOOLogNoteSet = @"script.debug.note.set";
75static NSString * const kOOLogNoteShowShipModel = @"script.debug.note.showShipModel";
76static NSString * const kOOLogNoteFuelLeak = @"script.debug.note.setFuelLeak";
77static NSString * const kOOLogNoteAddPlanet = @"script.debug.note.addPlanet";
78static NSString * const kOOLogNoteProcessSceneString = @"script.debug.note.processSceneString";
79
80static NSString * const kOOLogSyntaxSetPlanetInfo = @"script.debug.syntax.setPlanetInfo";
81static NSString * const kOOLogSyntaxAwardCargo = @"script.debug.syntax.awardCargo";
82static NSString * const kOOLogSyntaxAwardEquipment = @"script.debug.syntax.awardEquipment";
83static NSString * const kOOLogSyntaxRemoveEquipment = @"script.debug.syntax.removeEquipment";
84static NSString * const kOOLogSyntaxMessageShipAIs = @"script.debug.syntax.messageShipAIs";
85static NSString * const kOOLogSyntaxAddShips = @"script.debug.syntax.addShips";
86static NSString * const kOOLogSyntaxSet = @"script.debug.syntax.set";
87static NSString * const kOOLogSyntaxReset = @"script.debug.syntax.reset";
88static NSString * const kOOLogSyntaxIncrement = @"script.debug.syntax.increment";
89static NSString * const kOOLogSyntaxDecrement = @"script.debug.syntax.decrement";
90static NSString * const kOOLogSyntaxAdd = @"script.debug.syntax.add";
91static NSString * const kOOLogSyntaxSubtract = @"script.debug.syntax.subtract";
92
93static NSString * const kOOLogRemoveAllCargoNotDocked = @"script.error.removeAllCargo.notDocked";
94
95
96#define ACTIONS_TEMP_PREFIX "__oolite_actions_temp"
97static NSString * const kActionTempPrefix = @ ACTIONS_TEMP_PREFIX;
98
99
100static NSString *sMissionStringValue = nil;
101static NSString *sCurrentMissionKey = nil;
103
104
105@interface PlayerEntity (ScriptingPrivate)
106
107- (BOOL) scriptTestCondition:(NSArray *)scriptCondition;
108- (NSString *) expandScriptRightHandSide:(NSArray *)rhsComponents;
109
110- (void) scriptActions:(NSArray *)actions forTarget:(ShipEntity *)target missionKey:(NSString *)missionKey;
111- (NSString *) expandMessage:(NSString *)valueString;
112
113@end
114
115
116@implementation PlayerEntity (Scripting)
117
118
119static NSString *CurrentScriptNameOr(NSString *alternative)
120{
122 {
123 return [NSString stringWithFormat:@"\"%@\"", sCurrentMissionKey];
124 }
125 return alternative;
126}
127
128
129OOINLINE NSString *CurrentScriptDesc(void)
130{
131 return CurrentScriptNameOr(@"<anonymous actions>");
132}
133
134
135static void PerformScriptActions(NSArray *actions, Entity *target);
136static void PerformConditionalStatment(NSArray *actions, Entity *target);
137static void PerformActionStatment(NSArray *statement, Entity *target);
138static BOOL TestScriptConditions(NSArray *conditions);
139
140
141static void PerformScriptActions(NSArray *actions, Entity *target)
142{
143 NSArray *statement = nil;
144 foreach (statement, actions)
145 {
146 if ([[statement objectAtIndex:0] boolValue])
147 {
148 PerformConditionalStatment(statement, target);
149 }
150 else
151 {
152 PerformActionStatment(statement, target);
153 }
154 }
155}
156
157
158static void PerformConditionalStatment(NSArray *statement, Entity *target)
159{
160 /* A sanitized conditional statement takes the form of an array:
161 (true, conditions, trueActions, falseActions)
162 The first element is always true. The second is an array of conditions.
163 The third and four elements are actions to perform if the conditions
164 evaluate to true or false, respectively.
165 */
166
167 NSArray *conditions = nil;
168 NSArray *actions = nil;
169
170 conditions = [statement objectAtIndex:1];
171
172 if (TestScriptConditions(conditions))
173 {
174 actions = [statement objectAtIndex:2];
175 }
176 else
177 {
178 actions = [statement objectAtIndex:3];
179 }
180
181 PerformScriptActions(actions, target);
182}
183
184
185static void PerformActionStatment(NSArray *statement, Entity *target)
186{
187 /* A sanitized action statement takes the form of an array:
188 (false, selector [, argument])
189 The first element is always false. The second is the method selector
190 (as a string). If the method takes an argument, the third argument is
191 the argument string.
192
193 The sanitizer is responsible for ensuring that there is an argument,
194 even if it's the empty string, for any selector with a colon at the
195 end, and no arguments for selectors without colons. The runner can
196 therefore use the list's element count as a flag without examining the
197 selector.
198 */
199
200 NSString *selectorString = nil;
201 NSString *argumentString = nil;
202 NSString *expandedString = nil;
203 SEL selector = NULL;
204 NSMutableDictionary *locals = nil;
205 PlayerEntity *player = PLAYER;
206
207 selectorString = [statement objectAtIndex:1];
208 if ([statement count] > 2) argumentString = [statement objectAtIndex:2];
209
210 selector = NSSelectorFromString(selectorString);
211
212 if (target == nil || ![target respondsToSelector:selector])
213 {
214 target = player;
215 }
216
217 if (argumentString != nil)
218 {
219 // Method with argument; substitute [description] expressions.
220 locals = [player localVariablesForMission:sCurrentMissionKey];
221 expandedString = OOExpandDescriptionString(OOStringExpanderDefaultRandomSeed(), argumentString, nil, locals, nil, kOOExpandNoOptions);
222
223 [target performSelector:selector withObject:expandedString];
224 }
225 else
226 {
227 // Method without argument.
228 [target performSelector:selector];
229 }
230}
231
232
233static BOOL TestScriptConditions(NSArray *conditions)
234{
235 NSEnumerator *condEnum = nil;
236 NSArray *condition = nil;
237 PlayerEntity *player = PLAYER;
238
239 for (condEnum = [conditions objectEnumerator]; (condition = [condEnum nextObject]); )
240 {
241 if (![player scriptTestCondition:condition]) return NO;
242 }
243
244 return YES;
245}
246
247
248- (void) setScriptTarget:(ShipEntity *)ship
249{
250 scriptTarget = ship;
251}
252
253
255{
256 return scriptTarget;
257}
258
259
261{
262 // Some player stutuses should only be seen once per "event".
263 // This remaps them to something innocuous in case of recursion.
264 if (status == STATUS_DOCKING ||
265 status == STATUS_LAUNCHING ||
266 status == STATUS_ENTERING_WITCHSPACE ||
267 status == STATUS_EXITING_WITCHSPACE)
268 {
269 return STATUS_IN_FLIGHT;
270 }
271 else
272 {
273 return status;
274 }
275}
276
277
278static BOOL sRunningScript = NO;
279
280
281// Return the world scripts that care about -checkScript.
282- (NSDictionary *) worldScriptsRequiringTickle
283{
284 if (worldScriptsRequiringTickle != nil) return worldScriptsRequiringTickle;
285
286 NSMutableDictionary *tickleScripts = [NSMutableDictionary dictionaryWithCapacity:[worldScripts count]];
287 NSString *scriptName;
288 foreachkey (scriptName, worldScripts)
289 {
290 OOScript *candidateScript = [worldScripts objectForKey:scriptName];
291 if ([candidateScript requiresTickle])
292 {
293 [tickleScripts setObject:candidateScript forKey:scriptName];
294 }
295 }
296
297 worldScriptsRequiringTickle = [tickleScripts copy];
298 return worldScriptsRequiringTickle;
299}
300
301
302- (void) checkScript
303{
304 BOOL wasRunningScript = sRunningScript;
305 OOEntityStatus status, restoreStatus;
306
307 NSDictionary *tickleScripts = [self worldScriptsRequiringTickle];
308 if ([tickleScripts count] == 0)
309 {
310 // Quick exit if we only have JS scripts.
311 return;
312 }
313
314 [self setScriptTarget:self];
315
316 /* World scripts can potentially be invoked recursively, through
317 scriptActionOnTarget: and possibly other mechanisms. This is bad, but
318 that's the way it is. Legacy world scripts rely on only seeing certain
319 player statuses once per "event". To ensure this, we must lie about
320 the player's status when invoked recursively.
321
322 Of course, there are also methods in the game that rely on status not
323 lying. However, I don't believe any that rely on these particular
324 statuses can be legitimately invoked by scripts. The alternative would
325 be to track the "status-as-seen-by-scripts" separately from the "real"
326 status, which'd risk synchronization problems.
327
328 In summary, scriptActionOnTarget: is bad, and calling it from scripts
329 rather than AIs is very bad.
330 -- Ahruman, 20080302
331
332 Addendum: scriptActionOnTarget: is currently not in the whitelist for
333 script methods. Let's hope this doesn't turn out to be a problem.
334 -- Ahruman, 20090208
335 */
336 status = [self status];
337 restoreStatus = status;
338 @try
339 {
340 if (sRunningScript)
341 {
342 status = RecursiveRemapStatus(status);
343 [self setStatus:status];
344 }
345 sRunningScript = YES;
346
347 // After all that, actually running the scripts is trivial.
348 [[tickleScripts allValues] makeObjectsPerformSelector:@selector(runWithTarget:) withObject:self];
349 }
350 @catch (NSException *exception)
351 {
352 OOLog(kOOLogException, @"***** Exception running world scripts: %@ : %@", [exception name], [exception reason]);
353 }
354
355 // Restore anti-recursion measures.
356 sRunningScript = wasRunningScript;
357 if (status != restoreStatus) [self setStatus:restoreStatus];
358}
359
360
361- (void)runScriptActions:(NSArray *)actions withContextName:(NSString *)contextName forTarget:(ShipEntity *)target
362{
363 NSAutoreleasePool *pool = nil;
364 NSString *oldMissionKey = nil;
365 NSString * volatile theMissionKey = contextName; // Work-around for silly exception macros
366
367 pool = [[NSAutoreleasePool alloc] init];
368
369 // FIXME: does this actually make sense in the context of non-missions?
370 oldMissionKey = sCurrentMissionKey;
371 sCurrentMissionKey = theMissionKey;
372
373 @try
374 {
375 PerformScriptActions(actions, target);
376 }
377 @catch (NSException *exception)
378 {
379 OOLog(@"script.error.exception",
380 @"***** EXCEPTION %@: %@ while handling legacy script actions for %@",
381 [exception name],
382 [exception reason],
383 [theMissionKey hasPrefix:kActionTempPrefix] ? [target shortDescription] : theMissionKey);
384 // Suppress exception
385 }
386
387 sCurrentMissionKey = oldMissionKey;
388 [pool release];
389}
390
391
392- (void) runUnsanitizedScriptActions:(NSArray *)actions allowingAIMethods:(BOOL)allowAIMethods withContextName:(NSString *)contextName forTarget:(ShipEntity *)target
393{
394 [self runScriptActions:OOSanitizeLegacyScript(actions, contextName, allowAIMethods)
395 withContextName:contextName
396 forTarget:target];
397}
398
399
400- (BOOL) scriptTestConditions:(NSArray *)array
401{
402 BOOL result = NO;
403
404 @try
405 {
406 result = TestScriptConditions(array);
407 }
408 @catch (NSException *exception)
409 {
410 OOLog(@"script.error.exception",
411 @"***** EXCEPTION %@: %@ while testing legacy script conditions.",
412 [exception name],
413 [exception reason]);
414 // Suppress exception
415 }
416
417 return result;
418}
419
420
421- (BOOL) scriptTestCondition:(NSArray *)scriptCondition
422{
423 /* Test a script condition sanitized by OOLegacyScriptWhitelist.
424
425 A sanitized condition is an array of the form:
426 (opType, rawString, selector, comparisonType, operandArray).
427
428 opType and comparisonType are NSNumbers containing OOOperationType and
429 OOComparisonType enumerators, respectively.
430
431 rawString is the original textual representation of the condition for
432 display purposes.
433
434 selector is a string, either a method selector or a mission/local
435 variable name.
436
437 operandArray is an array of operands. Each operand is itself an array
438 of two items: a boolean indicating whether it's a method selector
439 (true) or a literal string (false), and a string.
440
441 The special opType OP_FALSE doesn't require any other elements in the
442 array. All other valid opTypes require the array to have five elements.
443
444 For performance reasons, this method assumes the script condition will
445 have been generated by OOSanitizeLegacyScriptConditions() and doesn't
446 perform extensive validity checks.
447 */
448
449 OOOperationType opType;
450 NSString *selectorString = nil;
451 SEL selector = NULL;
452 OOComparisonType comparator;
453 NSArray *operandArray = nil;
454 NSString *lhsString = nil;
455 NSString *expandedRHS = nil;
456 NSArray *rhsComponents = nil;
457 NSString *rhsItem = nil;
458 NSUInteger i, count;
459 NSCharacterSet *whitespace = nil;
460 double lhsValue, rhsValue;
461 BOOL lhsFlag, rhsFlag;
462
463 opType = [scriptCondition oo_unsignedIntAtIndex:0];
464 if (opType == OP_FALSE) return NO;
465
466 selectorString = [scriptCondition oo_stringAtIndex:2];
467 comparator = [scriptCondition oo_unsignedIntAtIndex:3];
468 operandArray = [scriptCondition oo_arrayAtIndex:4];
469
470 // Transform mission/local var ops into string ops.
471 if (opType == OP_MISSION_VAR)
472 {
473 sMissionStringValue = [mission_variables objectForKey:selectorString];
474 selector = @selector(mission_string);
475 opType = OP_STRING;
476 }
477 else if (opType == OP_LOCAL_VAR)
478 {
479 sMissionStringValue = [[self localVariablesForMission:sCurrentMissionKey] objectForKey:selectorString];
480 selector = @selector(mission_string);
481 opType = OP_STRING;
482 }
483 else
484 {
485 selector = NSSelectorFromString(selectorString);
486 }
487
488 expandedRHS = [self expandScriptRightHandSide:operandArray];
489
490 if (opType == OP_STRING)
491 {
492 lhsString = [self performSelector:selector];
493
494 #define DOUBLEVAL(x) ((x != nil) ? [x doubleValue] : 0.0)
495
496 switch (comparator)
497 {
499 return lhsString == nil;
500
501 case COMPARISON_EQUAL:
502 return [lhsString isEqualToString:expandedRHS];
503
505 return ![lhsString isEqualToString:expandedRHS];
506
508 return DOUBLEVAL(lhsString) < DOUBLEVAL(expandedRHS);
509
511 return DOUBLEVAL(lhsString) > DOUBLEVAL(expandedRHS);
512
513 case COMPARISON_ONEOF:
514 {
515 rhsComponents = [expandedRHS componentsSeparatedByString:@","];
516 count = [rhsComponents count];
517
518 whitespace = [NSCharacterSet whitespaceCharacterSet];
519 lhsString = [lhsString stringByTrimmingCharactersInSet:whitespace];
520
521 for (i = 0; i < count; i++)
522 {
523 rhsItem = [[rhsComponents objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace];
524 if ([lhsString isEqualToString:rhsItem])
525 {
526 return YES;
527 }
528 }
529 }
530 return NO;
531 }
532 }
533 else if (opType == OP_NUMBER)
534 {
535 lhsValue = [[self performSelector:selector] doubleValue];
536
537 if (comparator == COMPARISON_ONEOF)
538 {
539 rhsComponents = [expandedRHS componentsSeparatedByString:@","];
540 count = [rhsComponents count];
541
542 for (i = 0; i < count; i++)
543 {
544 rhsItem = [rhsComponents objectAtIndex:i];
545 rhsValue = [rhsItem doubleValue];
546
547 if (lhsValue == rhsValue)
548 {
549 return YES;
550 }
551 }
552
553 return NO;
554 }
555 else
556 {
557 rhsValue = [expandedRHS doubleValue];
558
559 switch (comparator)
560 {
561 case COMPARISON_EQUAL:
562 return lhsValue == rhsValue;
563
565 return lhsValue != rhsValue;
566
568 return lhsValue < rhsValue;
569
571 return lhsValue > rhsValue;
572
574 case COMPARISON_ONEOF:
575 // "Can't happen" - undefined should have been caught by the sanitizer, oneof is handled above.
576 OOLog(@"script.error.unexpectedOperator", @"***** SCRIPT ERROR: in %@, operator %@ is not valid for numbers, evaluating to false.", CurrentScriptDesc(), OOComparisonTypeToString(comparator));
577 return NO;
578 }
579 }
580 }
581 else if (opType == OP_BOOL)
582 {
583 lhsFlag = [[self performSelector:selector] isEqualToString:@"YES"];
584 rhsFlag = [expandedRHS isEqualToString:@"YES"];
585
586 switch (comparator)
587 {
588 case COMPARISON_EQUAL:
589 return lhsFlag == rhsFlag;
590
592 return lhsFlag != rhsFlag;
593
597 case COMPARISON_ONEOF:
598 // "Can't happen" - should have been caught by the sanitizer.
599 OOLog(@"script.error.unexpectedOperator", @"***** SCRIPT ERROR: in %@, operator %@ is not valid for booleans, evaluating to false.", CurrentScriptDesc(), OOComparisonTypeToString(comparator));
600 return NO;
601 }
602 }
603
604 // What are we doing here?
605 OOLog(@"script.error.fallthrough", @"***** SCRIPT ERROR: in %@, unhandled condition '%@' (%@). %@", CurrentScriptDesc(), [scriptCondition objectAtIndex:1], scriptCondition, @"This is an internal error, please report it.");
606 return NO;
607}
608
609
610- (NSString *) expandScriptRightHandSide:(NSArray *)rhsComponents
611{
612 NSMutableArray *result = nil;
613 NSArray *component = nil;
614 NSString *value = nil;
615
616 result = [NSMutableArray arrayWithCapacity:[rhsComponents count]];
617
618 foreach (component, rhsComponents)
619 {
620 /* Each component is a two-element array. The second element is a
621 string. The first element is a boolean indicating whether the
622 string is a selector (true) or a literal (false).
623
624 All valid selectors return a string or an NSNumber; in either
625 case, -description gives us a useful value to substitute into
626 the expanded string.
627 */
628
629 value = [component oo_stringAtIndex:1];
630
631 if ([[component objectAtIndex:0] boolValue])
632 {
633 value = [[self performSelector:NSSelectorFromString(value)] description];
634 if (value == nil) value = @"(null)"; // for backwards compatibility
635 }
636
637 [result addObject:value];
638 }
639
640 return [result componentsJoinedByString:@" "];
641}
642
643
644- (NSDictionary *) missionVariables
645{
646 return mission_variables;
647}
648
649
650- (NSString *)missionVariableForKey:(NSString *)key
651{
652 NSString *result = nil;
653 if (key != nil) result = [mission_variables objectForKey:key];
654 return result;
655}
656
657
658- (void)setMissionVariable:(NSString *)value forKey:(NSString *)key
659{
660 if (key != nil)
661 {
662 if (value != nil) [mission_variables setObject:value forKey:key];
663 else [mission_variables removeObjectForKey:key];
664 }
665}
666
667
668- (NSMutableDictionary *)localVariablesForMission:(NSString *)missionKey
669{
670 NSMutableDictionary *result = nil;
671
672 if (missionKey == nil) return nil;
673
674 result = [localVariables objectForKey:missionKey];
675 if (result == nil)
676 {
677 result = [NSMutableDictionary dictionary];
678 [localVariables setObject:result forKey:missionKey];
679 }
680
681 return result;
682}
683
684
685- (NSString *)localVariableForKey:(NSString *)variableName andMission:(NSString *)missionKey
686{
687 return [[localVariables oo_dictionaryForKey:missionKey] objectForKey:variableName];
688}
689
690
691- (void)setLocalVariable:(NSString *)value forKey:(NSString *)variableName andMission:(NSString *)missionKey
692{
693 NSMutableDictionary *locals = nil;
694
695 if (variableName != nil && missionKey != nil)
696 {
697 locals = [self localVariablesForMission:missionKey];
698 if (value != nil)
699 {
700 [locals setObject:value forKey:variableName];
701 }
702 else
703 {
704 [locals removeObjectForKey:variableName];
705 }
706 }
707}
708
709
710- (NSArray *) missionsList
711{
712 NSEnumerator *scriptEnum = nil;
713 NSString *scriptName = nil;
714 NSString *vars = nil;
715 NSMutableArray *result1 = nil;
716 NSMutableArray *result2 = nil;
717
718 result1 = [NSMutableArray array];
719 result2 = [NSMutableArray array];
720
721 NSArray* passengerManifest = [self passengerList];
722 NSArray* contractManifest = [self contractList];
723 NSArray* parcelManifest = [self parcelList];
724
725 if ([passengerManifest count] > 0)
726 {
727 [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-passengers")] arrayByAddingObjectsFromArray:passengerManifest]];
728 }
729
730 if ([parcelManifest count] > 0)
731 {
732 [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-parcels")] arrayByAddingObjectsFromArray:parcelManifest]];
733 }
734
735 if ([contractManifest count] > 0)
736 {
737 [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-contracts")] arrayByAddingObjectsFromArray:contractManifest]];
738 }
739
740 /* For proper display, array entries need to all be after string
741 * entries, so sort them now */
742 for (scriptEnum = [worldScripts keyEnumerator]; (scriptName = [scriptEnum nextObject]); )
743 {
744 vars = [mission_variables objectForKey:scriptName];
745
746 if (vars != nil)
747 {
748 if ([vars isKindOfClass:[NSString class]])
749 {
750 [result1 addObject:vars];
751 }
752 else if ([vars isKindOfClass:[NSArray class]])
753 {
754 BOOL found = NO;
755 NSArray *element = nil;
756 foreach (element, result2)
757 {
758 if ([[element oo_stringAtIndex:0] isEqualToString:[(NSArray*)vars oo_stringAtIndex:0]])
759 {
760
761 [result2 removeObject:element];
762 NSRange notTheHeader;
763 notTheHeader.location = 1;
764 notTheHeader.length = [(NSArray*)vars count]-1;
765 [result2 addObject:[element arrayByAddingObjectsFromArray:[(NSArray*)vars subarrayWithRange:notTheHeader]]];
766 found = YES;
767 break;
768 }
769 }
770 if (!found)
771 {
772 [result2 addObject:vars];
773 }
774 }
775 }
776 }
777 return [result1 arrayByAddingObjectsFromArray:result2];
778}
779
780
781- (NSString*) replaceVariablesInString:(NSString*) args
782{
783 NSMutableDictionary *locals = [self localVariablesForMission:sCurrentMissionKey];
784 NSMutableString *resultString = [NSMutableString stringWithString: args];
785 NSString *valueString;
786 unsigned i;
787 NSMutableArray *tokens = ScanTokensFromString(args);
788
789 for (i = 0; i < [tokens count]; i++)
790 {
791 valueString = [tokens objectAtIndex:i];
792
793 if ([valueString hasPrefix:@"mission_"] && [mission_variables objectForKey:valueString])
794 {
795 [resultString replaceOccurrencesOfString:valueString withString:[mission_variables objectForKey:valueString] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])];
796 }
797 else if ([locals objectForKey:valueString])
798 {
799 [resultString replaceOccurrencesOfString:valueString withString:[locals objectForKey:valueString] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])];
800 }
801 else if (([valueString hasSuffix:@"_number"])||([valueString hasSuffix:@"_bool"])||([valueString hasSuffix:@"_string"]))
802 {
803 SEL valueselector = NSSelectorFromString(valueString);
804 if ([self respondsToSelector:valueselector])
805 {
806 [resultString replaceOccurrencesOfString:valueString withString:[NSString stringWithFormat:@"%@", [self performSelector:valueselector]] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])];
807 }
808 }
809 else if ([valueString hasPrefix:@"["]&&[valueString hasSuffix:@"]"])
810 {
811 NSString* replaceString = OOExpand(valueString);
812 [resultString replaceOccurrencesOfString:valueString withString:replaceString options:NSLiteralSearch range:NSMakeRange(0, [resultString length])];
813 }
814 }
815
816 OOLog(kOOLogDebugReplaceVariablesInString, @"EXPANSION: \"%@\" becomes \"%@\"", args, resultString);
817
818 return [NSString stringWithString: resultString];
819}
820
821/*-----------------------------------------------------*/
822
823
824- (void) setMissionDescription:(NSString *)textKey
825{
826 [self setMissionDescription:textKey forMission:sCurrentMissionKey];
827}
828
829
830- (void) setMissionDescription:(NSString *)textKey forMission:(NSString *)key
831{
832 NSString *text = [[UNIVERSE missiontext] oo_stringForKey:textKey];
833
834 if (!text)
835 {
836 OOLogERR(kOOLogScriptMissionDescNoText, @"in %@, no mission text set for key '%@' [UNIVERSE missiontext] is:\n%@ ", CurrentScriptDesc(), textKey, [UNIVERSE missiontext]);
837 return;
838 }
839
840 [self setMissionInstructions:text forMission:key];
841}
842
843
844// implementation of mission.setInstructions(), also final part of legacy setMissionDescription
845- (void) setMissionInstructions:(NSString *)text forMission:(NSString *)key
846{
847 if (!key)
848 {
849 OOLogERR(kOOLogScriptMissionDescNoKey, @"in %@, mission key not set", CurrentScriptDesc());
850 return;
851 }
852
853 text = OOExpand(text);
854 text = [self replaceVariablesInString: text];
855
856 [mission_variables setObject:text forKey:key];
857}
858
859
860- (void) setMissionInstructionsList:(NSArray *)list forMission:(NSString *)key
861{
862 if (!key)
863 {
864 OOLogERR(kOOLogScriptMissionDescNoKey, @"in %@, mission key not set", CurrentScriptDesc());
865 return;
866 }
867
868 NSString *text = nil;
869 NSUInteger i,ct = [list count];
870 NSMutableArray *expandedList = [NSMutableArray arrayWithCapacity:ct];
871 for (i=0 ; i<ct ; i++)
872 {
873 text = [list oo_stringAtIndex:i defaultValue:nil];
874 if (text != nil)
875 {
876 text = OOExpand(text);
877 text = [self replaceVariablesInString: text];
878 [expandedList addObject:text];
879 }
880 }
881
882 [mission_variables setObject:expandedList forKey:key];
883}
884
885
886- (void) clearMissionDescription
887{
888 [self clearMissionDescriptionForMission:sCurrentMissionKey];
889}
890
891
892- (void) clearMissionDescriptionForMission:(NSString *)key
893{
894 if (!key)
895 {
896 OOLogERR(kOOLogScriptMissionDescNoKey, @"in %@, mission key not set", CurrentScriptDesc());
897 return;
898 }
899
900 if (![mission_variables objectForKey:key]) return;
901
902 [mission_variables removeObjectForKey:key];
903}
904
905
906- (NSString *) mission_string
907{
908 return sMissionStringValue;
909}
910
911
912- (NSString *) status_string
913{
914 return OOStringFromEntityStatus([self status]);
915}
916
917
918- (NSString *) gui_screen_string
919{
920 return OOStringFromGUIScreenID(gui_screen);
921}
922
923
924- (NSNumber *) galaxy_number
925{
926 return [NSNumber numberWithInt:[self currentGalaxyID]];
927}
928
929
930- (NSNumber *) planet_number
931{
932 return [NSNumber numberWithInt:[self currentSystemID]];
933}
934
935
936- (NSNumber *) score_number
937{
938 return [NSNumber numberWithUnsignedInt:[self score]];
939}
940
941
942- (NSNumber *) credits_number
943{
944 return [NSNumber numberWithDouble:[self creditBalance]];
945}
946
947
948- (NSNumber *) scriptTimer_number
949{
950 return [NSNumber numberWithDouble:[self scriptTimer]];
951}
952
953
954static int shipsFound;
955- (NSNumber *) shipsFound_number
956{
957 return [NSNumber numberWithInt:shipsFound];
958}
959
960
961- (NSNumber *) commanderLegalStatus_number
962{
963 return [NSNumber numberWithInt:[self legalStatus]];
964}
965
966
967- (void) setLegalStatus:(NSString *)valueString
968{
969 legalStatus = [valueString intValue];
970}
971
972
973- (NSString *) commanderLegalStatus_string
974{
975 return OODisplayStringFromLegalStatus(legalStatus);
976}
977
978
979- (NSNumber *) d100_number
980{
981 int d100 = ranrot_rand() % 100;
982 return [NSNumber numberWithInt:d100];
983}
984
985
986- (NSNumber *) pseudoFixedD100_number
987{
988 return [NSNumber numberWithInt:[self systemPseudoRandom100]];
989}
990
991
992- (NSNumber *) d256_number
993{
994 int d256 = ranrot_rand() % 256;
995 return [NSNumber numberWithInt:d256];
996}
997
998
999- (NSNumber *) pseudoFixedD256_number
1000{
1001 return [NSNumber numberWithInt:[self systemPseudoRandom256]];
1002}
1003
1004
1005- (NSNumber *) clock_number // returns the game time in seconds
1006{
1007 return [NSNumber numberWithDouble:ship_clock];
1008}
1009
1010
1011- (NSNumber *) clock_secs_number // returns the game time in seconds
1012{
1013 return [NSNumber numberWithUnsignedLongLong:ship_clock];
1014}
1015
1016
1017- (NSNumber *) clock_mins_number // returns the game time in minutes
1018{
1019 return [NSNumber numberWithUnsignedLongLong:ship_clock / 60.0];
1020}
1021
1022
1023- (NSNumber *) clock_hours_number // returns the game time in hours
1024{
1025 return [NSNumber numberWithUnsignedLongLong:ship_clock / 3600.0];
1026}
1027
1028
1029- (NSNumber *) clock_days_number // returns the game time in days
1030{
1031 return [NSNumber numberWithUnsignedLongLong:ship_clock / 86400.0];
1032}
1033
1034
1035- (NSNumber *) fuelLevel_number // returns the fuel level in LY
1036{
1037 return [NSNumber numberWithFloat:floor(0.1 * fuel)];
1038}
1039
1040
1041- (NSString *) dockedAtMainStation_bool
1042{
1043 if ([self dockedAtMainStation]) return @"YES";
1044 else return @"NO";
1045}
1046
1047
1048- (NSString *) foundEquipment_bool
1049{
1050 return (found_equipment)? @"YES" : @"NO";
1051}
1052
1053
1054- (NSString *) sunWillGoNova_bool // returns whether the sun is going to go nova
1055{
1056 return ([[UNIVERSE sun] willGoNova])? @"YES" : @"NO";
1057}
1058
1059
1060- (NSString *) sunGoneNova_bool // returns whether the sun has gone nova
1061{
1062 return ([[UNIVERSE sun] goneNova])? @"YES" : @"NO";
1063}
1064
1065
1066- (NSString *) missionChoice_string // returns nil or the key for the chosen option
1067{
1068 return missionChoice;
1069}
1070
1071
1072- (NSString *) missionKeyPress_string
1073{
1074 return missionKeyPress;
1075}
1076
1077
1078- (NSNumber *) dockedTechLevel_number
1079{
1080 StationEntity *dockedStation = [self dockedStation];
1081 if (!dockedStation)
1082 {
1083 return [self systemTechLevel_number];
1084 }
1085 return [NSNumber numberWithUnsignedInteger:[dockedStation equivalentTechLevel]];
1086}
1087
1088- (NSString *) dockedStationName_string // returns 'NONE' if the player isn't docked, [station name] if it is, 'UNKNOWN' otherwise (?)
1089{
1090 NSString *result = nil;
1091 if ([self status] != STATUS_DOCKED) return @"NONE";
1092
1093 result = [self dockedStationName];
1094 if (result == nil) result = @"UNKNOWN";
1095 return result;
1096}
1097
1098
1099- (NSString *) systemGovernment_string
1100{
1101 int government = [[self systemGovernment_number] intValue]; // 0 .. 7 (0 anarchic .. 7 most stable)
1102 NSString *result = OODisplayStringFromGovernmentID(government);
1103 if (result == nil) result = @"UNKNOWN";
1104
1105 return result;
1106}
1107
1108
1109- (NSNumber *) systemGovernment_number
1110{
1111 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1112 return [systeminfo objectForKey:KEY_GOVERNMENT];
1113}
1114
1115
1116- (NSString *) systemEconomy_string
1117{
1118 int economy = [[self systemEconomy_number] intValue]; // 0 .. 7 (0 rich industrial .. 7 poor agricultural)
1119 NSString *result = OODisplayStringFromEconomyID(economy);
1120 if (result == nil) result = @"UNKNOWN";
1121
1122 return result;
1123}
1124
1125
1126- (NSNumber *) systemEconomy_number
1127{
1128 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1129 return [systeminfo objectForKey:KEY_ECONOMY];
1130}
1131
1132
1133- (NSNumber *) systemTechLevel_number
1134{
1135 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1136 return [systeminfo objectForKey:KEY_TECHLEVEL];
1137}
1138
1139
1140- (NSNumber *) systemPopulation_number
1141{
1142 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1143 return [systeminfo objectForKey:KEY_POPULATION];
1144}
1145
1146
1147- (NSNumber *) systemProductivity_number
1148{
1149 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1150 return [systeminfo objectForKey:KEY_PRODUCTIVITY];
1151}
1152
1153
1154- (NSString *) commanderName_string
1155{
1156 return [self commanderName];
1157}
1158
1159
1160- (NSString *) commanderRank_string
1161{
1162 return OODisplayRatingStringFromKillCount([self score]);
1163}
1164
1165
1166- (NSString *) commanderShip_string
1167{
1168 return [self name];
1169}
1170
1171
1172- (NSString *) commanderShipDisplayName_string
1173{
1174 return [self displayName];
1175}
1176
1177/*-----------------------------------------------------*/
1178
1179
1180- (NSString *) expandMessage:(NSString *)valueString
1181{
1182 Random_Seed very_random_seed;
1183 very_random_seed.a = rand() & 255;
1184 very_random_seed.b = rand() & 255;
1185 very_random_seed.c = rand() & 255;
1186 very_random_seed.d = rand() & 255;
1187 very_random_seed.e = rand() & 255;
1188 very_random_seed.f = rand() & 255;
1189 seed_RNG_only_for_planet_description(very_random_seed);
1190 NSString* expandedMessage = OOExpand(valueString);
1191 return [self replaceVariablesInString: expandedMessage];
1192}
1193
1194
1195- (void) commsMessage:(NSString *)valueString
1196{
1197 [UNIVERSE addCommsMessage:[self expandMessage:valueString] forCount:4.5];
1198}
1199
1200
1201// Enabled on 02-May-2008 - Nikos
1202// This method does the same as -commsMessage, (which in fact calls), the difference being that scripts can use this
1203// method to have unpiloted ship entities sending comms messages.
1204- (void) commsMessageByUnpiloted:(NSString *)valueString
1205{
1206 [self commsMessage:valueString];
1207}
1208
1209
1210- (void) consoleMessage3s:(NSString *)valueString
1211{
1212 [UNIVERSE addMessage:[self expandMessage:valueString] forCount: 3];
1213}
1214
1215
1216- (void) consoleMessage6s:(NSString *)valueString
1217{
1218 [UNIVERSE addMessage:[self expandMessage:valueString] forCount: 6];
1219}
1220
1221
1222- (void) awardCredits:(NSString *)valueString
1223{
1224 if (scriptTarget != self) return;
1225
1226 /* We can't use -longLongValue here for Mac OS X 10.4 compatibility, but
1227 we don't need to since larger values have never been supported for
1228 legacy scripts.
1229 */
1230 int64_t award = [valueString intValue];
1231 award *= 10;
1232 if (award < 0 && credits < (OOCreditsQuantity)-award) credits = 0;
1233 else credits += award;
1234}
1235
1236
1237- (void) awardShipKills:(NSString *)valueString
1238{
1239 if (scriptTarget != self) return;
1240
1241 int value = [valueString intValue];
1242 if (0 < value) ship_kills += value;
1243}
1244
1245
1246- (void) awardEquipment:(NSString *)equipString //eg. EQ_NAVAL_ENERGY_UNIT
1247{
1248 if (scriptTarget != self) return;
1249
1250 if ([equipString isEqualToString:@"EQ_FUEL"])
1251 {
1252 [self setFuel:[self fuelCapacity]];
1253 }
1254
1256
1257 if ([eqType isMissileOrMine])
1258 {
1259 [self mountMissileWithRole:equipString];
1260 }
1261 else if([equipString hasPrefix:@"EQ_WEAPON"] && ![equipString hasSuffix:@"_DAMAGED"])
1262 {
1263 OOLog(kOOLogSyntaxAwardEquipment, @"***** SCRIPT ERROR: in %@, CANNOT award undamaged weapon:'%@'. Damaged weapons can be awarded instead.", CurrentScriptDesc(), equipString);
1264 }
1265 else if ([equipString hasSuffix:@"_DAMAGED"] && [self hasEquipmentItem:[equipString substringToIndex:[equipString length] - [@"_DAMAGED" length]]])
1266 {
1267 OOLog(kOOLogSyntaxAwardEquipment, @"***** SCRIPT ERROR: in %@, CANNOT award damaged equipment:'%@'. Undamaged version already equipped.", CurrentScriptDesc(), equipString);
1268 }
1269 else if ([eqType canCarryMultiple] || ![self hasEquipmentItem:equipString])
1270 {
1271 [self addEquipmentItem:equipString withValidation:YES inContext:@"scripted"];
1272 }
1273}
1274
1275
1276- (void) removeEquipment:(NSString *)equipKey //eg. EQ_NAVAL_ENERGY_UNIT
1277{
1278 if (scriptTarget != self) return;
1279
1280 if ([equipKey isEqualToString:@"EQ_FUEL"])
1281 {
1282 fuel = 0;
1283 return;
1284 }
1285
1286 if ([equipKey isEqualToString:@"EQ_CARGO_BAY"] && [self hasEquipmentItem:equipKey]
1287 && ([self extraCargo] > [self availableCargoSpace]))
1288 {
1289 OOLog(kOOLogSyntaxRemoveEquipment, @"***** SCRIPT ERROR: in %@, CANNOT remove cargo bay. Too much cargo.", CurrentScriptDesc());
1290 return;
1291 }
1292 if ([self hasEquipmentItem:equipKey] || [self hasEquipmentItem:[equipKey stringByAppendingString:@"_DAMAGED"]])
1293 {
1294 [self removeEquipmentItem:equipKey];
1295 }
1296
1297}
1298
1299
1300- (void) setPlanetinfo:(NSString *)key_valueString // uses key=value format
1301{
1302 NSArray * tokens = [key_valueString componentsSeparatedByString:@"="];
1303 NSString* keyString = nil;
1304 NSString* valueString = nil;
1305
1306 if ([tokens count] != 2)
1307 {
1308 OOLog(kOOLogSyntaxSetPlanetInfo, @"***** SCRIPT ERROR: in %@, CANNOT setPlanetinfo: '%@' (bad parameter count)", CurrentScriptDesc(), key_valueString);
1309 return;
1310 }
1311
1312 keyString = [[tokens objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1313 valueString = [[tokens objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1314
1315 /* Legacy script planetinfo settings are now non-persistent over save/load
1316 * Virtually nothing uses them any more, and expecting them to have a
1317 * manifest and identifying what it is if so seems unnecessary */
1318 [UNIVERSE setSystemDataKey:keyString value:valueString fromManifest:@""];
1319
1320}
1321
1322
1323- (void) setSpecificPlanetInfo:(NSString *)key_valueString // uses galaxy#=planet#=key=value
1324{
1325 NSArray * tokens = [key_valueString componentsSeparatedByString:@"="];
1326 NSString* keyString = nil;
1327 NSString* valueString = nil;
1328 int gnum, pnum;
1329
1330 if ([tokens count] != 4)
1331 {
1332 OOLog(kOOLogSyntaxSetPlanetInfo, @"***** SCRIPT ERROR: in %@, CANNOT setSpecificPlanetInfo: '%@' (bad parameter count)", CurrentScriptDesc(), key_valueString);
1333 return;
1334 }
1335
1336 gnum = [tokens oo_intAtIndex:0];
1337 pnum = [tokens oo_intAtIndex:1];
1338 keyString = [[tokens objectAtIndex:2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1339 valueString = [[tokens objectAtIndex:3] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1340
1341 [UNIVERSE setSystemDataForGalaxy:gnum planet:pnum key:keyString value:valueString fromManifest:@"" forLayer:OO_LAYER_OXP_DYNAMIC];
1342}
1343
1344
1345- (void) awardCargo:(NSString *)amount_typeString
1346{
1347 if (scriptTarget != self) return;
1348
1349 NSArray *tokens = ScanTokensFromString(amount_typeString);
1350 OOCargoQuantityDelta amount;
1351 OOCommodityType type;
1352 OOMassUnit unit;
1353
1354 if ([tokens count] != 2)
1355 {
1356 OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"bad parameter count");
1357 return;
1358 }
1359
1360
1361 type = [tokens oo_stringAtIndex:1];
1362 if (![[UNIVERSE commodities] goodDefined:type])
1363 {
1364 OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"unknown type");
1365 return;
1366 }
1367
1368 amount = [tokens oo_intAtIndex:0];
1369 if (amount < 0)
1370 {
1371 OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"negative quantity");
1372 return;
1373 }
1374
1375 unit = [shipCommodityData massUnitForGood:type];
1376 if (specialCargo && unit == UNITS_TONS)
1377 {
1378 OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"cargo hold full with special cargo");
1379 return;
1380 }
1381
1382 [self awardCommodityType:type amount:amount];
1383}
1384
1385
1386- (void) removeAllCargo
1387{
1388 [self removeAllCargo:NO];
1389}
1390
1391- (void) removeAllCargo:(BOOL)forceRemoval
1392{
1393 // Misnamed method. It only removes cargo measured in TONS, g & Kg items are not removed. --Kaks 20091004
1394 OOCommodityType type;
1395
1396 if (scriptTarget != self) return;
1397
1398 if ([self status] != STATUS_DOCKED && !forceRemoval)
1399 {
1400 OOLogWARN(kOOLogRemoveAllCargoNotDocked, @"%@removeAllCargo only works when docked.", [NSString stringWithFormat:@" in %@, ", CurrentScriptDesc()]);
1401 return;
1402 }
1403
1404 OOLog(kOOLogNoteRemoveAllCargo, @"%@ removeAllCargo", forceRemoval ? @"Forcing" : @"Going to");
1405
1406 foreach (type, [shipCommodityData goods])
1407 {
1408 if ([shipCommodityData massUnitForGood:type] == UNITS_TONS)
1409 {
1410 [shipCommodityData setQuantity:0 forGood:type];
1411 }
1412 }
1413
1414
1415 if (forceRemoval && [self status] != STATUS_DOCKED)
1416 {
1417 NSInteger i;
1418 for (i = [cargo count] - 1; i >= 0; i--)
1419 {
1420 ShipEntity* canister = [cargo objectAtIndex:i];
1421 if (!canister) break;
1422 // Since we are forcing cargo removal, we don't really care about the unit of measurement. Any
1423 // commodity at more than 1000kg or 1000000gr will be inside cargopods, so remove those too.
1424 [cargo removeObjectAtIndex:i];
1425 }
1426 }
1427
1428 DESTROY(specialCargo);
1429
1430 [self calculateCurrentCargo];
1431}
1432
1433
1434- (void) useSpecialCargo:(NSString *)descriptionString
1435{
1436 if (scriptTarget != self) return;
1437
1438 [self removeAllCargo:YES];
1439 OOLog(kOOLogNoteUseSpecialCargo, @"Going to useSpecialCargo:'%@'", descriptionString);
1440 specialCargo = [OOExpand(descriptionString) retain];
1441}
1442
1443
1444- (void) testForEquipment:(NSString *)equipString //eg. EQ_NAVAL_ENERGY_UNIT
1445{
1446 found_equipment = [self hasEquipmentItem:equipString];
1447}
1448
1449
1450- (void) awardFuel:(NSString *)valueString // add to fuel up to 7.0 LY
1451{
1452 int delta = 10 * [valueString floatValue];
1453 OOFuelQuantity scriptTargetFuelBeforeAward = [scriptTarget fuel];
1454
1455 if (delta < 0 && scriptTargetFuelBeforeAward < (unsigned)-delta) [scriptTarget setFuel:0];
1456 else
1457 {
1458 [scriptTarget setFuel:(scriptTargetFuelBeforeAward + delta)];
1459 }
1460}
1461
1462
1463- (void) messageShipAIs:(NSString *)roles_message
1464{
1465 NSMutableArray* tokens = ScanTokensFromString(roles_message);
1466 NSString* roleString = nil;
1467 NSString* messageString = nil;
1468
1469 if ([tokens count] < 2)
1470 {
1471 OOLog(kOOLogSyntaxMessageShipAIs, @"***** SCRIPT ERROR: in %@, CANNOT messageShipAIs: '%@' (bad parameter count)", CurrentScriptDesc(), roles_message);
1472 return;
1473 }
1474
1475 roleString = [tokens objectAtIndex:0];
1476 [tokens removeObjectAtIndex:0];
1477 messageString = [tokens componentsJoinedByString:@" "];
1478
1479 NSArray *targets = [UNIVERSE findShipsMatchingPredicate:HasPrimaryRolePredicate
1480 parameter:roleString
1481 inRange:-1
1482 ofEntity:nil];
1483
1484 ShipEntity *target;
1485 foreach(target, targets) {
1486 [[target getAI] reactToMessage:messageString context:@"messageShipAIs:"];
1487 }
1488}
1489
1490
1491- (void) ejectItem:(NSString *)itemKey
1492{
1493 if (scriptTarget == nil) scriptTarget = self;
1494 [scriptTarget ejectShipOfType:itemKey];
1495}
1496
1497
1498- (void) addShips:(NSString *)roles_number
1499{
1500 NSMutableArray* tokens = ScanTokensFromString(roles_number);
1501 NSString* roleString = nil;
1502 NSString* numberString = nil;
1503
1504 if ([tokens count] != 2)
1505 {
1506 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addShips: '%@' (expected <role> <count>)", CurrentScriptDesc(), roles_number);
1507 return;
1508 }
1509
1510 roleString = [tokens objectAtIndex:0];
1511 numberString = [tokens objectAtIndex:1];
1512
1513 int number = [numberString intValue];
1514 if (number < 0)
1515 {
1516 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, can't add %i ships -- that's less than zero, y'know..", CurrentScriptDesc(), number);
1517 return;
1518 }
1519
1520 OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ships with role '%@'", number, roleString);
1521
1522 while (number--)
1523 [UNIVERSE witchspaceShipWithPrimaryRole:roleString];
1524}
1525
1526
1527- (void) addSystemShips:(NSString *)roles_number_position
1528{
1529 NSMutableArray* tokens = ScanTokensFromString(roles_number_position);
1530 NSString* roleString = nil;
1531 NSString* numberString = nil;
1532 NSString* positionString = nil;
1533
1534 if ([tokens count] != 3)
1535 {
1536 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addSystemShips: '%@' (expected <role> <count> <position>)", CurrentScriptDesc(), roles_number_position);
1537 return;
1538 }
1539
1540 roleString = [tokens objectAtIndex:0];
1541 numberString = [tokens objectAtIndex:1];
1542 positionString = [tokens objectAtIndex:2];
1543
1544 int number = [numberString intValue];
1545 double posn = [positionString doubleValue];
1546 if (number < 0)
1547 {
1548 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, can't add %i ships -- that's less than zero, y'know..", CurrentScriptDesc(), number);
1549 return;
1550 }
1551
1552 OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ships with role '%@' at a point %.3f along route1", number, roleString, posn);
1553
1554 while (number--)
1555 [UNIVERSE addShipWithRole:roleString nearRouteOneAt:posn];
1556}
1557
1558
1559- (void) addShipsAt:(NSString *)roles_number_system_x_y_z
1560{
1561 NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z);
1562
1563 NSString* roleString = nil;
1564 NSString* numberString = nil;
1565 NSString* systemString = nil;
1566 NSString* xString = nil;
1567 NSString* yString = nil;
1568 NSString* zString = nil;
1569
1570 if ([tokens count] != 6)
1571 {
1572 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addShipsAt: '%@' (expected <role> <count> <coordinate-system> <x> <y> <z>)", CurrentScriptDesc(), roles_number_system_x_y_z);
1573 return;
1574 }
1575
1576 roleString = [tokens objectAtIndex:0];
1577 numberString = [tokens objectAtIndex:1];
1578 systemString = [tokens objectAtIndex:2];
1579 xString = [tokens objectAtIndex:3];
1580 yString = [tokens objectAtIndex:4];
1581 zString = [tokens objectAtIndex:5];
1582
1583 HPVector posn = make_HPvector([xString doubleValue], [yString doubleValue], [zString doubleValue]);
1584
1585 int number = [numberString intValue];
1586 if (number < 1)
1587 {
1588 OOLog(kOOLogSyntaxAddShips, @"----- WARNING in %@ Tried to add %i ships -- no ship added.", CurrentScriptDesc(), number);
1589 return;
1590 }
1591
1592 OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ship(s) with role '%@' at point (%.3f, %.3f, %.3f) using system %@", number, roleString, posn.x, posn.y, posn.z, systemString);
1593
1594 if (![UNIVERSE addShips: number withRole:roleString nearPosition: posn withCoordinateSystem: systemString])
1595 {
1596 OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, %@ could not add %u ships with role \"%@\"", CurrentScriptDesc(), @"addShipsAt:", number, roleString);
1597 }
1598}
1599
1600
1601- (void) addShipsAtPrecisely:(NSString *)roles_number_system_x_y_z
1602{
1603 NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z);
1604
1605 NSString* roleString = nil;
1606 NSString* numberString = nil;
1607 NSString* systemString = nil;
1608 NSString* xString = nil;
1609 NSString* yString = nil;
1610 NSString* zString = nil;
1611
1612 if ([tokens count] != 6)
1613 {
1614 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@,* CANNOT addShipsAtPrecisely: '%@' (expected <role> <count> <coordinate-system> <x> <y> <z>)", CurrentScriptDesc(), roles_number_system_x_y_z);
1615 return;
1616 }
1617
1618 roleString = [tokens objectAtIndex:0];
1619 numberString = [tokens objectAtIndex:1];
1620 systemString = [tokens objectAtIndex:2];
1621 xString = [tokens objectAtIndex:3];
1622 yString = [tokens objectAtIndex:4];
1623 zString = [tokens objectAtIndex:5];
1624
1625 HPVector posn = make_HPvector([xString doubleValue], [yString doubleValue], [zString doubleValue]);
1626
1627 int number = [numberString intValue];
1628 if (number < 1)
1629 {
1630 OOLog(kOOLogSyntaxAddShips, @"----- WARNING: in %@, Can't add %i ships -- no ship added.", CurrentScriptDesc(), number);
1631 return;
1632 }
1633
1634 OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ship(s) with role '%@' precisely at point (%.3f, %.3f, %.3f) using system %@", number, roleString, posn.x, posn.y, posn.z, systemString);
1635
1636 if (![UNIVERSE addShips: number withRole:roleString atPosition: posn withCoordinateSystem: systemString])
1637 {
1638 OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, %@ could not add %u ships with role '%@'", CurrentScriptDesc(), @"addShipsAtPrecisely:", number, roleString);
1639 }
1640}
1641
1642
1643- (void) addShipsWithinRadius:(NSString *)roles_number_system_x_y_z_r
1644{
1645 NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z_r);
1646
1647 if ([tokens count] != 7)
1648 {
1649 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT 'addShipsWithinRadius: %@' (expected <role> <count> <coordinate-system> <x> <y> <z> <radius>))", CurrentScriptDesc(), roles_number_system_x_y_z_r);
1650 return;
1651 }
1652
1653 NSString* roleString = [tokens objectAtIndex:0];
1654 int number = [[tokens objectAtIndex:1] intValue];
1655 NSString* systemString = [tokens objectAtIndex:2];
1656 double x = [[tokens objectAtIndex:3] doubleValue];
1657 double y = [[tokens objectAtIndex:4] doubleValue];
1658 double z = [[tokens objectAtIndex:5] doubleValue];
1659 GLfloat r = [[tokens objectAtIndex:6] floatValue];
1660 HPVector posn = make_HPvector(x, y, z);
1661
1662 if (number < 1)
1663 {
1664 OOLog(kOOLogSyntaxAddShips, @"----- WARNING: in %@, can't add %i ships -- no ship added.", CurrentScriptDesc(), number);
1665 return;
1666 }
1667
1668 OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ship(s) with role '%@' within %.2f radius about point (%.3f, %.3f, %.3f) using system %@", number, roleString, r, x, y, z, systemString);
1669
1670 if (![UNIVERSE addShips:number withRole: roleString nearPosition: posn withCoordinateSystem: systemString withinRadius: r])
1671 {
1672 OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR :in %@, %@ could not add %u ships with role \"%@\"", CurrentScriptDesc(), @"addShipsWithinRadius:", number, roleString);
1673 }
1674}
1675
1676
1677- (void) spawnShip:(NSString *)ship_key
1678{
1679 if ([UNIVERSE spawnShip:ship_key])
1680 {
1681 OOLog(kOOLogNoteAddShips, @"DEBUG: Spawned ship with shipdata key '%@'.", ship_key);
1682 }
1683 else
1684 {
1685 OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, could not spawn ship with shipdata key '%@'.", CurrentScriptDesc(), ship_key);
1686 }
1687}
1688
1689
1690- (void) set:(NSString *)missionvariable_value
1691{
1692 NSMutableArray *tokens = ScanTokensFromString(missionvariable_value);
1693 NSString *missionVariableString = nil;
1694 NSString *valueString = nil;
1695 BOOL hasMissionPrefix, hasLocalPrefix;
1696
1697 if ([tokens count] < 2)
1698 {
1699 OOLog(kOOLogSyntaxSet, @"***** SCRIPT ERROR: in %@, CANNOT SET '%@' (expected mission_variable or local_variable followed by value expression)", CurrentScriptDesc(), missionvariable_value);
1700 return;
1701 }
1702
1703 missionVariableString = [tokens objectAtIndex:0];
1704 [tokens removeObjectAtIndex:0];
1705 valueString = [tokens componentsJoinedByString:@" "];
1706
1707 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1708 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1709
1710 if (!hasMissionPrefix && !hasLocalPrefix)
1711 {
1712 OOLog(kOOLogSyntaxSet, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString);
1713 return;
1714 }
1715
1716 OOLog(kOOLogNoteSet, @"DEBUG: script %@ is set to %@", missionVariableString, valueString);
1717
1718 if (hasMissionPrefix)
1719 {
1720 [self setMissionVariable:valueString forKey:missionVariableString];
1721 }
1722 else
1723 {
1724 [self setLocalVariable:valueString forKey:missionVariableString andMission:sCurrentMissionKey];
1725 }
1726}
1727
1728
1729- (void) reset:(NSString *)missionvariable
1730{
1731 NSString* missionVariableString = [missionvariable stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1732 BOOL hasMissionPrefix, hasLocalPrefix;
1733
1734 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1735 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1736
1737 if (hasMissionPrefix)
1738 {
1739 [self setMissionVariable:nil forKey:missionVariableString];
1740 }
1741 else if (hasLocalPrefix)
1742 {
1743 [self setLocalVariable:nil forKey:missionVariableString andMission:sCurrentMissionKey];
1744 }
1745 else
1746 {
1747 OOLog(kOOLogSyntaxReset, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString);
1748 }
1749}
1750
1751
1752- (void) increment:(NSString *)missionVariableString
1753{
1754 BOOL hasMissionPrefix, hasLocalPrefix;
1755 int value = 0;
1756
1757 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1758 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1759
1760 if (hasMissionPrefix)
1761 {
1762 value = [[self missionVariableForKey:missionVariableString] intValue];
1763 value++;
1764 [self setMissionVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString];
1765 }
1766 else if (hasLocalPrefix)
1767 {
1768 value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] intValue];
1769 value++;
1770 [self setLocalVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString andMission:sCurrentMissionKey];
1771 }
1772 else
1773 {
1774 OOLog(kOOLogSyntaxIncrement, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString);
1775 }
1776}
1777
1778
1779- (void) decrement:(NSString *)missionVariableString
1780{
1781 BOOL hasMissionPrefix, hasLocalPrefix;
1782 int value = 0;
1783
1784 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1785 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1786
1787 if (hasMissionPrefix)
1788 {
1789 value = [[self missionVariableForKey:missionVariableString] intValue];
1790 value--;
1791 [self setMissionVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString];
1792 }
1793 else if (hasLocalPrefix)
1794 {
1795 value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] intValue];
1796 value--;
1797 [self setLocalVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString andMission:sCurrentMissionKey];
1798 }
1799 else
1800 {
1801 OOLog(kOOLogSyntaxDecrement, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString);
1802 }
1803}
1804
1805
1806- (void) add:(NSString *)missionVariableString_value
1807{
1808 NSString* missionVariableString = nil;
1809 NSString* valueString;
1810 double value;
1811 NSMutableArray* tokens = ScanTokensFromString(missionVariableString_value);
1812 BOOL hasMissionPrefix, hasLocalPrefix;
1813
1814 if ([tokens count] < 2)
1815 {
1816 OOLog(kOOLogSyntaxAdd, @"***** SCRIPT ERROR: in %@, CANNOT ADD: '%@'", CurrentScriptDesc(), missionVariableString_value);
1817 return;
1818 }
1819
1820 missionVariableString = [tokens objectAtIndex:0];
1821 [tokens removeObjectAtIndex:0];
1822 valueString = [tokens componentsJoinedByString:@" "];
1823
1824 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1825 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1826
1827 if (hasMissionPrefix)
1828 {
1829 value = [[self missionVariableForKey:missionVariableString] doubleValue];
1830 value += [valueString doubleValue];
1831 [self setMissionVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString];
1832 }
1833 else if (hasLocalPrefix)
1834 {
1835 value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] doubleValue];
1836 value += [valueString doubleValue];
1837 [self setLocalVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString andMission:sCurrentMissionKey];
1838 }
1839 else
1840 {
1841 OOLog(kOOLogSyntaxAdd, @"***** SCRIPT ERROR: in %@, CANNOT ADD: '%@' -- IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString_value, missionVariableString_value);
1842 }
1843}
1844
1845
1846- (void) subtract:(NSString *)missionVariableString_value
1847{
1848 NSString* missionVariableString = nil;
1849 NSString* valueString;
1850 double value;
1851 NSMutableArray* tokens = ScanTokensFromString(missionVariableString_value);
1852 BOOL hasMissionPrefix, hasLocalPrefix;
1853
1854 if ([tokens count] < 2)
1855 {
1856 OOLog(kOOLogSyntaxSubtract, @"***** SCRIPT ERROR: in %@, CANNOT SUBTRACT: '%@'", CurrentScriptDesc(), missionVariableString_value);
1857 return;
1858 }
1859
1860 missionVariableString = [tokens objectAtIndex:0];
1861 [tokens removeObjectAtIndex:0];
1862 valueString = [tokens componentsJoinedByString:@" "];
1863
1864 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1865 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1866
1867 if (hasMissionPrefix)
1868 {
1869 value = [[self missionVariableForKey:missionVariableString] doubleValue];
1870 value -= [valueString doubleValue];
1871 [self setMissionVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString];
1872 }
1873 else if (hasLocalPrefix)
1874 {
1875 value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] doubleValue];
1876 value -= [valueString doubleValue];
1877 [self setLocalVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString andMission:sCurrentMissionKey];
1878 }
1879 else
1880 {
1881 OOLog(kOOLogSyntaxSubtract, @"***** SCRIPT ERROR: in %@, CANNOT SUBTRACT: '%@' -- IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString_value, missionVariableString_value);
1882 }
1883}
1884
1885
1886- (void) checkForShips:(NSString *)roleString
1887{
1888 shipsFound = [UNIVERSE countShipsWithPrimaryRole:roleString];
1889}
1890
1891
1892- (void) resetScriptTimer
1893{
1894 script_time = 0.0;
1895 script_time_check = SCRIPT_TIMER_INTERVAL;
1896 script_time_interval = SCRIPT_TIMER_INTERVAL;
1897}
1898
1899
1900- (void) addMissionText: (NSString *)textKey
1901{
1902 NSString *text = nil;
1903
1904 if ([textKey isEqualToString:lastTextKey]) return; // don't repeatedly add the same text
1905 [lastTextKey release];
1906 lastTextKey = [textKey copy];
1907
1908 // Replace literal \n in strings with line breaks and perform expansions.
1909 text = [[UNIVERSE missiontext] oo_stringForKey:textKey];
1910 if (text == nil) return;
1912 text = [self replaceVariablesInString:text];
1913
1914 [self addLiteralMissionText:text];
1915}
1916
1917
1918- (void) addLiteralMissionText:(NSString *)text
1919{
1920 if (text != nil)
1921 {
1922 GuiDisplayGen *gui = [UNIVERSE gui];
1923
1924 NSString *para = nil;
1925 foreach (para, [text componentsSeparatedByString:@"\n"])
1926 {
1927 missionTextRow = [gui addLongText:para startingAtRow:missionTextRow align:GUI_ALIGN_LEFT];
1928 }
1929 }
1930}
1931
1932
1933- (void) setMissionChoiceByTextEntry:(BOOL)enable
1934{
1935 MyOpenGLView *gameView = [UNIVERSE gameView];
1936 _missionTextEntry = enable;
1937 [gameView resetTypedString];
1938}
1939
1940
1941- (void) setMissionChoices:(NSString *)choicesKey // choicesKey is a key for a dictionary of
1942{ // choices/choice phrases in missiontext.plist and also..
1943 NSDictionary *choicesDict = [[UNIVERSE missiontext] oo_dictionaryForKey:choicesKey];
1944 if ([choicesDict count] == 0)
1945 {
1946 return;
1947 }
1948 [self setMissionChoicesDictionary:choicesDict];
1949}
1950
1951
1952- (void) setMissionChoicesDictionary:(NSDictionary *)choicesDict
1953{
1954 unsigned i;
1955 bool keysOK = true;
1956 GuiDisplayGen* gui = [UNIVERSE gui];
1957 // TODO: MORE STUFF HERE
1958 //
1959 // What it does now:
1960 // find list of choices in missiontext.plist
1961 // add them to gui setting the key for each line to the key in the dict of choices
1962 // and the text of the line to the value in the dict of choices
1963 // and also set the selectable range
1964 // ++ change the mission screen's response to wait for a choice
1965 // and only if the selectable range is not present ask:
1966 // Press Space Commander...
1967 //
1968
1969 NSUInteger end_row = 21;
1970 if ([[self hud] allowBigGui])
1971 {
1972 end_row = 27;
1973 }
1974
1975 NSArray *choiceKeys = [choicesDict allKeys];
1976 /* Guard against potential for numeric keys in dictionary, which
1977 * would cause an unhandled exception in the sorter. See
1978 * OOJavaScriptEngine::OOJSDictionaryFromJSObject for further
1979 * thoughts. - CIM 15/2/13 */
1980 for (i=0; i < [choiceKeys count]; i++)
1981 {
1982 if (![[choiceKeys objectAtIndex:i] isKindOfClass:[NSString class]])
1983 {
1984 OOLog(@"test.script.error",@"Choices list in mission screen has non-string value %@",[choiceKeys objectAtIndex:i]);
1985 keysOK = false;
1986 }
1987 }
1988 if (keysOK)
1989 {
1990 // only try this if they're all strings
1991 choiceKeys = [choiceKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
1992 }
1993
1994 NSInteger keysCount = [choiceKeys count];
1995 if ((end_row + 1) < [choiceKeys count]) {
1996 OOLogERR(kOOLogException, @"in mission.runScreen choices: number of choices defined (%llu) is greater than available lines (%llu). Check HUD settings for allowBigGui.", [choiceKeys count], (end_row + 1));
1997 keysCount = end_row + 1;
1998 }
1999
2000 [gui setText:@"" forRow:end_row]; // clears out the 'Press spacebar' message
2001 [gui setKey:@"" forRow:end_row]; // clears the key to enable pollDemoControls to check for a selection
2002 [gui setSelectableRange:NSMakeRange(0,0)]; // clears the selectable range
2003 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; // enables mouse selection of the choices list items
2004
2005 OOGUIRow choicesRow = (end_row+1) - keysCount;
2006 NSString *choiceKey = nil;
2007 id choiceValue = nil;
2008 NSString *choiceText = nil;
2009
2010 BOOL selectableRowExists = NO;
2011 NSUInteger firstSelectableRow = end_row;
2012
2013 foreach (choiceKey, choiceKeys)
2014 {
2015 choiceValue = [choicesDict objectForKey:choiceKey];
2016 OOGUIAlignment alignment = GUI_ALIGN_CENTER;
2017 OOColor *rowColor = [OOColor yellowColor];
2018 BOOL selectable = YES;
2019 if ([choiceValue isKindOfClass:[NSString class]])
2020 {
2021 choiceText = [NSString stringWithFormat:@" %@ ",(NSString*)choiceValue];
2022 }
2023 else if ([choiceValue isKindOfClass:[NSDictionary class]])
2024 {
2025 NSDictionary *choiceOpts = (NSDictionary*)choiceValue;
2026 choiceText = [NSString stringWithFormat:@" %@ ",[choiceOpts oo_stringForKey:@"text"]];
2027 NSString *alignmentChoice = [choiceOpts oo_stringForKey:@"alignment" defaultValue:@"CENTER"];
2028 if ([alignmentChoice isEqualToString:@"LEFT"])
2029 {
2030 alignment = GUI_ALIGN_LEFT;
2031 }
2032 else if ([alignmentChoice isEqualToString:@"RIGHT"])
2033 {
2034 alignment = GUI_ALIGN_RIGHT;
2035 }
2036 id colorDesc = [choiceOpts objectForKey:@"color"];
2037 if ([choiceOpts oo_boolForKey:@"unselectable"])
2038 {
2039 selectable = NO;
2040 }
2041 if (colorDesc != nil)
2042 {
2043 rowColor = [OOColor colorWithDescription:colorDesc];
2044 }
2045 else if (!selectable) // different default
2046 {
2047 rowColor = [OOColor darkGrayColor];
2048 }
2049 }
2050 else
2051 {
2052 continue; // invalid type
2053 }
2054 choiceText = OOExpand(choiceText);
2055 choiceText = [self replaceVariablesInString:choiceText];
2056 // allow blank rows
2057 if (![choiceText isEqualToString:@" "])
2058 {
2059 [gui setText:choiceText forRow:choicesRow align: alignment];
2060 if (selectable)
2061 {
2062 [gui setKey:choiceKey forRow:choicesRow];
2063 }
2064 else
2065 {
2066 [gui setKey:GUI_KEY_SKIP forRow:choicesRow];
2067 }
2068 [gui setColor:rowColor forRow:choicesRow];
2069 if (selectable && !selectableRowExists)
2070 {
2071 selectableRowExists = YES;
2072 firstSelectableRow = choicesRow;
2073 }
2074 }
2075 else
2076 {
2077 [gui setKey:GUI_KEY_SKIP forRow:choicesRow];
2078 }
2079 choicesRow++;
2080 if (choicesRow > (end_row + 1)) break;
2081 }
2082
2083 if (!selectableRowExists)
2084 {
2085 // just in case choices are set but they're all blank.
2086 [gui setText:@" " forRow:end_row align: GUI_ALIGN_CENTER];
2087 [gui setKey:@"" forRow:end_row];
2088 [gui setColor:[OOColor yellowColor] forRow:end_row];
2089 }
2090
2091 [gui setSelectableRange:NSMakeRange((end_row+1) - keysCount, keysCount)];
2092 [gui setSelectedRow: firstSelectableRow];
2093
2094 [self resetMissionChoice];
2095}
2096
2097
2098- (void) resetMissionChoice
2099{
2100 [self setMissionChoice:nil];
2101}
2102
2103
2104- (void) clearMissionScreen
2105{
2106 [self setMissionOverlayDescriptor:nil];
2107 [self setMissionBackgroundDescriptor:nil];
2108 [self setMissionBackgroundSpecial:nil];
2109 [self setMissionTitle:nil];
2110 [self setMissionMusic:nil];
2111 [self showShipModel:nil];
2112}
2113
2114
2115- (void) addMissionDestination:(NSString *)destinations
2116{
2117 unsigned j;
2118 int dest;
2119 NSMutableArray *tokens = ScanTokensFromString(destinations);
2120
2121 for (j = 0; j < [tokens count]; j++)
2122 {
2123 dest = [tokens oo_intAtIndex:j];
2124 if (dest < 0 || dest > 255)
2125 continue;
2126
2127 [self addMissionDestinationMarker:[self defaultMarker:dest]];
2128 }
2129}
2130
2131
2132- (void) removeMissionDestination:(NSString *)destinations
2133{
2134 unsigned j;
2135 int dest;
2136 NSMutableArray *tokens = ScanTokensFromString(destinations);
2137
2138 for (j = 0; j < [tokens count]; j++)
2139 {
2140 dest = [[tokens objectAtIndex:j] intValue];
2141 if (dest < 0 || dest > 255) continue;
2142
2143 [self removeMissionDestinationMarker:[self defaultMarker:dest]];
2144 }
2145}
2146
2147
2148- (void) showShipModel:(NSString *)role
2149{
2150 if ([role isEqualToString:@"none"] || [role length] == 0)
2151 {
2152 [UNIVERSE removeDemoShips];
2153 return;
2154 }
2155
2156 ShipEntity *ship = [UNIVERSE makeDemoShipWithRole:role spinning:YES];
2157 OOLog(kOOLogNoteShowShipModel, @"::::: showShipModel:'%@' (%@) (%@)", role, ship, [ship name]);
2158}
2159
2160
2161- (void) setMissionMusic:(NSString *)value
2162{
2163 if ([value length] == 0 || [[value lowercaseString] isEqualToString:@"none"])
2164 {
2165 value = nil;
2166 }
2168}
2169
2170
2171- (NSString *) missionTitle
2172{
2173 return _missionTitle;
2174}
2175
2176
2177- (void) setMissionTitle:(NSString *)value
2178{
2179 if (_missionTitle != value)
2180 {
2181 [_missionTitle release];
2182 _missionTitle = [value copy];
2183 }
2184}
2185
2186
2187- (void) setMissionImage:(NSString *)value
2188{
2189 if ([value length] != 0 && ![[value lowercaseString] isEqualToString:@"none"])
2190 {
2191 [self setMissionOverlayDescriptor:[NSDictionary dictionaryWithObject:value forKey:@"name"]];
2192 }
2193 else
2194 {
2195 [self setMissionOverlayDescriptor:nil];
2196 }
2197
2198}
2199
2200
2201- (void) setMissionBackground:(NSString *)value
2202{
2203 if ([value length] != 0 && ![[value lowercaseString] isEqualToString:@"none"])
2204 {
2205 [self setMissionBackgroundDescriptor:[NSDictionary dictionaryWithObject:value forKey:@"name"]];
2206 }
2207 else
2208 {
2209 [self setMissionBackgroundDescriptor:nil];
2210 }
2211}
2212
2213
2214- (void) setFuelLeak:(NSString *)value
2215{
2216 if (scriptTarget != self)
2217 {
2218 [scriptTarget setFuel:0];
2219 return;
2220 }
2221
2222 fuel_leak_rate = [value doubleValue];
2223 if (fuel_leak_rate > 0)
2224 {
2225 [self playFuelLeak];
2226 [UNIVERSE addMessage:DESC(@"danger-fuel-leak") forCount:6];
2227 OOLog(kOOLogNoteFuelLeak, @"%@", @"FUEL LEAK activated!");
2228 }
2229}
2230
2231
2232- (NSNumber *) fuelLeakRate_number
2233{
2234 return [NSNumber numberWithFloat:[self fuelLeakRate]];
2235}
2236
2237
2238- (void) setSunNovaIn:(NSString *)time_value
2239{
2240 double time_until_nova = [time_value doubleValue];
2241 [[UNIVERSE sun] setGoingNova:YES inTime: time_until_nova];
2242}
2243
2244
2245- (void) launchFromStation
2246{
2247 // ensure autosave is ready for the next unscripted launch
2248 if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES];
2249 if ([self status] == STATUS_DOCKING) [self setStatus:STATUS_DOCKED]; // needed here to prevent the normal update from continuing with docking.
2250 [self leaveDock:[self dockedStation]];
2251}
2252
2253
2254- (void) blowUpStation
2255{
2256 StationEntity *mainStation = nil;
2257
2258 mainStation = [UNIVERSE station];
2259 if (mainStation != nil)
2260 {
2261 [UNIVERSE unMagicMainStation];
2262 [mainStation takeEnergyDamage:500000000.0 from:nil becauseOf:nil weaponIdentifier:@""]; // 500 million should do it!
2263 }
2264}
2265
2266
2267- (void) sendAllShipsAway
2268{
2269 if (!UNIVERSE)
2270 return;
2271 int ent_count = UNIVERSE->n_entities;
2272 Entity** uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list
2273 Entity* my_entities[ent_count];
2274 int i;
2275 for (i = 0; i < ent_count; i++)
2276 my_entities[i] = [uni_entities[i] retain]; // retained
2277
2278 for (i = 1; i < ent_count; i++)
2279 {
2280 Entity* e1 = my_entities[i];
2281 if ([e1 isShip])
2282 {
2283 ShipEntity* se1 = (ShipEntity*)e1;
2284 int e_class = [e1 scanClass];
2285 if (((e_class == CLASS_NEUTRAL)||(e_class == CLASS_POLICE)||(e_class == CLASS_MILITARY)||(e_class == CLASS_THARGOID)) &&
2286 ! ([se1 isStation] && [se1 maxFlightSpeed] == 0) && // exclude only stations, not carriers.
2287 [se1 hasHyperspaceMotor]) // exclude non jumping ships. Escorts will still be able to follow a mother.
2288 {
2289 AI* se1AI = [se1 getAI];
2290 [se1 setFuel:MAX(PLAYER_MAX_FUEL, [se1 fuelCapacity])];
2291 [se1 setAITo:@"exitingTraderAI.plist"]; // lets them return to their previous state after the jump
2292 [se1AI setState:@"EXIT_SYSTEM"];
2293 // The following should prevent all ships leaving at once (freezes oolite on slower machines)
2294 [se1AI setNextThinkTime:[UNIVERSE getTime] + 3 + (ranrot_rand() & 15)];
2295 [se1 setPrimaryRole:@"oolite-none"]; // prevents new ship from appearing at witchpoint when this one leaves!
2296 }
2297 }
2298 }
2299
2300 for (i = 0; i < ent_count; i++)
2301 {
2302 [my_entities[i] release]; // released
2303 }
2304}
2305
2306
2307- (OOPlanetEntity *) addPlanet: (NSString *)planetKey
2308{
2309 OOLog(kOOLogNoteAddPlanet, @"addPlanet: %@", planetKey);
2310
2311 if (!UNIVERSE)
2312 return nil;
2313 NSDictionary* dict = [[UNIVERSE systemManager] getPropertiesForSystemKey:planetKey];
2314 if (!dict)
2315 {
2316 OOLog(@"script.error.addPlanet.keyNotFound", @"***** ERROR: could not find an entry in planetinfo.plist for '%@'", planetKey);
2317 return nil;
2318 }
2319
2320 /*- add planet -*/
2321 OOLog(kOOLogDebugAddPlanet, @"DEBUG: initPlanetFromDictionary: %@", dict);
2322 OOPlanetEntity *planet = [[[OOPlanetEntity alloc] initFromDictionary:dict withAtmosphere:YES andSeed:[[UNIVERSE systemManager] getRandomSeedForCurrentSystem] forSystem:system_id] autorelease];
2323
2324 Quaternion planetOrientation;
2325 if (ScanQuaternionFromString([dict objectForKey:@"orientation"], &planetOrientation))
2326 {
2327 [planet setOrientation:planetOrientation];
2328 }
2329
2330 if (![dict objectForKey:@"position"])
2331 {
2332 OOLog(@"script.error.addPlanet.noPosition", @"***** ERROR: you must specify a position for scripted planet '%@' before it can be created", planetKey);
2333 return nil;
2334 }
2335
2336 NSString *positionString = [dict objectForKey:@"position"];
2337 if([positionString hasPrefix:@"abs "] && ([UNIVERSE planet] != nil || [UNIVERSE sun] !=nil))
2338 {
2339 OOLogWARN(@"script.deprecated", @"setting %@ for %@ '%@' in 'abs' inside .plists can cause compatibility issues across Oolite versions. Use coordinates relative to main system objects instead.",@"position",@"planet",planetKey);
2340 }
2341
2342 HPVector posn = [UNIVERSE coordinatesFromCoordinateSystemString:positionString];
2343 if (posn.x || posn.y || posn.z)
2344 {
2345 OOLog(kOOLogDebugAddPlanet, @"planet position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString);
2346 }
2347 else
2348 {
2349 ScanHPVectorFromString(positionString, &posn);
2350 OOLog(kOOLogDebugAddPlanet, @"planet position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString);
2351 }
2352 [planet setPosition: posn];
2353
2354 [UNIVERSE addEntity:planet];
2355 return planet;
2356}
2357
2358
2359- (OOPlanetEntity *) addMoon: (NSString *)moonKey
2360{
2361 OOLog(kOOLogNoteAddPlanet, @"DEBUG: addMoon '%@'", moonKey);
2362
2363 if (!UNIVERSE)
2364 return nil;
2365 NSDictionary* dict = [[UNIVERSE systemManager] getPropertiesForSystemKey:moonKey];
2366 if (!dict)
2367 {
2368 OOLog(@"script.error.addPlanet.keyNotFound", @"***** ERROR: could not find an entry in planetinfo.plist for '%@'", moonKey);
2369 return nil;
2370 }
2371
2372 OOLog(kOOLogDebugAddPlanet, @"DEBUG: initMoonFromDictionary: %@", dict);
2373 OOPlanetEntity *planet = [[[OOPlanetEntity alloc] initFromDictionary:dict withAtmosphere:NO andSeed:[[UNIVERSE systemManager] getRandomSeedForCurrentSystem] forSystem:system_id] autorelease];
2374
2375 Quaternion planetOrientation;
2376 if (ScanQuaternionFromString([dict objectForKey:@"orientation"], &planetOrientation))
2377 {
2378 [planet setOrientation:planetOrientation];
2379 }
2380
2381 if (![dict objectForKey:@"position"])
2382 {
2383 OOLog(@"script.error.addPlanet.noPosition", @"***** ERROR: you must specify a position for scripted moon '%@' before it can be created", moonKey);
2384 return nil;
2385 }
2386
2387 NSString *positionString = [dict objectForKey:@"position"];
2388 if([positionString hasPrefix:@"abs "] && ([UNIVERSE planet] != nil || [UNIVERSE sun] !=nil))
2389 {
2390 OOLogWARN(@"script.deprecated", @"setting %@ for %@ '%@' in 'abs' inside .plists can cause compatibility issues across Oolite versions. Use coordinates relative to main system objects instead.",@"position",@"moon",moonKey);
2391 }
2392 HPVector posn = [UNIVERSE coordinatesFromCoordinateSystemString:positionString];
2393 if (posn.x || posn.y || posn.z)
2394 {
2395 OOLog(kOOLogDebugAddPlanet, @"moon position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString);
2396 }
2397 else
2398 {
2399 ScanHPVectorFromString(positionString, &posn);
2400 OOLog(kOOLogDebugAddPlanet, @"moon position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString);
2401 }
2402 [planet setPosition: posn];
2403
2404 [UNIVERSE addEntity:planet];
2405 return planet;
2406}
2407
2408
2409- (void) debugOn
2410{
2412 OOLog(kOOLogDebugOnOff, @"%@", @"SCRIPT debug messages ON");
2413}
2414
2415
2416- (void) debugOff
2417{
2418 OOLog(kOOLogDebugOnOff, @"%@", @"SCRIPT debug messages OFF");
2420}
2421
2422
2423- (void) debugMessage:(NSString *)args
2424{
2425 OOLog(kOOLogDebugMessage, @"SCRIPT debugMessage: %@", args);
2426}
2427
2428
2429- (void) playSound:(NSString *) soundName
2430{
2431 [self playLegacyScriptSound:soundName];
2432}
2433
2434/*-----------------------------------------------------*/
2435
2436
2437- (void) doMissionCallback
2438{
2439 // make sure we don't call the same callback twice
2440 _missionWithCallback = NO;
2442}
2443
2444
2445- (void) clearMissionScreenID
2446{
2447 [_missionScreenID release];
2448 _missionScreenID = nil;
2449}
2450
2451
2452- (void) setMissionScreenID:(NSString *)msid
2453{
2454 _missionScreenID = [msid retain];
2455}
2456
2457
2458- (NSString *) missionScreenID
2459{
2460 return _missionScreenID;
2461}
2462
2463
2464- (void) endMissionScreenAndNoteOpportunity
2465{
2466 _missionAllowInterrupt = NO;
2467 [self clearMissionScreenID];
2468 // Older scripts might intercept missionScreenEnded first, and call secondary mission screens.
2469 if(![self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")])
2470 {
2471 // if we're here, no mission screen is running. Opportunity! :)
2472 [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")];
2473 }
2474}
2475
2476
2477- (void) setGuiToMissionScreen
2478{
2479 // reset special background as legacy scripts can't use it, and this
2480 // is only called by legacy scripts
2481 [self setMissionBackgroundSpecial:nil];
2482 // likewise exit screen target
2483 [self setMissionExitScreen:GUI_SCREEN_STATUS];
2484
2485 [self setGuiToMissionScreenWithCallback:NO];
2486}
2487
2488
2489- (void) refreshMissionScreenTextEntry
2490{
2491 MyOpenGLView *gameView = [UNIVERSE gameView];
2492 GuiDisplayGen *gui = [UNIVERSE gui];
2493 NSUInteger end_row = 21;
2494 if ([[self hud] allowBigGui])
2495 {
2496 end_row = 27;
2497 }
2498
2499 [gui setText:[NSString stringWithFormat:DESC(@"mission-screen-text-prompt-@"), [gameView typedString]] forRow:end_row align:GUI_ALIGN_LEFT];
2500 [gui setColor:[OOColor cyanColor] forRow:end_row];
2501
2502 [gui setShowTextCursor:YES];
2503 [gui setCurrentRow:end_row];
2504
2505}
2506
2507
2508- (void) setGuiToMissionScreenWithCallback:(BOOL) callback
2509{
2510 GuiDisplayGen *gui = [UNIVERSE gui];
2511 OOGUIScreenID oldScreen = gui_screen;
2512 NSUInteger end_row = 21;
2513 if ([[self hud] allowBigGui])
2514 {
2515 end_row = 27;
2516 }
2517
2518 // GUI stuff
2519 {
2520 [gui clear];
2521 [gui setTitle:[self missionTitle] ?: DESC(@"mission-information")];
2522
2523 if (!_missionTextEntry)
2524 {
2525 [gui setText:DESC(@"press-space-commander") forRow:end_row align:GUI_ALIGN_CENTER];
2526 [gui setColor:[OOColor yellowColor] forRow:end_row];
2527 [gui setKey:@"spacebar" forRow:end_row];
2528 [gui setShowTextCursor:NO];
2529 }
2530 else
2531 {
2532 [self refreshMissionScreenTextEntry];
2533 }
2534 [gui setSelectableRange:NSMakeRange(0,0)];
2535
2536 [gui setForegroundTextureDescriptor:[self missionOverlayDescriptorOrDefault]];
2537 NSDictionary *background_desc = [self missionBackgroundDescriptorOrDefault];
2538 [gui setBackgroundTextureDescriptor:background_desc];
2539 // must set special second as setting the descriptor resets it
2540 BOOL overridden = ([self missionBackgroundDescriptor] != nil);
2541 [gui setBackgroundTextureSpecial:[self missionBackgroundSpecial] withBackground:!overridden];
2542
2543
2544 }
2545 /* ends */
2546
2547 missionTextRow = 1;
2548
2549
2550 if (gui)
2551 gui_screen = GUI_SCREEN_MISSION;
2552
2553 if (lastTextKey)
2554 {
2555 [lastTextKey release];
2556 lastTextKey = nil;
2557 }
2558
2560
2561 // the following are necessary...
2562 [UNIVERSE enterGUIViewModeWithMouseInteraction:NO];
2563 _missionWithCallback = callback;
2564 _missionAllowInterrupt = NO;
2565 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
2566
2567}
2568
2569
2570- (void) setBackgroundFromDescriptionsKey:(NSString*) d_key
2571{
2572 NSArray * items = (NSArray *)[[UNIVERSE descriptions] objectForKey:d_key];
2573 //
2574 if (!items)
2575 return;
2576 //
2577 [self addScene: items atOffset: kZeroVector];
2578 //
2579 [self setShowDemoShips: YES];
2580}
2581
2582
2583- (void) addScene:(NSArray *)items atOffset:(Vector)off
2584{
2585 unsigned i;
2586
2587 if (items == nil) return;
2588
2589 for (i = 0; i < [items count]; i++)
2590 {
2591 id item = [items objectAtIndex:i];
2592 if ([item isKindOfClass:[NSString class]])
2593 {
2594 [self processSceneString:item atOffset: off];
2595 }
2596 else if ([item isKindOfClass:[NSArray class]])
2597 {
2598 [self addScene:item atOffset: off];
2599 }
2600 else if ([item isKindOfClass:[NSDictionary class]])
2601 {
2602 [self processSceneDictionary:item atOffset: off];
2603 }
2604 }
2605}
2606
2607
2608- (BOOL) processSceneDictionary:(NSDictionary *) couplet atOffset:(Vector) off
2609{
2610 NSArray *conditions = [couplet objectForKey:@"conditions"];
2611 NSArray *actions = nil;
2612 if ([couplet objectForKey:@"do"])
2613 actions = [NSArray arrayWithObject: [couplet objectForKey:@"do"]];
2614 NSArray *else_actions = nil;
2615 if ([couplet objectForKey:@"else"])
2616 else_actions = [NSArray arrayWithObject: [couplet objectForKey:@"else"]];
2617 BOOL success = YES;
2618 if (conditions == nil)
2619 {
2620 OOLog(@"script.scene.couplet.badConditions", @"***** SCENE ERROR: %@ - conditions not %@, returning %@.", [couplet description], @" found",@"YES and performing 'do' actions");
2621 }
2622 else
2623 {
2624 if (![conditions isKindOfClass:[NSArray class]])
2625 {
2626 OOLog(@"script.scene.couplet.badConditions", @"***** SCENE ERROR: %@ - conditions not %@, returning %@.", [conditions description], @"an array",@"NO");
2627 return NO;
2628 }
2629 }
2630
2631 // check conditions..
2632 success = TestScriptConditions(OOSanitizeLegacyScriptConditions(conditions, @"<scene dictionary conditions>"));
2633
2634 // perform successful actions...
2635 if ((success) && (actions) && [actions count])
2636 [self addScene: actions atOffset: off];
2637
2638 // perform unsuccessful actions
2639 if ((!success) && (else_actions) && [else_actions count])
2640 [self addScene: else_actions atOffset: off];
2641
2642 return success;
2643}
2644
2645
2646- (BOOL) processSceneString:(NSString*) item atOffset:(Vector) off
2647{
2648 Vector model_p0;
2649 Quaternion model_q;
2650
2651 if (!item)
2652 return NO;
2653 NSArray * i_info = ScanTokensFromString(item);
2654 if (!i_info)
2655 return NO;
2656 NSString* i_key = [(NSString*)[i_info objectAtIndex:0] lowercaseString];
2657
2658 OOLog(kOOLogNoteProcessSceneString, @"..... processing %@ (%@)", i_info, i_key);
2659
2660 //
2661 // recursively add further scenes:
2662 //
2663 if ([i_key isEqualToString:@"scene"])
2664 {
2665 if ([i_info count] != 5) // must be scene_key_x_y_z
2666 return NO; // 0.... 1.. 2 3 4
2667 NSString* scene_key = (NSString*)[i_info objectAtIndex: 1];
2668 Vector scene_offset = {0};
2669 ScanVectorFromString([[i_info subarrayWithRange:NSMakeRange(2, 3)] componentsJoinedByString:@" "], &scene_offset);
2670 scene_offset.x += off.x; scene_offset.y += off.y; scene_offset.z += off.z;
2671 NSArray * scene_items = (NSArray *)[[UNIVERSE descriptions] objectForKey:scene_key];
2672 OOLog(kOOLogDebugProcessSceneStringAddScene, @"::::: adding scene: '%@'", scene_key);
2673 //
2674 if (scene_items)
2675 {
2676 [self addScene: scene_items atOffset: scene_offset];
2677 return YES;
2678 }
2679 else
2680 return NO;
2681 }
2682 //
2683 // Add ship models:
2684 //
2685 if ([i_key isEqualToString:@"ship"]||[i_key isEqualToString:@"model"]||[i_key isEqualToString:@"role"])
2686 {
2687 if ([i_info count] != 10) // must be item_name_x_y_z_W_X_Y_Z_align
2688 {
2689 return NO; // 0... 1... 2 3 4 5 6 7 8 9....
2690 }
2691
2692 ShipEntity* ship = nil;
2693
2694 if ([i_key isEqualToString:@"ship"]||[i_key isEqualToString:@"model"])
2695 {
2696 ship = [UNIVERSE newShipWithName:[i_info oo_stringAtIndex: 1]];
2697 }
2698 else if ([i_key isEqualToString:@"role"])
2699 {
2700 ship = [UNIVERSE newShipWithRole:[i_info oo_stringAtIndex: 1]];
2701 }
2702 if (!ship)
2703 return NO;
2704
2705 ScanVectorAndQuaternionFromString([[i_info subarrayWithRange:NSMakeRange(2, 7)] componentsJoinedByString:@" "], &model_p0, &model_q);
2706
2707 Vector model_offset = positionOffsetForShipInRotationToAlignment(ship, model_q, [i_info oo_stringAtIndex:9]);
2708 model_p0 = vector_add(model_p0, vector_subtract(off, model_offset));
2709
2710 OOLog(kOOLogDebugProcessSceneStringAddModel, @"::::: adding model to scene:'%@'", ship);
2711 [ship setOrientation: model_q];
2712 [ship setPosition: vectorToHPVector(model_p0)];
2713 [UNIVERSE setMainLightPosition:(Vector){ DEMO_LIGHT_POSITION }]; // set light origin
2714 [ship setScanClass: CLASS_NO_DRAW];
2715 [ship switchAITo: @"nullAI.plist"];
2716 [UNIVERSE addEntity: ship]; // STATUS_IN_FLIGHT, AI state GLOBAL
2717 [ship setStatus: STATUS_COCKPIT_DISPLAY];
2718 [ship setRoll: 0.0];
2719 [ship setPitch: 0.0];
2720 [ship setVelocity: kZeroVector];
2721 [ship setBehaviour: BEHAVIOUR_STOP_STILL];
2722
2723 [ship release];
2724 return YES;
2725 }
2726 //
2727 // Add player ship model:
2728 //
2729 if ([i_key isEqualToString:@"player"])
2730 {
2731 if ([i_info count] != 9) // must be player_x_y_z_W_X_Y_Z_align
2732 return NO; // 0..... 1 2 3 4 5 6 7 8....
2733
2734 ShipEntity* doppelganger = [UNIVERSE newShipWithName:[self shipDataKey]]; // retain count = 1
2735 if (!doppelganger)
2736 return NO;
2737
2738 ScanVectorAndQuaternionFromString([[i_info subarrayWithRange:NSMakeRange( 1, 7)] componentsJoinedByString:@" "], &model_p0, &model_q);
2739
2740 Vector model_offset = positionOffsetForShipInRotationToAlignment( doppelganger, model_q, (NSString*)[i_info objectAtIndex:8]);
2741 model_p0.x += off.x - model_offset.x;
2742 model_p0.y += off.y - model_offset.y;
2743 model_p0.z += off.z - model_offset.z;
2744
2745 OOLog(kOOLogDebugProcessSceneStringAddModel, @"::::: adding model to scene:'%@'", doppelganger);
2746 [doppelganger setOrientation: model_q];
2747 [doppelganger setPosition: vectorToHPVector(model_p0)];
2748 [UNIVERSE setMainLightPosition:(Vector){ DEMO_LIGHT_POSITION }]; // set light origin
2749 [doppelganger setScanClass: CLASS_NO_DRAW];
2750 [doppelganger switchAITo: @"nullAI.plist"];
2751 [UNIVERSE addEntity: doppelganger];
2752 [doppelganger setStatus: STATUS_COCKPIT_DISPLAY];
2753 [doppelganger setRoll: 0.0];
2754 [doppelganger setPitch: 0.0];
2755 [doppelganger setVelocity: kZeroVector];
2756 [doppelganger setBehaviour: BEHAVIOUR_STOP_STILL];
2757
2758 [doppelganger release];
2759 return YES;
2760 }
2761 //
2762 // Add planet model: selected via gui-scene-show-planet/-local-planet
2763 //
2764 if ([i_key isEqualToString:@"local-planet"] || [i_key isEqualToString:@"target-planet"])
2765 {
2766 if ([i_info count] != 4) // must be xxxxx-planet_x_y_z
2767 return NO; // 0........... 1 2 3
2768
2769 // sunlight position for F7 screen is chosen pseudo randomly from 4 different positions.
2770 if (info_system_id & 8)
2771 {
2772 _sysInfoLight = (info_system_id & 2) ? (Vector){ -10000.0, 4000.0, -10000.0 } : (Vector){ -12000.0, -5000.0, -10000.0 };
2773 }
2774 else
2775 {
2776 _sysInfoLight = (info_system_id & 2) ? (Vector){ 6000.0, -5000.0, -10000.0 } : (Vector){ 6000.0, 4000.0, -10000.0 };
2777 }
2778
2779 [UNIVERSE setMainLightPosition:_sysInfoLight]; // set light origin
2780
2781#if NEW_PLANETS
2782 OOPlanetEntity *originalPlanet = nil;
2783 if ([i_key isEqualToString:@"local-planet"] && [UNIVERSE sun])
2784 {
2785 originalPlanet = [UNIVERSE planet];
2786 }
2787 else
2788 {
2789 originalPlanet = [[[OOPlanetEntity alloc] initAsMainPlanetForSystem:info_system_id] autorelease];
2790 }
2791 OOPlanetEntity *doppelganger = [originalPlanet miniatureVersion];
2792 if (doppelganger == nil) return NO;
2793
2794#else
2795 OOPlanetEntity* doppelganger = nil;
2796 NSMutableDictionary *planetInfo = [NSMutableDictionary dictionaryWithDictionary:[UNIVERSE generateSystemData:target_system_seed]];
2797
2798 if ([i_key isEqualToString:@"local-planet"] && [UNIVERSE sun])
2799 {
2800 OOPlanetEntity *mainPlanet = [UNIVERSE planet];
2801 OOTexture *texture = [mainPlanet texture];
2802 if (texture != nil)
2803 {
2804 [planetInfo setObject:texture forKey:@"_oo_textureObject"];
2805 [planetInfo oo_setBool:[mainPlanet isExplicitlyTextured] forKey:@"_oo_isExplicitlyTextured"];
2806 [planetInfo oo_setBool:YES forKey:@"mainForLocalSystem"];
2807 //[planetInfo oo_setQuaternion:[mainPlanet orientation] forKey:@"orientation"]; // the orientation is overwritten later on, without regard for the real planet's orientation.
2808 }
2809 }
2810
2811 doppelganger = [[OOPlanetEntity alloc] initFromDictionary:planetInfo withAtmosphere:YES andSeed:target_system_seed];
2812 [doppelganger miniaturize];
2813 [doppelganger autorelease];
2814
2815 if (doppelganger == nil) return NO;
2816#endif
2817
2818 ScanVectorFromString([[i_info subarrayWithRange:NSMakeRange(1, 3)] componentsJoinedByString:@" "], &model_p0);
2819
2820 // miniature radii are roughly between 60 and 120. Place miniatures with a radius bigger than 60 a bit futher away.
2821 model_p0 = vector_multiply_scalar(model_p0, 1 - 0.5 * ((60 - [doppelganger radius]) / 60));
2822
2823 model_p0 = vector_add(model_p0, off);
2824
2825 // TODO: find better quaternion values.
2826#if NEW_PLANETS
2827 //Quaternion model_q = { 0.83, 0.365148, 0.182574, 0.0 }; // shows new planets' north pole.
2828 //Quaternion model_q = { 0.83, -0.365148, 0.182574, 0.0 }; // shows new planets' south pole.
2829 Quaternion model_q = { 0.83, 0.12, 0.44, 0.0 }; // new planets - default orientation.
2830#else
2831 //model_q = make_quaternion( M_SQRT1_2, 0.314, M_SQRT1_2, 0.0 );
2832 Quaternion model_q = { 0.833492, 0.333396, 0.440611, 0.0 };
2833#endif
2834 OOLog(kOOLogDebugProcessSceneStringAddMiniPlanet, @"::::: adding %@ to scene:'%@'", i_key, doppelganger);
2835 [doppelganger setOrientation: model_q];
2836 // HPVect: mission screen coordinates are small enough that we don't need high-precision for calculations
2837 [doppelganger setPosition: vectorToHPVector(model_p0)];
2838 /* MKW - add rotation based on current time
2839 * - necessary to duplicate the rotation already performed in PlanetEntity.m since we reset the orientation above. */
2840 int deltaT = floor(fmod([self clockTimeAdjusted], 86400));
2841 [doppelganger update: deltaT];
2842 [UNIVERSE addEntity:doppelganger];
2843
2844 return YES;
2845 }
2846
2847 return NO;
2848}
2849
2850
2851- (BOOL) addEqScriptForKey:(NSString *)eq_key
2852{
2853 if (eq_key == nil) return NO;
2854
2855 NSString *scriptName = [[OOEquipmentType equipmentTypeWithIdentifier:eq_key] scriptName];
2856
2857 OOLog(@"player.equipmentScript", @"Added equipment %@, with the following script property: '%@'.", eq_key, scriptName);
2858
2859 if (scriptName == nil) return NO;
2860
2861 NSMutableDictionary *properties = [NSMutableDictionary dictionary];
2862
2863 // no duplicates!
2864 NSArray *eqScript = nil;
2865 foreach (eqScript, eqScripts)
2866 {
2867 NSString *key = [eqScript oo_stringAtIndex:0];
2868 if ([key isEqualToString: eq_key]) return NO;
2869 }
2870
2871 [properties setObject:self forKey:@"ship"];
2872 [properties setObject:eq_key forKey:@"equipmentKey"];
2873 OOScript *s = [OOScript jsScriptFromFileNamed:scriptName properties:properties];
2874 if (s == nil) return NO;
2875
2876 OOLog(@"player.equipmentScript", @"Script '%@': installation %@successful.", scriptName,(s == nil ? @"un" : @""));
2877
2878 [eqScripts addObject:[NSArray arrayWithObjects:eq_key,s,nil]];
2879 if (primedEquipment == [eqScripts count] - 1) primedEquipment++; // if primed-none, keep it as primed-none.
2880 OOLog(@"player.equipmentScript", @"Scriptable equipment available: %llu.", [eqScripts count]);
2881 return YES;
2882}
2883
2884
2885- (void) removeEqScriptForKey:(NSString *)eq_key
2886{
2887 if (eq_key == nil) return;
2888
2889 NSString *key = nil;
2890 NSUInteger i, count = [eqScripts count];
2891
2892 for (i = 0; i < count; i++)
2893 {
2894 key = [[eqScripts oo_arrayAtIndex:i] oo_stringAtIndex:0];
2895 if ([key isEqualToString: eq_key])
2896 {
2897 [eqScripts removeObjectAtIndex:i];
2898
2899 if (i == primedEquipment) primedEquipment = count; // primed-none
2900 else if (i < primedEquipment) primedEquipment--; // track the primed equipment
2901 if (count == primedEquipment) primedEquipment--; // the array has shrunk by one!
2902
2903 OOLog(@"player.equipmentScript", @"Removed equipment %@, with the following script property: '%@'.", eq_key, [[OOEquipmentType equipmentTypeWithIdentifier:eq_key] scriptName]);
2904 }
2905 }
2906}
2907
2908
2909- (NSUInteger) eqScriptIndexForKey:(NSString *)eq_key
2910{
2911 NSUInteger i, count = [eqScripts count];
2912
2913 if (eq_key != nil)
2914 {
2915 for (i = 0; i < count; i++)
2916 {
2917 NSString *key = [[eqScripts oo_arrayAtIndex:i] oo_stringAtIndex:0];
2918 if ([key isEqualToString: eq_key]) return i;
2919 }
2920 }
2921
2922 return count;
2923}
2924
2925
2926- (void) targetNearestHostile
2927{
2928 [self scanForHostiles];
2929 Entity *ent = [self foundTarget];
2930 if (ent != nil)
2931 {
2932 ident_engaged = YES;
2933 missile_status = MISSILE_STATUS_TARGET_LOCKED;
2934 [self addTarget:ent];
2935 }
2936}
2937
2938
2939- (void) targetNearestIncomingMissile
2940{
2941 [self scanForNearestIncomingMissile];
2942 Entity *ent = [self foundTarget];
2943 if (ent != nil)
2944 {
2945 ident_engaged = YES;
2946 missile_status = MISSILE_STATUS_TARGET_LOCKED;
2947 [self addTarget:ent];
2948 }
2949}
2950
2951
2952- (void) setGalacticHyperspaceBehaviourTo:(NSString *)galacticHyperspaceBehaviourString
2953{
2954 OOGalacticHyperspaceBehaviour ghBehaviour = OOGalacticHyperspaceBehaviourFromString(galacticHyperspaceBehaviourString);
2955 if (ghBehaviour == GALACTIC_HYPERSPACE_BEHAVIOUR_UNKNOWN)
2956 {
2957 OOLog(@"player.setGalacticHyperspaceBehaviour.invalidInput",
2958 @"setGalacticHyperspaceBehaviourTo: called with unknown behaviour %@.", galacticHyperspaceBehaviourString);
2959 }
2960 [self setGalacticHyperspaceBehaviour:ghBehaviour];
2961}
2962
2963
2964- (void) setGalacticHyperspaceFixedCoordsTo:(NSString *)galacticHyperspaceFixedCoordsString
2965{
2966 NSArray *coord_vals = ScanTokensFromString(galacticHyperspaceFixedCoordsString);
2967 if ([coord_vals count] < 2) // Will be 0 if string is nil
2968 {
2969 OOLog(@"player.setGalacticHyperspaceFixedCoords.invalidInput", @"%@",
2970 @"setGalacticHyperspaceFixedCoords: called with bad specifier. Defaulting to Oolite standard.");
2971 galacticHyperspaceFixedCoords.x = galacticHyperspaceFixedCoords.y = 0x60;
2972 }
2973
2974 [self setGalacticHyperspaceFixedCoordsX:[coord_vals oo_unsignedCharAtIndex:0]
2975 y:[coord_vals oo_unsignedCharAtIndex:1]];
2976}
2977
2978@end
2979
2980
2982{
2983 switch (type)
2984 {
2985 case COMPARISON_EQUAL: return @"equal";
2986 case COMPARISON_NOTEQUAL: return @"notequal";
2987 case COMPARISON_LESSTHAN: return @"lessthan";
2988 case COMPARISON_GREATERTHAN: return @"greaterthan";
2989 case COMPARISON_ONEOF: return @"oneof";
2990 case COMPARISON_UNDEFINED: return @"undefined";
2991 }
2992 return @"<error: invalid comparison type>";
2993}
OOEntityStatus
Definition Entity.h:60
NSString * OOStringFromEntityStatus(OOEntityStatus status) CONST_FUNC
NSInteger OOGUIRow
OOGUIAlignment
@ GUI_ALIGN_RIGHT
@ GUI_ALIGN_LEFT
@ GUI_ALIGN_CENTER
#define DESTROY(x)
Definition OOCocoa.h:75
#define foreachkey(VAR, DICT)
Definition OOCocoa.h:353
NSString * OODisplayStringFromEconomyID(OOEconomyID economy)
NSString * OODisplayStringFromGovernmentID(OOGovernmentID government)
#define OOINLINE
#define OOJSID(str)
Definition OOJSPropID.h:38
NSArray * OOSanitizeLegacyScriptConditions(NSArray *conditions, NSString *context)
#define OOLogWARN(class, format,...)
Definition OOLogging.h:113
#define OOLogERR(class, format,...)
Definition OOLogging.h:112
NSString *const kOOLogException
Definition OOLogging.m:651
#define OOLog(class, format,...)
Definition OOLogging.h:88
void OOLogSetDisplayMessagesInClass(NSString *inClass, BOOL inFlag)
Definition OOLogging.m:182
return self
unsigned count
return nil
float y
float x
@ kOOExpandBackslashN
Convert literal "\\n"s to line breaks (used for missiontext.plist for historical reasons).
@ kOOExpandNoOptions
Random_Seed OOStringExpanderDefaultRandomSeed(void)
#define OOExpandWithOptions(seed, options, string,...)
NSString * OOExpandDescriptionString(Random_Seed seed, NSString *string, NSDictionary *overrides, NSDictionary *legacyLocals, NSString *systemName, OOExpandOptions options)
#define OOExpand(string,...)
NSMutableArray * ScanTokensFromString(NSString *values)
BOOL ScanVectorAndQuaternionFromString(NSString *xyzwxyzString, Vector *outVector, Quaternion *outQuaternion)
BOOL ScanHPVectorFromString(NSString *xyzString, HPVector *outVector)
BOOL ScanQuaternionFromString(NSString *wxyzString, Quaternion *outQuaternion)
BOOL ScanVectorFromString(NSString *xyzString, Vector *outVector)
uint16_t OOFuelQuantity
Definition OOTypes.h:179
NSString * OOCommodityType
Definition OOTypes.h:106
uint64_t OOCreditsQuantity
Definition OOTypes.h:182
int32_t OOCargoQuantityDelta
Definition OOTypes.h:177
OOMassUnit
Definition OOTypes.h:123
@ UNITS_TONS
Definition OOTypes.h:124
NSString * OOComparisonTypeToString(OOComparisonType type) CONST_FUNC
static NSString *const kOOLogSyntaxSet
static NSString *const kOOLogRemoveAllCargoNotDocked
static NSString *const kOOLogSyntaxIncrement
static NSString *const kOOLogNoteSet
static NSString *const kOOLogDebugOnOff
static NSString *const kOOLogNoteUseSpecialCargo
static NSString *const kOOLogSyntaxSetPlanetInfo
static NSString * sCurrentMissionKey
static NSString *const kOOLogSyntaxReset
static NSString *const kOOLogNoteRemoveAllCargo
static NSString *const kOOLogSyntaxAwardEquipment
#define DOUBLEVAL(x)
static NSString *const kOOLogSyntaxDecrement
static NSString *const kOOLogDebugReplaceVariablesInString
static NSString *const kOOLogSyntaxSubtract
static NSString *const kOOLogDebugProcessSceneStringAddMiniPlanet
static NSString *const kOOLogDebugProcessSceneStringAddModel
static NSString *const kOOLogSyntaxRemoveEquipment
static NSString *const kOOLogDebugAddPlanet
static NSString *const kOOLogScriptMissionDescNoKey
static ShipEntity * scriptTarget
static NSString *const kOOLogScriptMissionDescNoText
static NSString *const kOOLogDebugOnMetaClass
static NSString *const kOOLogNoteShowShipModel
static NSString *const kOOLogDebugProcessSceneStringAddScene
static NSString *const kOOLogSyntaxAwardCargo
static NSString *const kOOLogNoteAddShips
static BOOL sRunningScript
static NSString *const kOOLogDebugMessage
static int shipsFound
static NSString * sMissionStringValue
static NSString *const kOOLogScriptAddShipsFailed
#define ACTIONS_TEMP_PREFIX
static NSString *const kOOLogSyntaxMessageShipAIs
static NSString *const kOOLogSyntaxAdd
static NSString *const kOOLogNoteFuelLeak
static NSString *const kOOLogNoteAddPlanet
static NSString *const kOOLogSyntaxAddShips
static NSString *const kOOLogNoteProcessSceneString
static NSString *const kActionTempPrefix
OOGalacticHyperspaceBehaviour
OOGUIScreenID
OOGalacticHyperspaceBehaviour OOGalacticHyperspaceBehaviourFromString(NSString *string) PURE_FUNC
NSString * OODisplayRatingStringFromKillCount(unsigned kills)
#define PLAYER
#define SCRIPT_TIMER_INTERVAL
@ MISSILE_STATUS_TARGET_LOCKED
NSString * OODisplayStringFromLegalStatus(int legalStatus)
NSString * OOStringFromGUIScreenID(OOGUIScreenID screen) CONST_FUNC
#define UNIVERSE
Definition Universe.h:842
static NSString * CurrentScriptNameOr(NSString *alternative)
OOINLINE OOEntityStatus RecursiveRemapStatus(OOEntityStatus status)
Definition AI.h:38
void setNextThinkTime:(OOTimeAbsolute ntt)
Definition AI.m:658
void setState:(NSString *stateName)
Definition AI.m:334
void reactToMessage:context:(NSString *message,[context] NSString *debugContext)
Definition AI.m:404
void setVelocity:(Vector vel)
Definition Entity.m:758
void setOrientation:(Quaternion quat)
Definition Entity.m:726
OOScanClass scanClass
Definition Entity.h:106
void setScanClass:(OOScanClass sClass)
Definition Entity.m:800
void setPosition:(HPVector posn)
Definition Entity.m:648
BOOL setSelectedRow:(OOGUIRow row)
OOGUIRow addLongText:startingAtRow:align:(NSString *str,[startingAtRow] OOGUIRow row,[align] OOGUIAlignment alignment)
void setText:forRow:(NSString *str,[forRow] OOGUIRow row)
void setText:forRow:align:(NSString *str,[forRow] OOGUIRow row,[align] OOGUIAlignment alignment)
BOOL setForegroundTextureDescriptor:(NSDictionary *descriptor)
void setSelectableRange:(NSRange range)
void setBackgroundTextureSpecial:withBackground:(OOGUIBackgroundSpecial spec,[withBackground] BOOL withBackground)
void setColor:forRow:(OOColor *color,[forRow] OOGUIRow row)
void setTitle:(NSString *str)
void setShowTextCursor:(BOOL yesno)
void setCurrentRow:(OOGUIRow value)
BOOL setBackgroundTextureDescriptor:(NSDictionary *descriptor)
void setKey:forRow:(NSString *str,[forRow] OOGUIRow row)
void resetTypedString()
NSMutableString * typedString
OOColor * cyanColor()
Definition OOColor.m:286
OOColor * darkGrayColor()
Definition OOColor.m:244
OOColor * colorWithDescription:(id description)
Definition OOColor.m:127
OOColor * yellowColor()
Definition OOColor.m:292
NSString * scriptName()
OOEquipmentType * equipmentTypeWithIdentifier:(NSString *identifier)
OOJavaScriptEngine * sharedEngine()
OOMusicController * sharedController()
void setMissionMusic:(NSString *missionMusicName)
instancetype miniatureVersion()
void setOrientation:(Quaternion quat)
void update:(OOTimeDelta delta_t)
id jsScriptFromFileNamed:properties:(NSString *fileName,[properties] NSDictionary *properties)
Definition OOScript.m:191
NSMutableDictionary * localVariablesForMission:(NSString *missionKey)
void setFuel:(OOFuelQuantity amount)
void setStatus:(OOEntityStatus stat)
void setRoll:(double amount)
void setPrimaryRole:(NSString *role)
OOFuelQuantity fuel
Definition ShipEntity.h:288
OOFuelQuantity fuelCapacity()
void setPitch:(double amount)
void setAITo:(NSString *aiString)
ShipEntity * ejectShipOfType:(NSString *shipKey)
void setBehaviour:(OOBehaviour cond)
void switchAITo:(NSString *aiString)
OOTechLevelID equivalentTechLevel
void takeEnergyDamage:from:becauseOf:weaponIdentifier:(double amount, [from] Entity *ent, [becauseOf] Entity *other, [weaponIdentifier] NSString *weaponIdentifier)
void seed_RNG_only_for_planet_description(Random_Seed s_seed)
#define ranrot_rand()