Oolite 1.91.0.7604-240417-a536cbe
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 NSEnumerator *componentEnum = nil;
614 NSArray *component = nil;
615 NSString *value = nil;
616
617 result = [NSMutableArray arrayWithCapacity:[rhsComponents count]];
618
619 for (componentEnum = [rhsComponents objectEnumerator]; (component = [componentEnum nextObject]); )
620 {
621 /* Each component is a two-element array. The second element is a
622 string. The first element is a boolean indicating whether the
623 string is a selector (true) or a literal (false).
624
625 All valid selectors return a string or an NSNumber; in either
626 case, -description gives us a useful value to substitute into
627 the expanded string.
628 */
629
630 value = [component oo_stringAtIndex:1];
631
632 if ([[component objectAtIndex:0] boolValue])
633 {
634 value = [[self performSelector:NSSelectorFromString(value)] description];
635 if (value == nil) value = @"(null)"; // for backwards compatibility
636 }
637
638 [result addObject:value];
639 }
640
641 return [result componentsJoinedByString:@" "];
642}
643
644
645- (NSDictionary *) missionVariables
646{
647 return mission_variables;
648}
649
650
651- (NSString *)missionVariableForKey:(NSString *)key
652{
653 NSString *result = nil;
654 if (key != nil) result = [mission_variables objectForKey:key];
655 return result;
656}
657
658
659- (void)setMissionVariable:(NSString *)value forKey:(NSString *)key
660{
661 if (key != nil)
662 {
663 if (value != nil) [mission_variables setObject:value forKey:key];
664 else [mission_variables removeObjectForKey:key];
665 }
666}
667
668
669- (NSMutableDictionary *)localVariablesForMission:(NSString *)missionKey
670{
671 NSMutableDictionary *result = nil;
672
673 if (missionKey == nil) return nil;
674
675 result = [localVariables objectForKey:missionKey];
676 if (result == nil)
677 {
678 result = [NSMutableDictionary dictionary];
679 [localVariables setObject:result forKey:missionKey];
680 }
681
682 return result;
683}
684
685
686- (NSString *)localVariableForKey:(NSString *)variableName andMission:(NSString *)missionKey
687{
688 return [[localVariables oo_dictionaryForKey:missionKey] objectForKey:variableName];
689}
690
691
692- (void)setLocalVariable:(NSString *)value forKey:(NSString *)variableName andMission:(NSString *)missionKey
693{
694 NSMutableDictionary *locals = nil;
695
696 if (variableName != nil && missionKey != nil)
697 {
698 locals = [self localVariablesForMission:missionKey];
699 if (value != nil)
700 {
701 [locals setObject:value forKey:variableName];
702 }
703 else
704 {
705 [locals removeObjectForKey:variableName];
706 }
707 }
708}
709
710
711- (NSArray *) missionsList
712{
713 NSEnumerator *scriptEnum = nil;
714 NSString *scriptName = nil;
715 NSString *vars = nil;
716 NSMutableArray *result1 = nil;
717 NSMutableArray *result2 = nil;
718
719 result1 = [NSMutableArray array];
720 result2 = [NSMutableArray array];
721
722 NSArray* passengerManifest = [self passengerList];
723 NSArray* contractManifest = [self contractList];
724 NSArray* parcelManifest = [self parcelList];
725
726 if ([passengerManifest count] > 0)
727 {
728 [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-passengers")] arrayByAddingObjectsFromArray:passengerManifest]];
729 }
730
731 if ([parcelManifest count] > 0)
732 {
733 [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-parcels")] arrayByAddingObjectsFromArray:parcelManifest]];
734 }
735
736 if ([contractManifest count] > 0)
737 {
738 [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-contracts")] arrayByAddingObjectsFromArray:contractManifest]];
739 }
740
741 /* For proper display, array entries need to all be after string
742 * entries, so sort them now */
743 for (scriptEnum = [worldScripts keyEnumerator]; (scriptName = [scriptEnum nextObject]); )
744 {
745 vars = [mission_variables objectForKey:scriptName];
746
747 if (vars != nil)
748 {
749 if ([vars isKindOfClass:[NSString class]])
750 {
751 [result1 addObject:vars];
752 }
753 else if ([vars isKindOfClass:[NSArray class]])
754 {
755 BOOL found = NO;
756 NSArray *element = nil;
757 foreach (element, result2)
758 {
759 if ([[element oo_stringAtIndex:0] isEqualToString:[(NSArray*)vars oo_stringAtIndex:0]])
760 {
761
762 [result2 removeObject:element];
763 NSRange notTheHeader;
764 notTheHeader.location = 1;
765 notTheHeader.length = [(NSArray*)vars count]-1;
766 [result2 addObject:[element arrayByAddingObjectsFromArray:[(NSArray*)vars subarrayWithRange:notTheHeader]]];
767 found = YES;
768 break;
769 }
770 }
771 if (!found)
772 {
773 [result2 addObject:vars];
774 }
775 }
776 }
777 }
778 return [result1 arrayByAddingObjectsFromArray:result2];
779}
780
781
782- (NSString*) replaceVariablesInString:(NSString*) args
783{
784 NSMutableDictionary *locals = [self localVariablesForMission:sCurrentMissionKey];
785 NSMutableString *resultString = [NSMutableString stringWithString: args];
786 NSString *valueString;
787 unsigned i;
788 NSMutableArray *tokens = ScanTokensFromString(args);
789
790 for (i = 0; i < [tokens count]; i++)
791 {
792 valueString = [tokens objectAtIndex:i];
793
794 if ([valueString hasPrefix:@"mission_"] && [mission_variables objectForKey:valueString])
795 {
796 [resultString replaceOccurrencesOfString:valueString withString:[mission_variables objectForKey:valueString] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])];
797 }
798 else if ([locals objectForKey:valueString])
799 {
800 [resultString replaceOccurrencesOfString:valueString withString:[locals objectForKey:valueString] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])];
801 }
802 else if (([valueString hasSuffix:@"_number"])||([valueString hasSuffix:@"_bool"])||([valueString hasSuffix:@"_string"]))
803 {
804 SEL valueselector = NSSelectorFromString(valueString);
805 if ([self respondsToSelector:valueselector])
806 {
807 [resultString replaceOccurrencesOfString:valueString withString:[NSString stringWithFormat:@"%@", [self performSelector:valueselector]] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])];
808 }
809 }
810 else if ([valueString hasPrefix:@"["]&&[valueString hasSuffix:@"]"])
811 {
812 NSString* replaceString = OOExpand(valueString);
813 [resultString replaceOccurrencesOfString:valueString withString:replaceString options:NSLiteralSearch range:NSMakeRange(0, [resultString length])];
814 }
815 }
816
817 OOLog(kOOLogDebugReplaceVariablesInString, @"EXPANSION: \"%@\" becomes \"%@\"", args, resultString);
818
819 return [NSString stringWithString: resultString];
820}
821
822/*-----------------------------------------------------*/
823
824
825- (void) setMissionDescription:(NSString *)textKey
826{
827 [self setMissionDescription:textKey forMission:sCurrentMissionKey];
828}
829
830
831- (void) setMissionDescription:(NSString *)textKey forMission:(NSString *)key
832{
833 NSString *text = [[UNIVERSE missiontext] oo_stringForKey:textKey];
834
835 if (!text)
836 {
837 OOLogERR(kOOLogScriptMissionDescNoText, @"in %@, no mission text set for key '%@' [UNIVERSE missiontext] is:\n%@ ", CurrentScriptDesc(), textKey, [UNIVERSE missiontext]);
838 return;
839 }
840
841 [self setMissionInstructions:text forMission:key];
842}
843
844
845// implementation of mission.setInstructions(), also final part of legacy setMissionDescription
846- (void) setMissionInstructions:(NSString *)text forMission:(NSString *)key
847{
848 if (!key)
849 {
850 OOLogERR(kOOLogScriptMissionDescNoKey, @"in %@, mission key not set", CurrentScriptDesc());
851 return;
852 }
853
854 text = OOExpand(text);
855 text = [self replaceVariablesInString: text];
856
857 [mission_variables setObject:text forKey:key];
858}
859
860
861- (void) setMissionInstructionsList:(NSArray *)list forMission:(NSString *)key
862{
863 if (!key)
864 {
865 OOLogERR(kOOLogScriptMissionDescNoKey, @"in %@, mission key not set", CurrentScriptDesc());
866 return;
867 }
868
869 NSString *text = nil;
870 NSUInteger i,ct = [list count];
871 NSMutableArray *expandedList = [NSMutableArray arrayWithCapacity:ct];
872 for (i=0 ; i<ct ; i++)
873 {
874 text = [list oo_stringAtIndex:i defaultValue:nil];
875 if (text != nil)
876 {
877 text = OOExpand(text);
878 text = [self replaceVariablesInString: text];
879 [expandedList addObject:text];
880 }
881 }
882
883 [mission_variables setObject:expandedList forKey:key];
884}
885
886
887- (void) clearMissionDescription
888{
889 [self clearMissionDescriptionForMission:sCurrentMissionKey];
890}
891
892
893- (void) clearMissionDescriptionForMission:(NSString *)key
894{
895 if (!key)
896 {
897 OOLogERR(kOOLogScriptMissionDescNoKey, @"in %@, mission key not set", CurrentScriptDesc());
898 return;
899 }
900
901 if (![mission_variables objectForKey:key]) return;
902
903 [mission_variables removeObjectForKey:key];
904}
905
906
907- (NSString *) mission_string
908{
909 return sMissionStringValue;
910}
911
912
913- (NSString *) status_string
914{
915 return OOStringFromEntityStatus([self status]);
916}
917
918
919- (NSString *) gui_screen_string
920{
921 return OOStringFromGUIScreenID(gui_screen);
922}
923
924
925- (NSNumber *) galaxy_number
926{
927 return [NSNumber numberWithInt:[self currentGalaxyID]];
928}
929
930
931- (NSNumber *) planet_number
932{
933 return [NSNumber numberWithInt:[self currentSystemID]];
934}
935
936
937- (NSNumber *) score_number
938{
939 return [NSNumber numberWithUnsignedInt:[self score]];
940}
941
942
943- (NSNumber *) credits_number
944{
945 return [NSNumber numberWithDouble:[self creditBalance]];
946}
947
948
949- (NSNumber *) scriptTimer_number
950{
951 return [NSNumber numberWithDouble:[self scriptTimer]];
952}
953
954
955static int shipsFound;
956- (NSNumber *) shipsFound_number
957{
958 return [NSNumber numberWithInt:shipsFound];
959}
960
961
962- (NSNumber *) commanderLegalStatus_number
963{
964 return [NSNumber numberWithInt:[self legalStatus]];
965}
966
967
968- (void) setLegalStatus:(NSString *)valueString
969{
970 legalStatus = [valueString intValue];
971}
972
973
974- (NSString *) commanderLegalStatus_string
975{
976 return OODisplayStringFromLegalStatus(legalStatus);
977}
978
979
980- (NSNumber *) d100_number
981{
982 int d100 = ranrot_rand() % 100;
983 return [NSNumber numberWithInt:d100];
984}
985
986
987- (NSNumber *) pseudoFixedD100_number
988{
989 return [NSNumber numberWithInt:[self systemPseudoRandom100]];
990}
991
992
993- (NSNumber *) d256_number
994{
995 int d256 = ranrot_rand() % 256;
996 return [NSNumber numberWithInt:d256];
997}
998
999
1000- (NSNumber *) pseudoFixedD256_number
1001{
1002 return [NSNumber numberWithInt:[self systemPseudoRandom256]];
1003}
1004
1005
1006- (NSNumber *) clock_number // returns the game time in seconds
1007{
1008 return [NSNumber numberWithDouble:ship_clock];
1009}
1010
1011
1012- (NSNumber *) clock_secs_number // returns the game time in seconds
1013{
1014 return [NSNumber numberWithUnsignedLongLong:ship_clock];
1015}
1016
1017
1018- (NSNumber *) clock_mins_number // returns the game time in minutes
1019{
1020 return [NSNumber numberWithUnsignedLongLong:ship_clock / 60.0];
1021}
1022
1023
1024- (NSNumber *) clock_hours_number // returns the game time in hours
1025{
1026 return [NSNumber numberWithUnsignedLongLong:ship_clock / 3600.0];
1027}
1028
1029
1030- (NSNumber *) clock_days_number // returns the game time in days
1031{
1032 return [NSNumber numberWithUnsignedLongLong:ship_clock / 86400.0];
1033}
1034
1035
1036- (NSNumber *) fuelLevel_number // returns the fuel level in LY
1037{
1038 return [NSNumber numberWithFloat:floor(0.1 * fuel)];
1039}
1040
1041
1042- (NSString *) dockedAtMainStation_bool
1043{
1044 if ([self dockedAtMainStation]) return @"YES";
1045 else return @"NO";
1046}
1047
1048
1049- (NSString *) foundEquipment_bool
1050{
1051 return (found_equipment)? @"YES" : @"NO";
1052}
1053
1054
1055- (NSString *) sunWillGoNova_bool // returns whether the sun is going to go nova
1056{
1057 return ([[UNIVERSE sun] willGoNova])? @"YES" : @"NO";
1058}
1059
1060
1061- (NSString *) sunGoneNova_bool // returns whether the sun has gone nova
1062{
1063 return ([[UNIVERSE sun] goneNova])? @"YES" : @"NO";
1064}
1065
1066
1067- (NSString *) missionChoice_string // returns nil or the key for the chosen option
1068{
1069 return missionChoice;
1070}
1071
1072
1073- (NSString *) missionKeyPress_string
1074{
1075 return missionKeyPress;
1076}
1077
1078
1079- (NSNumber *) dockedTechLevel_number
1080{
1081 StationEntity *dockedStation = [self dockedStation];
1082 if (!dockedStation)
1083 {
1084 return [self systemTechLevel_number];
1085 }
1086 return [NSNumber numberWithUnsignedInteger:[dockedStation equivalentTechLevel]];
1087}
1088
1089- (NSString *) dockedStationName_string // returns 'NONE' if the player isn't docked, [station name] if it is, 'UNKNOWN' otherwise (?)
1090{
1091 NSString *result = nil;
1092 if ([self status] != STATUS_DOCKED) return @"NONE";
1093
1094 result = [self dockedStationName];
1095 if (result == nil) result = @"UNKNOWN";
1096 return result;
1097}
1098
1099
1100- (NSString *) systemGovernment_string
1101{
1102 int government = [[self systemGovernment_number] intValue]; // 0 .. 7 (0 anarchic .. 7 most stable)
1103 NSString *result = OODisplayStringFromGovernmentID(government);
1104 if (result == nil) result = @"UNKNOWN";
1105
1106 return result;
1107}
1108
1109
1110- (NSNumber *) systemGovernment_number
1111{
1112 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1113 return [systeminfo objectForKey:KEY_GOVERNMENT];
1114}
1115
1116
1117- (NSString *) systemEconomy_string
1118{
1119 int economy = [[self systemEconomy_number] intValue]; // 0 .. 7 (0 rich industrial .. 7 poor agricultural)
1120 NSString *result = OODisplayStringFromEconomyID(economy);
1121 if (result == nil) result = @"UNKNOWN";
1122
1123 return result;
1124}
1125
1126
1127- (NSNumber *) systemEconomy_number
1128{
1129 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1130 return [systeminfo objectForKey:KEY_ECONOMY];
1131}
1132
1133
1134- (NSNumber *) systemTechLevel_number
1135{
1136 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1137 return [systeminfo objectForKey:KEY_TECHLEVEL];
1138}
1139
1140
1141- (NSNumber *) systemPopulation_number
1142{
1143 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1144 return [systeminfo objectForKey:KEY_POPULATION];
1145}
1146
1147
1148- (NSNumber *) systemProductivity_number
1149{
1150 NSDictionary *systeminfo = [UNIVERSE currentSystemData];
1151 return [systeminfo objectForKey:KEY_PRODUCTIVITY];
1152}
1153
1154
1155- (NSString *) commanderName_string
1156{
1157 return [self commanderName];
1158}
1159
1160
1161- (NSString *) commanderRank_string
1162{
1163 return OODisplayRatingStringFromKillCount([self score]);
1164}
1165
1166
1167- (NSString *) commanderShip_string
1168{
1169 return [self name];
1170}
1171
1172
1173- (NSString *) commanderShipDisplayName_string
1174{
1175 return [self displayName];
1176}
1177
1178/*-----------------------------------------------------*/
1179
1180
1181- (NSString *) expandMessage:(NSString *)valueString
1182{
1183 Random_Seed very_random_seed;
1184 very_random_seed.a = rand() & 255;
1185 very_random_seed.b = rand() & 255;
1186 very_random_seed.c = rand() & 255;
1187 very_random_seed.d = rand() & 255;
1188 very_random_seed.e = rand() & 255;
1189 very_random_seed.f = rand() & 255;
1190 seed_RNG_only_for_planet_description(very_random_seed);
1191 NSString* expandedMessage = OOExpand(valueString);
1192 return [self replaceVariablesInString: expandedMessage];
1193}
1194
1195
1196- (void) commsMessage:(NSString *)valueString
1197{
1198 [UNIVERSE addCommsMessage:[self expandMessage:valueString] forCount:4.5];
1199}
1200
1201
1202// Enabled on 02-May-2008 - Nikos
1203// This method does the same as -commsMessage, (which in fact calls), the difference being that scripts can use this
1204// method to have unpiloted ship entities sending comms messages.
1205- (void) commsMessageByUnpiloted:(NSString *)valueString
1206{
1207 [self commsMessage:valueString];
1208}
1209
1210
1211- (void) consoleMessage3s:(NSString *)valueString
1212{
1213 [UNIVERSE addMessage:[self expandMessage:valueString] forCount: 3];
1214}
1215
1216
1217- (void) consoleMessage6s:(NSString *)valueString
1218{
1219 [UNIVERSE addMessage:[self expandMessage:valueString] forCount: 6];
1220}
1221
1222
1223- (void) awardCredits:(NSString *)valueString
1224{
1225 if (scriptTarget != self) return;
1226
1227 /* We can't use -longLongValue here for Mac OS X 10.4 compatibility, but
1228 we don't need to since larger values have never been supported for
1229 legacy scripts.
1230 */
1231 int64_t award = [valueString intValue];
1232 award *= 10;
1233 if (award < 0 && credits < (OOCreditsQuantity)-award) credits = 0;
1234 else credits += award;
1235}
1236
1237
1238- (void) awardShipKills:(NSString *)valueString
1239{
1240 if (scriptTarget != self) return;
1241
1242 int value = [valueString intValue];
1243 if (0 < value) ship_kills += value;
1244}
1245
1246
1247- (void) awardEquipment:(NSString *)equipString //eg. EQ_NAVAL_ENERGY_UNIT
1248{
1249 if (scriptTarget != self) return;
1250
1251 if ([equipString isEqualToString:@"EQ_FUEL"])
1252 {
1253 [self setFuel:[self fuelCapacity]];
1254 }
1255
1257
1258 if ([eqType isMissileOrMine])
1259 {
1260 [self mountMissileWithRole:equipString];
1261 }
1262 else if([equipString hasPrefix:@"EQ_WEAPON"] && ![equipString hasSuffix:@"_DAMAGED"])
1263 {
1264 OOLog(kOOLogSyntaxAwardEquipment, @"***** SCRIPT ERROR: in %@, CANNOT award undamaged weapon:'%@'. Damaged weapons can be awarded instead.", CurrentScriptDesc(), equipString);
1265 }
1266 else if ([equipString hasSuffix:@"_DAMAGED"] && [self hasEquipmentItem:[equipString substringToIndex:[equipString length] - [@"_DAMAGED" length]]])
1267 {
1268 OOLog(kOOLogSyntaxAwardEquipment, @"***** SCRIPT ERROR: in %@, CANNOT award damaged equipment:'%@'. Undamaged version already equipped.", CurrentScriptDesc(), equipString);
1269 }
1270 else if ([eqType canCarryMultiple] || ![self hasEquipmentItem:equipString])
1271 {
1272 [self addEquipmentItem:equipString withValidation:YES inContext:@"scripted"];
1273 }
1274}
1275
1276
1277- (void) removeEquipment:(NSString *)equipKey //eg. EQ_NAVAL_ENERGY_UNIT
1278{
1279 if (scriptTarget != self) return;
1280
1281 if ([equipKey isEqualToString:@"EQ_FUEL"])
1282 {
1283 fuel = 0;
1284 return;
1285 }
1286
1287 if ([equipKey isEqualToString:@"EQ_CARGO_BAY"] && [self hasEquipmentItem:equipKey]
1288 && ([self extraCargo] > [self availableCargoSpace]))
1289 {
1290 OOLog(kOOLogSyntaxRemoveEquipment, @"***** SCRIPT ERROR: in %@, CANNOT remove cargo bay. Too much cargo.", CurrentScriptDesc());
1291 return;
1292 }
1293 if ([self hasEquipmentItem:equipKey] || [self hasEquipmentItem:[equipKey stringByAppendingString:@"_DAMAGED"]])
1294 {
1295 [self removeEquipmentItem:equipKey];
1296 }
1297
1298}
1299
1300
1301- (void) setPlanetinfo:(NSString *)key_valueString // uses key=value format
1302{
1303 NSArray * tokens = [key_valueString componentsSeparatedByString:@"="];
1304 NSString* keyString = nil;
1305 NSString* valueString = nil;
1306
1307 if ([tokens count] != 2)
1308 {
1309 OOLog(kOOLogSyntaxSetPlanetInfo, @"***** SCRIPT ERROR: in %@, CANNOT setPlanetinfo: '%@' (bad parameter count)", CurrentScriptDesc(), key_valueString);
1310 return;
1311 }
1312
1313 keyString = [[tokens objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1314 valueString = [[tokens objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1315
1316 /* Legacy script planetinfo settings are now non-persistent over save/load
1317 * Virtually nothing uses them any more, and expecting them to have a
1318 * manifest and identifying what it is if so seems unnecessary */
1319 [UNIVERSE setSystemDataKey:keyString value:valueString fromManifest:@""];
1320
1321}
1322
1323
1324- (void) setSpecificPlanetInfo:(NSString *)key_valueString // uses galaxy#=planet#=key=value
1325{
1326 NSArray * tokens = [key_valueString componentsSeparatedByString:@"="];
1327 NSString* keyString = nil;
1328 NSString* valueString = nil;
1329 int gnum, pnum;
1330
1331 if ([tokens count] != 4)
1332 {
1333 OOLog(kOOLogSyntaxSetPlanetInfo, @"***** SCRIPT ERROR: in %@, CANNOT setSpecificPlanetInfo: '%@' (bad parameter count)", CurrentScriptDesc(), key_valueString);
1334 return;
1335 }
1336
1337 gnum = [tokens oo_intAtIndex:0];
1338 pnum = [tokens oo_intAtIndex:1];
1339 keyString = [[tokens objectAtIndex:2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1340 valueString = [[tokens objectAtIndex:3] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1341
1342 [UNIVERSE setSystemDataForGalaxy:gnum planet:pnum key:keyString value:valueString fromManifest:@"" forLayer:OO_LAYER_OXP_DYNAMIC];
1343}
1344
1345
1346- (void) awardCargo:(NSString *)amount_typeString
1347{
1348 if (scriptTarget != self) return;
1349
1350 NSArray *tokens = ScanTokensFromString(amount_typeString);
1351 OOCargoQuantityDelta amount;
1352 OOCommodityType type;
1353 OOMassUnit unit;
1354
1355 if ([tokens count] != 2)
1356 {
1357 OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"bad parameter count");
1358 return;
1359 }
1360
1361
1362 type = [tokens oo_stringAtIndex:1];
1363 if (![[UNIVERSE commodities] goodDefined:type])
1364 {
1365 OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"unknown type");
1366 return;
1367 }
1368
1369 amount = [tokens oo_intAtIndex:0];
1370 if (amount < 0)
1371 {
1372 OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"negative quantity");
1373 return;
1374 }
1375
1376 unit = [shipCommodityData massUnitForGood:type];
1377 if (specialCargo && unit == UNITS_TONS)
1378 {
1379 OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"cargo hold full with special cargo");
1380 return;
1381 }
1382
1383 [self awardCommodityType:type amount:amount];
1384}
1385
1386
1387- (void) removeAllCargo
1388{
1389 [self removeAllCargo:NO];
1390}
1391
1392- (void) removeAllCargo:(BOOL)forceRemoval
1393{
1394 // Misnamed method. It only removes cargo measured in TONS, g & Kg items are not removed. --Kaks 20091004
1395 OOCommodityType type;
1396
1397 if (scriptTarget != self) return;
1398
1399 if ([self status] != STATUS_DOCKED && !forceRemoval)
1400 {
1401 OOLogWARN(kOOLogRemoveAllCargoNotDocked, @"%@removeAllCargo only works when docked.", [NSString stringWithFormat:@" in %@, ", CurrentScriptDesc()]);
1402 return;
1403 }
1404
1405 OOLog(kOOLogNoteRemoveAllCargo, @"%@ removeAllCargo", forceRemoval ? @"Forcing" : @"Going to");
1406
1407 foreach(type, [shipCommodityData goods])
1408 {
1409 if ([shipCommodityData massUnitForGood:type] == UNITS_TONS)
1410 {
1411 [shipCommodityData setQuantity:0 forGood:type];
1412 }
1413 }
1414
1415
1416 if (forceRemoval && [self status] != STATUS_DOCKED)
1417 {
1418 NSInteger i;
1419 for (i = [cargo count] - 1; i >= 0; i--)
1420 {
1421 ShipEntity* canister = [cargo objectAtIndex:i];
1422 if (!canister) break;
1423 // Since we are forcing cargo removal, we don't really care about the unit of measurement. Any
1424 // commodity at more than 1000kg or 1000000gr will be inside cargopods, so remove those too.
1425 [cargo removeObjectAtIndex:i];
1426 }
1427 }
1428
1429 DESTROY(specialCargo);
1430
1431 [self calculateCurrentCargo];
1432}
1433
1434
1435- (void) useSpecialCargo:(NSString *)descriptionString
1436{
1437 if (scriptTarget != self) return;
1438
1439 [self removeAllCargo:YES];
1440 OOLog(kOOLogNoteUseSpecialCargo, @"Going to useSpecialCargo:'%@'", descriptionString);
1441 specialCargo = [OOExpand(descriptionString) retain];
1442}
1443
1444
1445- (void) testForEquipment:(NSString *)equipString //eg. EQ_NAVAL_ENERGY_UNIT
1446{
1447 found_equipment = [self hasEquipmentItem:equipString];
1448}
1449
1450
1451- (void) awardFuel:(NSString *)valueString // add to fuel up to 7.0 LY
1452{
1453 int delta = 10 * [valueString floatValue];
1454 OOFuelQuantity scriptTargetFuelBeforeAward = [scriptTarget fuel];
1455
1456 if (delta < 0 && scriptTargetFuelBeforeAward < (unsigned)-delta) [scriptTarget setFuel:0];
1457 else
1458 {
1459 [scriptTarget setFuel:(scriptTargetFuelBeforeAward + delta)];
1460 }
1461}
1462
1463
1464- (void) messageShipAIs:(NSString *)roles_message
1465{
1466 NSMutableArray* tokens = ScanTokensFromString(roles_message);
1467 NSString* roleString = nil;
1468 NSString* messageString = nil;
1469
1470 if ([tokens count] < 2)
1471 {
1472 OOLog(kOOLogSyntaxMessageShipAIs, @"***** SCRIPT ERROR: in %@, CANNOT messageShipAIs: '%@' (bad parameter count)", CurrentScriptDesc(), roles_message);
1473 return;
1474 }
1475
1476 roleString = [tokens objectAtIndex:0];
1477 [tokens removeObjectAtIndex:0];
1478 messageString = [tokens componentsJoinedByString:@" "];
1479
1480 NSArray *targets = [UNIVERSE findShipsMatchingPredicate:HasPrimaryRolePredicate
1481 parameter:roleString
1482 inRange:-1
1483 ofEntity:nil];
1484
1485 ShipEntity *target;
1486 foreach(target, targets) {
1487 [[target getAI] reactToMessage:messageString context:@"messageShipAIs:"];
1488 }
1489}
1490
1491
1492- (void) ejectItem:(NSString *)itemKey
1493{
1494 if (scriptTarget == nil) scriptTarget = self;
1495 [scriptTarget ejectShipOfType:itemKey];
1496}
1497
1498
1499- (void) addShips:(NSString *)roles_number
1500{
1501 NSMutableArray* tokens = ScanTokensFromString(roles_number);
1502 NSString* roleString = nil;
1503 NSString* numberString = nil;
1504
1505 if ([tokens count] != 2)
1506 {
1507 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addShips: '%@' (expected <role> <count>)", CurrentScriptDesc(), roles_number);
1508 return;
1509 }
1510
1511 roleString = [tokens objectAtIndex:0];
1512 numberString = [tokens objectAtIndex:1];
1513
1514 int number = [numberString intValue];
1515 if (number < 0)
1516 {
1517 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, can't add %i ships -- that's less than zero, y'know..", CurrentScriptDesc(), number);
1518 return;
1519 }
1520
1521 OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ships with role '%@'", number, roleString);
1522
1523 while (number--)
1524 [UNIVERSE witchspaceShipWithPrimaryRole:roleString];
1525}
1526
1527
1528- (void) addSystemShips:(NSString *)roles_number_position
1529{
1530 NSMutableArray* tokens = ScanTokensFromString(roles_number_position);
1531 NSString* roleString = nil;
1532 NSString* numberString = nil;
1533 NSString* positionString = nil;
1534
1535 if ([tokens count] != 3)
1536 {
1537 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addSystemShips: '%@' (expected <role> <count> <position>)", CurrentScriptDesc(), roles_number_position);
1538 return;
1539 }
1540
1541 roleString = [tokens objectAtIndex:0];
1542 numberString = [tokens objectAtIndex:1];
1543 positionString = [tokens objectAtIndex:2];
1544
1545 int number = [numberString intValue];
1546 double posn = [positionString doubleValue];
1547 if (number < 0)
1548 {
1549 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, can't add %i ships -- that's less than zero, y'know..", CurrentScriptDesc(), number);
1550 return;
1551 }
1552
1553 OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ships with role '%@' at a point %.3f along route1", number, roleString, posn);
1554
1555 while (number--)
1556 [UNIVERSE addShipWithRole:roleString nearRouteOneAt:posn];
1557}
1558
1559
1560- (void) addShipsAt:(NSString *)roles_number_system_x_y_z
1561{
1562 NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z);
1563
1564 NSString* roleString = nil;
1565 NSString* numberString = nil;
1566 NSString* systemString = nil;
1567 NSString* xString = nil;
1568 NSString* yString = nil;
1569 NSString* zString = nil;
1570
1571 if ([tokens count] != 6)
1572 {
1573 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addShipsAt: '%@' (expected <role> <count> <coordinate-system> <x> <y> <z>)", CurrentScriptDesc(), roles_number_system_x_y_z);
1574 return;
1575 }
1576
1577 roleString = [tokens objectAtIndex:0];
1578 numberString = [tokens objectAtIndex:1];
1579 systemString = [tokens objectAtIndex:2];
1580 xString = [tokens objectAtIndex:3];
1581 yString = [tokens objectAtIndex:4];
1582 zString = [tokens objectAtIndex:5];
1583
1584 HPVector posn = make_HPvector([xString doubleValue], [yString doubleValue], [zString doubleValue]);
1585
1586 int number = [numberString intValue];
1587 if (number < 1)
1588 {
1589 OOLog(kOOLogSyntaxAddShips, @"----- WARNING in %@ Tried to add %i ships -- no ship added.", CurrentScriptDesc(), number);
1590 return;
1591 }
1592
1593 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);
1594
1595 if (![UNIVERSE addShips: number withRole:roleString nearPosition: posn withCoordinateSystem: systemString])
1596 {
1597 OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, %@ could not add %u ships with role \"%@\"", CurrentScriptDesc(), @"addShipsAt:", number, roleString);
1598 }
1599}
1600
1601
1602- (void) addShipsAtPrecisely:(NSString *)roles_number_system_x_y_z
1603{
1604 NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z);
1605
1606 NSString* roleString = nil;
1607 NSString* numberString = nil;
1608 NSString* systemString = nil;
1609 NSString* xString = nil;
1610 NSString* yString = nil;
1611 NSString* zString = nil;
1612
1613 if ([tokens count] != 6)
1614 {
1615 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@,* CANNOT addShipsAtPrecisely: '%@' (expected <role> <count> <coordinate-system> <x> <y> <z>)", CurrentScriptDesc(), roles_number_system_x_y_z);
1616 return;
1617 }
1618
1619 roleString = [tokens objectAtIndex:0];
1620 numberString = [tokens objectAtIndex:1];
1621 systemString = [tokens objectAtIndex:2];
1622 xString = [tokens objectAtIndex:3];
1623 yString = [tokens objectAtIndex:4];
1624 zString = [tokens objectAtIndex:5];
1625
1626 HPVector posn = make_HPvector([xString doubleValue], [yString doubleValue], [zString doubleValue]);
1627
1628 int number = [numberString intValue];
1629 if (number < 1)
1630 {
1631 OOLog(kOOLogSyntaxAddShips, @"----- WARNING: in %@, Can't add %i ships -- no ship added.", CurrentScriptDesc(), number);
1632 return;
1633 }
1634
1635 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);
1636
1637 if (![UNIVERSE addShips: number withRole:roleString atPosition: posn withCoordinateSystem: systemString])
1638 {
1639 OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, %@ could not add %u ships with role '%@'", CurrentScriptDesc(), @"addShipsAtPrecisely:", number, roleString);
1640 }
1641}
1642
1643
1644- (void) addShipsWithinRadius:(NSString *)roles_number_system_x_y_z_r
1645{
1646 NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z_r);
1647
1648 if ([tokens count] != 7)
1649 {
1650 OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT 'addShipsWithinRadius: %@' (expected <role> <count> <coordinate-system> <x> <y> <z> <radius>))", CurrentScriptDesc(), roles_number_system_x_y_z_r);
1651 return;
1652 }
1653
1654 NSString* roleString = [tokens objectAtIndex:0];
1655 int number = [[tokens objectAtIndex:1] intValue];
1656 NSString* systemString = [tokens objectAtIndex:2];
1657 double x = [[tokens objectAtIndex:3] doubleValue];
1658 double y = [[tokens objectAtIndex:4] doubleValue];
1659 double z = [[tokens objectAtIndex:5] doubleValue];
1660 GLfloat r = [[tokens objectAtIndex:6] floatValue];
1661 HPVector posn = make_HPvector(x, y, z);
1662
1663 if (number < 1)
1664 {
1665 OOLog(kOOLogSyntaxAddShips, @"----- WARNING: in %@, can't add %i ships -- no ship added.", CurrentScriptDesc(), number);
1666 return;
1667 }
1668
1669 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);
1670
1671 if (![UNIVERSE addShips:number withRole: roleString nearPosition: posn withCoordinateSystem: systemString withinRadius: r])
1672 {
1673 OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR :in %@, %@ could not add %u ships with role \"%@\"", CurrentScriptDesc(), @"addShipsWithinRadius:", number, roleString);
1674 }
1675}
1676
1677
1678- (void) spawnShip:(NSString *)ship_key
1679{
1680 if ([UNIVERSE spawnShip:ship_key])
1681 {
1682 OOLog(kOOLogNoteAddShips, @"DEBUG: Spawned ship with shipdata key '%@'.", ship_key);
1683 }
1684 else
1685 {
1686 OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, could not spawn ship with shipdata key '%@'.", CurrentScriptDesc(), ship_key);
1687 }
1688}
1689
1690
1691- (void) set:(NSString *)missionvariable_value
1692{
1693 NSMutableArray *tokens = ScanTokensFromString(missionvariable_value);
1694 NSString *missionVariableString = nil;
1695 NSString *valueString = nil;
1696 BOOL hasMissionPrefix, hasLocalPrefix;
1697
1698 if ([tokens count] < 2)
1699 {
1700 OOLog(kOOLogSyntaxSet, @"***** SCRIPT ERROR: in %@, CANNOT SET '%@' (expected mission_variable or local_variable followed by value expression)", CurrentScriptDesc(), missionvariable_value);
1701 return;
1702 }
1703
1704 missionVariableString = [tokens objectAtIndex:0];
1705 [tokens removeObjectAtIndex:0];
1706 valueString = [tokens componentsJoinedByString:@" "];
1707
1708 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1709 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1710
1711 if (!hasMissionPrefix && !hasLocalPrefix)
1712 {
1713 OOLog(kOOLogSyntaxSet, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString);
1714 return;
1715 }
1716
1717 OOLog(kOOLogNoteSet, @"DEBUG: script %@ is set to %@", missionVariableString, valueString);
1718
1719 if (hasMissionPrefix)
1720 {
1721 [self setMissionVariable:valueString forKey:missionVariableString];
1722 }
1723 else
1724 {
1725 [self setLocalVariable:valueString forKey:missionVariableString andMission:sCurrentMissionKey];
1726 }
1727}
1728
1729
1730- (void) reset:(NSString *)missionvariable
1731{
1732 NSString* missionVariableString = [missionvariable stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1733 BOOL hasMissionPrefix, hasLocalPrefix;
1734
1735 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1736 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1737
1738 if (hasMissionPrefix)
1739 {
1740 [self setMissionVariable:nil forKey:missionVariableString];
1741 }
1742 else if (hasLocalPrefix)
1743 {
1744 [self setLocalVariable:nil forKey:missionVariableString andMission:sCurrentMissionKey];
1745 }
1746 else
1747 {
1748 OOLog(kOOLogSyntaxReset, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString);
1749 }
1750}
1751
1752
1753- (void) increment:(NSString *)missionVariableString
1754{
1755 BOOL hasMissionPrefix, hasLocalPrefix;
1756 int value = 0;
1757
1758 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1759 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1760
1761 if (hasMissionPrefix)
1762 {
1763 value = [[self missionVariableForKey:missionVariableString] intValue];
1764 value++;
1765 [self setMissionVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString];
1766 }
1767 else if (hasLocalPrefix)
1768 {
1769 value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] intValue];
1770 value++;
1771 [self setLocalVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString andMission:sCurrentMissionKey];
1772 }
1773 else
1774 {
1775 OOLog(kOOLogSyntaxIncrement, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString);
1776 }
1777}
1778
1779
1780- (void) decrement:(NSString *)missionVariableString
1781{
1782 BOOL hasMissionPrefix, hasLocalPrefix;
1783 int value = 0;
1784
1785 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1786 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1787
1788 if (hasMissionPrefix)
1789 {
1790 value = [[self missionVariableForKey:missionVariableString] intValue];
1791 value--;
1792 [self setMissionVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString];
1793 }
1794 else if (hasLocalPrefix)
1795 {
1796 value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] intValue];
1797 value--;
1798 [self setLocalVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString andMission:sCurrentMissionKey];
1799 }
1800 else
1801 {
1802 OOLog(kOOLogSyntaxDecrement, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString);
1803 }
1804}
1805
1806
1807- (void) add:(NSString *)missionVariableString_value
1808{
1809 NSString* missionVariableString = nil;
1810 NSString* valueString;
1811 double value;
1812 NSMutableArray* tokens = ScanTokensFromString(missionVariableString_value);
1813 BOOL hasMissionPrefix, hasLocalPrefix;
1814
1815 if ([tokens count] < 2)
1816 {
1817 OOLog(kOOLogSyntaxAdd, @"***** SCRIPT ERROR: in %@, CANNOT ADD: '%@'", CurrentScriptDesc(), missionVariableString_value);
1818 return;
1819 }
1820
1821 missionVariableString = [tokens objectAtIndex:0];
1822 [tokens removeObjectAtIndex:0];
1823 valueString = [tokens componentsJoinedByString:@" "];
1824
1825 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1826 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1827
1828 if (hasMissionPrefix)
1829 {
1830 value = [[self missionVariableForKey:missionVariableString] doubleValue];
1831 value += [valueString doubleValue];
1832 [self setMissionVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString];
1833 }
1834 else if (hasLocalPrefix)
1835 {
1836 value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] doubleValue];
1837 value += [valueString doubleValue];
1838 [self setLocalVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString andMission:sCurrentMissionKey];
1839 }
1840 else
1841 {
1842 OOLog(kOOLogSyntaxAdd, @"***** SCRIPT ERROR: in %@, CANNOT ADD: '%@' -- IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString_value, missionVariableString_value);
1843 }
1844}
1845
1846
1847- (void) subtract:(NSString *)missionVariableString_value
1848{
1849 NSString* missionVariableString = nil;
1850 NSString* valueString;
1851 double value;
1852 NSMutableArray* tokens = ScanTokensFromString(missionVariableString_value);
1853 BOOL hasMissionPrefix, hasLocalPrefix;
1854
1855 if ([tokens count] < 2)
1856 {
1857 OOLog(kOOLogSyntaxSubtract, @"***** SCRIPT ERROR: in %@, CANNOT SUBTRACT: '%@'", CurrentScriptDesc(), missionVariableString_value);
1858 return;
1859 }
1860
1861 missionVariableString = [tokens objectAtIndex:0];
1862 [tokens removeObjectAtIndex:0];
1863 valueString = [tokens componentsJoinedByString:@" "];
1864
1865 hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"];
1866 hasLocalPrefix = [missionVariableString hasPrefix:@"local_"];
1867
1868 if (hasMissionPrefix)
1869 {
1870 value = [[self missionVariableForKey:missionVariableString] doubleValue];
1871 value -= [valueString doubleValue];
1872 [self setMissionVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString];
1873 }
1874 else if (hasLocalPrefix)
1875 {
1876 value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] doubleValue];
1877 value -= [valueString doubleValue];
1878 [self setLocalVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString andMission:sCurrentMissionKey];
1879 }
1880 else
1881 {
1882 OOLog(kOOLogSyntaxSubtract, @"***** SCRIPT ERROR: in %@, CANNOT SUBTRACT: '%@' -- IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString_value, missionVariableString_value);
1883 }
1884}
1885
1886
1887- (void) checkForShips:(NSString *)roleString
1888{
1889 shipsFound = [UNIVERSE countShipsWithPrimaryRole:roleString];
1890}
1891
1892
1893- (void) resetScriptTimer
1894{
1895 script_time = 0.0;
1896 script_time_check = SCRIPT_TIMER_INTERVAL;
1897 script_time_interval = SCRIPT_TIMER_INTERVAL;
1898}
1899
1900
1901- (void) addMissionText: (NSString *)textKey
1902{
1903 NSString *text = nil;
1904
1905 if ([textKey isEqualToString:lastTextKey]) return; // don't repeatedly add the same text
1906 [lastTextKey release];
1907 lastTextKey = [textKey copy];
1908
1909 // Replace literal \n in strings with line breaks and perform expansions.
1910 text = [[UNIVERSE missiontext] oo_stringForKey:textKey];
1911 if (text == nil) return;
1913 text = [self replaceVariablesInString:text];
1914
1915 [self addLiteralMissionText:text];
1916}
1917
1918
1919- (void) addLiteralMissionText:(NSString *)text
1920{
1921 if (text != nil)
1922 {
1923 GuiDisplayGen *gui = [UNIVERSE gui];
1924
1925 NSString *para = nil;
1926 foreach (para, [text componentsSeparatedByString:@"\n"])
1927 {
1928 missionTextRow = [gui addLongText:para startingAtRow:missionTextRow align:GUI_ALIGN_LEFT];
1929 }
1930 }
1931}
1932
1933
1934- (void) setMissionChoiceByTextEntry:(BOOL)enable
1935{
1936 MyOpenGLView *gameView = [UNIVERSE gameView];
1937 _missionTextEntry = enable;
1938 [gameView resetTypedString];
1939}
1940
1941
1942- (void) setMissionChoices:(NSString *)choicesKey // choicesKey is a key for a dictionary of
1943{ // choices/choice phrases in missiontext.plist and also..
1944 NSDictionary *choicesDict = [[UNIVERSE missiontext] oo_dictionaryForKey:choicesKey];
1945 if ([choicesDict count] == 0)
1946 {
1947 return;
1948 }
1949 [self setMissionChoicesDictionary:choicesDict];
1950}
1951
1952
1953- (void) setMissionChoicesDictionary:(NSDictionary *)choicesDict
1954{
1955 unsigned i;
1956 bool keysOK = true;
1957 GuiDisplayGen* gui = [UNIVERSE gui];
1958 // TODO: MORE STUFF HERE
1959 //
1960 // What it does now:
1961 // find list of choices in missiontext.plist
1962 // add them to gui setting the key for each line to the key in the dict of choices
1963 // and the text of the line to the value in the dict of choices
1964 // and also set the selectable range
1965 // ++ change the mission screen's response to wait for a choice
1966 // and only if the selectable range is not present ask:
1967 // Press Space Commander...
1968 //
1969
1970 NSUInteger end_row = 21;
1971 if ([[self hud] allowBigGui])
1972 {
1973 end_row = 27;
1974 }
1975
1976 NSArray *choiceKeys = [choicesDict allKeys];
1977 /* Guard against potential for numeric keys in dictionary, which
1978 * would cause an unhandled exception in the sorter. See
1979 * OOJavaScriptEngine::OOJSDictionaryFromJSObject for further
1980 * thoughts. - CIM 15/2/13 */
1981 for (i=0; i < [choiceKeys count]; i++)
1982 {
1983 if (![[choiceKeys objectAtIndex:i] isKindOfClass:[NSString class]])
1984 {
1985 OOLog(@"test.script.error",@"Choices list in mission screen has non-string value %@",[choiceKeys objectAtIndex:i]);
1986 keysOK = false;
1987 }
1988 }
1989 if (keysOK)
1990 {
1991 // only try this if they're all strings
1992 choiceKeys = [choiceKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
1993 }
1994
1995 NSInteger keysCount = [choiceKeys count];
1996 if ((end_row + 1) < [choiceKeys count]) {
1997 OOLogERR(kOOLogException, @"in mission.runScreen choices: number of choices defined (%i) is greater than available lines (%i). Check HUD settings for allowBigGui.", [choiceKeys count], (end_row + 1));
1998 keysCount = end_row + 1;
1999 }
2000
2001 [gui setText:@"" forRow:end_row]; // clears out the 'Press spacebar' message
2002 [gui setKey:@"" forRow:end_row]; // clears the key to enable pollDemoControls to check for a selection
2003 [gui setSelectableRange:NSMakeRange(0,0)]; // clears the selectable range
2004 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; // enables mouse selection of the choices list items
2005
2006 OOGUIRow choicesRow = (end_row+1) - keysCount;
2007 NSEnumerator *choiceEnum = nil;
2008 NSString *choiceKey = nil;
2009 id choiceValue = nil;
2010 NSString *choiceText = nil;
2011
2012 BOOL selectableRowExists = NO;
2013 NSUInteger firstSelectableRow = end_row;
2014
2015 for (choiceEnum = [choiceKeys objectEnumerator]; (choiceKey = [choiceEnum nextObject]); )
2016 {
2017 choiceValue = [choicesDict objectForKey:choiceKey];
2018 OOGUIAlignment alignment = GUI_ALIGN_CENTER;
2019 OOColor *rowColor = [OOColor yellowColor];
2020 BOOL selectable = YES;
2021 if ([choiceValue isKindOfClass:[NSString class]])
2022 {
2023 choiceText = [NSString stringWithFormat:@" %@ ",(NSString*)choiceValue];
2024 }
2025 else if ([choiceValue isKindOfClass:[NSDictionary class]])
2026 {
2027 NSDictionary *choiceOpts = (NSDictionary*)choiceValue;
2028 choiceText = [NSString stringWithFormat:@" %@ ",[choiceOpts oo_stringForKey:@"text"]];
2029 NSString *alignmentChoice = [choiceOpts oo_stringForKey:@"alignment" defaultValue:@"CENTER"];
2030 if ([alignmentChoice isEqualToString:@"LEFT"])
2031 {
2032 alignment = GUI_ALIGN_LEFT;
2033 }
2034 else if ([alignmentChoice isEqualToString:@"RIGHT"])
2035 {
2036 alignment = GUI_ALIGN_RIGHT;
2037 }
2038 id colorDesc = [choiceOpts objectForKey:@"color"];
2039 if ([choiceOpts oo_boolForKey:@"unselectable"])
2040 {
2041 selectable = NO;
2042 }
2043 if (colorDesc != nil)
2044 {
2045 rowColor = [OOColor colorWithDescription:colorDesc];
2046 }
2047 else if (!selectable) // different default
2048 {
2049 rowColor = [OOColor darkGrayColor];
2050 }
2051 }
2052 else
2053 {
2054 continue; // invalid type
2055 }
2056 choiceText = OOExpand(choiceText);
2057 choiceText = [self replaceVariablesInString:choiceText];
2058 // allow blank rows
2059 if (![choiceText isEqualToString:@" "])
2060 {
2061 [gui setText:choiceText forRow:choicesRow align: alignment];
2062 if (selectable)
2063 {
2064 [gui setKey:choiceKey forRow:choicesRow];
2065 }
2066 else
2067 {
2068 [gui setKey:GUI_KEY_SKIP forRow:choicesRow];
2069 }
2070 [gui setColor:rowColor forRow:choicesRow];
2071 if (selectable && !selectableRowExists)
2072 {
2073 selectableRowExists = YES;
2074 firstSelectableRow = choicesRow;
2075 }
2076 }
2077 else
2078 {
2079 [gui setKey:GUI_KEY_SKIP forRow:choicesRow];
2080 }
2081 choicesRow++;
2082 if (choicesRow > (end_row + 1)) break;
2083 }
2084
2085 if (!selectableRowExists)
2086 {
2087 // just in case choices are set but they're all blank.
2088 [gui setText:@" " forRow:end_row align: GUI_ALIGN_CENTER];
2089 [gui setKey:@"" forRow:end_row];
2090 [gui setColor:[OOColor yellowColor] forRow:end_row];
2091 }
2092
2093 [gui setSelectableRange:NSMakeRange((end_row+1) - keysCount, keysCount)];
2094 [gui setSelectedRow: firstSelectableRow];
2095
2096 [self resetMissionChoice];
2097}
2098
2099
2100- (void) resetMissionChoice
2101{
2102 [self setMissionChoice:nil];
2103}
2104
2105
2106- (void) clearMissionScreen
2107{
2108 [self setMissionOverlayDescriptor:nil];
2109 [self setMissionBackgroundDescriptor:nil];
2110 [self setMissionBackgroundSpecial:nil];
2111 [self setMissionTitle:nil];
2112 [self setMissionMusic:nil];
2113 [self showShipModel:nil];
2114}
2115
2116
2117- (void) addMissionDestination:(NSString *)destinations
2118{
2119 unsigned j;
2120 int dest;
2121 NSMutableArray *tokens = ScanTokensFromString(destinations);
2122
2123 for (j = 0; j < [tokens count]; j++)
2124 {
2125 dest = [tokens oo_intAtIndex:j];
2126 if (dest < 0 || dest > 255)
2127 continue;
2128
2129 [self addMissionDestinationMarker:[self defaultMarker:dest]];
2130 }
2131}
2132
2133
2134- (void) removeMissionDestination:(NSString *)destinations
2135{
2136 unsigned j;
2137 int dest;
2138 NSMutableArray *tokens = ScanTokensFromString(destinations);
2139
2140 for (j = 0; j < [tokens count]; j++)
2141 {
2142 dest = [[tokens objectAtIndex:j] intValue];
2143 if (dest < 0 || dest > 255) continue;
2144
2145 [self removeMissionDestinationMarker:[self defaultMarker:dest]];
2146 }
2147}
2148
2149
2150- (void) showShipModel:(NSString *)role
2151{
2152 if ([role isEqualToString:@"none"] || [role length] == 0)
2153 {
2154 [UNIVERSE removeDemoShips];
2155 return;
2156 }
2157
2158 ShipEntity *ship = [UNIVERSE makeDemoShipWithRole:role spinning:YES];
2159 OOLog(kOOLogNoteShowShipModel, @"::::: showShipModel:'%@' (%@) (%@)", role, ship, [ship name]);
2160}
2161
2162
2163- (void) setMissionMusic:(NSString *)value
2164{
2165 if ([value length] == 0 || [[value lowercaseString] isEqualToString:@"none"])
2166 {
2167 value = nil;
2168 }
2170}
2171
2172
2173- (NSString *) missionTitle
2174{
2175 return _missionTitle;
2176}
2177
2178
2179- (void) setMissionTitle:(NSString *)value
2180{
2181 if (_missionTitle != value)
2182 {
2183 [_missionTitle release];
2184 _missionTitle = [value copy];
2185 }
2186}
2187
2188
2189- (void) setMissionImage:(NSString *)value
2190{
2191 if ([value length] != 0 && ![[value lowercaseString] isEqualToString:@"none"])
2192 {
2193 [self setMissionOverlayDescriptor:[NSDictionary dictionaryWithObject:value forKey:@"name"]];
2194 }
2195 else
2196 {
2197 [self setMissionOverlayDescriptor:nil];
2198 }
2199
2200}
2201
2202
2203- (void) setMissionBackground:(NSString *)value
2204{
2205 if ([value length] != 0 && ![[value lowercaseString] isEqualToString:@"none"])
2206 {
2207 [self setMissionBackgroundDescriptor:[NSDictionary dictionaryWithObject:value forKey:@"name"]];
2208 }
2209 else
2210 {
2211 [self setMissionBackgroundDescriptor:nil];
2212 }
2213}
2214
2215
2216- (void) setFuelLeak:(NSString *)value
2217{
2218 if (scriptTarget != self)
2219 {
2220 [scriptTarget setFuel:0];
2221 return;
2222 }
2223
2224 fuel_leak_rate = [value doubleValue];
2225 if (fuel_leak_rate > 0)
2226 {
2227 [self playFuelLeak];
2228 [UNIVERSE addMessage:DESC(@"danger-fuel-leak") forCount:6];
2229 OOLog(kOOLogNoteFuelLeak, @"%@", @"FUEL LEAK activated!");
2230 }
2231}
2232
2233
2234- (NSNumber *) fuelLeakRate_number
2235{
2236 return [NSNumber numberWithFloat:[self fuelLeakRate]];
2237}
2238
2239
2240- (void) setSunNovaIn:(NSString *)time_value
2241{
2242 double time_until_nova = [time_value doubleValue];
2243 [[UNIVERSE sun] setGoingNova:YES inTime: time_until_nova];
2244}
2245
2246
2247- (void) launchFromStation
2248{
2249 // ensure autosave is ready for the next unscripted launch
2250 if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES];
2251 if ([self status] == STATUS_DOCKING) [self setStatus:STATUS_DOCKED]; // needed here to prevent the normal update from continuing with docking.
2252 [self leaveDock:[self dockedStation]];
2253}
2254
2255
2256- (void) blowUpStation
2257{
2258 StationEntity *mainStation = nil;
2259
2260 mainStation = [UNIVERSE station];
2261 if (mainStation != nil)
2262 {
2263 [UNIVERSE unMagicMainStation];
2264 [mainStation takeEnergyDamage:500000000.0 from:nil becauseOf:nil weaponIdentifier:@""]; // 500 million should do it!
2265 }
2266}
2267
2268
2269- (void) sendAllShipsAway
2270{
2271 if (!UNIVERSE)
2272 return;
2273 int ent_count = UNIVERSE->n_entities;
2274 Entity** uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list
2275 Entity* my_entities[ent_count];
2276 int i;
2277 for (i = 0; i < ent_count; i++)
2278 my_entities[i] = [uni_entities[i] retain]; // retained
2279
2280 for (i = 1; i < ent_count; i++)
2281 {
2282 Entity* e1 = my_entities[i];
2283 if ([e1 isShip])
2284 {
2285 ShipEntity* se1 = (ShipEntity*)e1;
2286 int e_class = [e1 scanClass];
2287 if (((e_class == CLASS_NEUTRAL)||(e_class == CLASS_POLICE)||(e_class == CLASS_MILITARY)||(e_class == CLASS_THARGOID)) &&
2288 ! ([se1 isStation] && [se1 maxFlightSpeed] == 0) && // exclude only stations, not carriers.
2289 [se1 hasHyperspaceMotor]) // exclude non jumping ships. Escorts will still be able to follow a mother.
2290 {
2291 AI* se1AI = [se1 getAI];
2292 [se1 setFuel:MAX(PLAYER_MAX_FUEL, [se1 fuelCapacity])];
2293 [se1 setAITo:@"exitingTraderAI.plist"]; // lets them return to their previous state after the jump
2294 [se1AI setState:@"EXIT_SYSTEM"];
2295 // The following should prevent all ships leaving at once (freezes oolite on slower machines)
2296 [se1AI setNextThinkTime:[UNIVERSE getTime] + 3 + (ranrot_rand() & 15)];
2297 [se1 setPrimaryRole:@"oolite-none"]; // prevents new ship from appearing at witchpoint when this one leaves!
2298 }
2299 }
2300 }
2301
2302 for (i = 0; i < ent_count; i++)
2303 {
2304 [my_entities[i] release]; // released
2305 }
2306}
2307
2308
2309- (OOPlanetEntity *) addPlanet: (NSString *)planetKey
2310{
2311 OOLog(kOOLogNoteAddPlanet, @"addPlanet: %@", planetKey);
2312
2313 if (!UNIVERSE)
2314 return nil;
2315 NSDictionary* dict = [[UNIVERSE systemManager] getPropertiesForSystemKey:planetKey];
2316 if (!dict)
2317 {
2318 OOLog(@"script.error.addPlanet.keyNotFound", @"***** ERROR: could not find an entry in planetinfo.plist for '%@'", planetKey);
2319 return nil;
2320 }
2321
2322 /*- add planet -*/
2323 OOLog(kOOLogDebugAddPlanet, @"DEBUG: initPlanetFromDictionary: %@", dict);
2324 OOPlanetEntity *planet = [[[OOPlanetEntity alloc] initFromDictionary:dict withAtmosphere:YES andSeed:[[UNIVERSE systemManager] getRandomSeedForCurrentSystem] forSystem:system_id] autorelease];
2325
2326 Quaternion planetOrientation;
2327 if (ScanQuaternionFromString([dict objectForKey:@"orientation"], &planetOrientation))
2328 {
2329 [planet setOrientation:planetOrientation];
2330 }
2331
2332 if (![dict objectForKey:@"position"])
2333 {
2334 OOLog(@"script.error.addPlanet.noPosition", @"***** ERROR: you must specify a position for scripted planet '%@' before it can be created", planetKey);
2335 return nil;
2336 }
2337
2338 NSString *positionString = [dict objectForKey:@"position"];
2339 if([positionString hasPrefix:@"abs "] && ([UNIVERSE planet] != nil || [UNIVERSE sun] !=nil))
2340 {
2341 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);
2342 }
2343
2344 HPVector posn = [UNIVERSE coordinatesFromCoordinateSystemString:positionString];
2345 if (posn.x || posn.y || posn.z)
2346 {
2347 OOLog(kOOLogDebugAddPlanet, @"planet position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString);
2348 }
2349 else
2350 {
2351 ScanHPVectorFromString(positionString, &posn);
2352 OOLog(kOOLogDebugAddPlanet, @"planet position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString);
2353 }
2354 [planet setPosition: posn];
2355
2356 [UNIVERSE addEntity:planet];
2357 return planet;
2358}
2359
2360
2361- (OOPlanetEntity *) addMoon: (NSString *)moonKey
2362{
2363 OOLog(kOOLogNoteAddPlanet, @"DEBUG: addMoon '%@'", moonKey);
2364
2365 if (!UNIVERSE)
2366 return nil;
2367 NSDictionary* dict = [[UNIVERSE systemManager] getPropertiesForSystemKey:moonKey];
2368 if (!dict)
2369 {
2370 OOLog(@"script.error.addPlanet.keyNotFound", @"***** ERROR: could not find an entry in planetinfo.plist for '%@'", moonKey);
2371 return nil;
2372 }
2373
2374 OOLog(kOOLogDebugAddPlanet, @"DEBUG: initMoonFromDictionary: %@", dict);
2375 OOPlanetEntity *planet = [[[OOPlanetEntity alloc] initFromDictionary:dict withAtmosphere:NO andSeed:[[UNIVERSE systemManager] getRandomSeedForCurrentSystem] forSystem:system_id] autorelease];
2376
2377 Quaternion planetOrientation;
2378 if (ScanQuaternionFromString([dict objectForKey:@"orientation"], &planetOrientation))
2379 {
2380 [planet setOrientation:planetOrientation];
2381 }
2382
2383 if (![dict objectForKey:@"position"])
2384 {
2385 OOLog(@"script.error.addPlanet.noPosition", @"***** ERROR: you must specify a position for scripted moon '%@' before it can be created", moonKey);
2386 return nil;
2387 }
2388
2389 NSString *positionString = [dict objectForKey:@"position"];
2390 if([positionString hasPrefix:@"abs "] && ([UNIVERSE planet] != nil || [UNIVERSE sun] !=nil))
2391 {
2392 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);
2393 }
2394 HPVector posn = [UNIVERSE coordinatesFromCoordinateSystemString:positionString];
2395 if (posn.x || posn.y || posn.z)
2396 {
2397 OOLog(kOOLogDebugAddPlanet, @"moon position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString);
2398 }
2399 else
2400 {
2401 ScanHPVectorFromString(positionString, &posn);
2402 OOLog(kOOLogDebugAddPlanet, @"moon position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString);
2403 }
2404 [planet setPosition: posn];
2405
2406 [UNIVERSE addEntity:planet];
2407 return planet;
2408}
2409
2410
2411- (void) debugOn
2412{
2414 OOLog(kOOLogDebugOnOff, @"%@", @"SCRIPT debug messages ON");
2415}
2416
2417
2418- (void) debugOff
2419{
2420 OOLog(kOOLogDebugOnOff, @"%@", @"SCRIPT debug messages OFF");
2422}
2423
2424
2425- (void) debugMessage:(NSString *)args
2426{
2427 OOLog(kOOLogDebugMessage, @"SCRIPT debugMessage: %@", args);
2428}
2429
2430
2431- (void) playSound:(NSString *) soundName
2432{
2433 [self playLegacyScriptSound:soundName];
2434}
2435
2436/*-----------------------------------------------------*/
2437
2438
2439- (void) doMissionCallback
2440{
2441 // make sure we don't call the same callback twice
2442 _missionWithCallback = NO;
2444}
2445
2446
2447- (void) clearMissionScreenID
2448{
2449 [_missionScreenID release];
2450 _missionScreenID = nil;
2451}
2452
2453
2454- (void) setMissionScreenID:(NSString *)msid
2455{
2456 _missionScreenID = [msid retain];
2457}
2458
2459
2460- (NSString *) missionScreenID
2461{
2462 return _missionScreenID;
2463}
2464
2465
2466- (void) endMissionScreenAndNoteOpportunity
2467{
2468 _missionAllowInterrupt = NO;
2469 [self clearMissionScreenID];
2470 // Older scripts might intercept missionScreenEnded first, and call secondary mission screens.
2471 if(![self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")])
2472 {
2473 // if we're here, no mission screen is running. Opportunity! :)
2474 [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")];
2475 }
2476}
2477
2478
2479- (void) setGuiToMissionScreen
2480{
2481 // reset special background as legacy scripts can't use it, and this
2482 // is only called by legacy scripts
2483 [self setMissionBackgroundSpecial:nil];
2484 // likewise exit screen target
2485 [self setMissionExitScreen:GUI_SCREEN_STATUS];
2486
2487 [self setGuiToMissionScreenWithCallback:NO];
2488}
2489
2490
2491- (void) refreshMissionScreenTextEntry
2492{
2493 MyOpenGLView *gameView = [UNIVERSE gameView];
2494 GuiDisplayGen *gui = [UNIVERSE gui];
2495 NSUInteger end_row = 21;
2496 if ([[self hud] allowBigGui])
2497 {
2498 end_row = 27;
2499 }
2500
2501 [gui setText:[NSString stringWithFormat:DESC(@"mission-screen-text-prompt-@"), [gameView typedString]] forRow:end_row align:GUI_ALIGN_LEFT];
2502 [gui setColor:[OOColor cyanColor] forRow:end_row];
2503
2504 [gui setShowTextCursor:YES];
2505 [gui setCurrentRow:end_row];
2506
2507}
2508
2509
2510- (void) setGuiToMissionScreenWithCallback:(BOOL) callback
2511{
2512 GuiDisplayGen *gui = [UNIVERSE gui];
2513 OOGUIScreenID oldScreen = gui_screen;
2514 NSUInteger end_row = 21;
2515 if ([[self hud] allowBigGui])
2516 {
2517 end_row = 27;
2518 }
2519
2520 // GUI stuff
2521 {
2522 [gui clear];
2523 [gui setTitle:[self missionTitle] ?: DESC(@"mission-information")];
2524
2525 if (!_missionTextEntry)
2526 {
2527 [gui setText:DESC(@"press-space-commander") forRow:end_row align:GUI_ALIGN_CENTER];
2528 [gui setColor:[OOColor yellowColor] forRow:end_row];
2529 [gui setKey:@"spacebar" forRow:end_row];
2530 [gui setShowTextCursor:NO];
2531 }
2532 else
2533 {
2534 [self refreshMissionScreenTextEntry];
2535 }
2536 [gui setSelectableRange:NSMakeRange(0,0)];
2537
2538 [gui setForegroundTextureDescriptor:[self missionOverlayDescriptorOrDefault]];
2539 NSDictionary *background_desc = [self missionBackgroundDescriptorOrDefault];
2540 [gui setBackgroundTextureDescriptor:background_desc];
2541 // must set special second as setting the descriptor resets it
2542 BOOL overridden = ([self missionBackgroundDescriptor] != nil);
2543 [gui setBackgroundTextureSpecial:[self missionBackgroundSpecial] withBackground:!overridden];
2544
2545
2546 }
2547 /* ends */
2548
2549 missionTextRow = 1;
2550
2551
2552 if (gui)
2553 gui_screen = GUI_SCREEN_MISSION;
2554
2555 if (lastTextKey)
2556 {
2557 [lastTextKey release];
2558 lastTextKey = nil;
2559 }
2560
2562
2563 // the following are necessary...
2564 [UNIVERSE enterGUIViewModeWithMouseInteraction:NO];
2565 _missionWithCallback = callback;
2566 _missionAllowInterrupt = NO;
2567 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
2568
2569}
2570
2571
2572- (void) setBackgroundFromDescriptionsKey:(NSString*) d_key
2573{
2574 NSArray * items = (NSArray *)[[UNIVERSE descriptions] objectForKey:d_key];
2575 //
2576 if (!items)
2577 return;
2578 //
2579 [self addScene: items atOffset: kZeroVector];
2580 //
2581 [self setShowDemoShips: YES];
2582}
2583
2584
2585- (void) addScene:(NSArray *)items atOffset:(Vector)off
2586{
2587 unsigned i;
2588
2589 if (items == nil) return;
2590
2591 for (i = 0; i < [items count]; i++)
2592 {
2593 id item = [items objectAtIndex:i];
2594 if ([item isKindOfClass:[NSString class]])
2595 {
2596 [self processSceneString:item atOffset: off];
2597 }
2598 else if ([item isKindOfClass:[NSArray class]])
2599 {
2600 [self addScene:item atOffset: off];
2601 }
2602 else if ([item isKindOfClass:[NSDictionary class]])
2603 {
2604 [self processSceneDictionary:item atOffset: off];
2605 }
2606 }
2607}
2608
2609
2610- (BOOL) processSceneDictionary:(NSDictionary *) couplet atOffset:(Vector) off
2611{
2612 NSArray *conditions = [couplet objectForKey:@"conditions"];
2613 NSArray *actions = nil;
2614 if ([couplet objectForKey:@"do"])
2615 actions = [NSArray arrayWithObject: [couplet objectForKey:@"do"]];
2616 NSArray *else_actions = nil;
2617 if ([couplet objectForKey:@"else"])
2618 else_actions = [NSArray arrayWithObject: [couplet objectForKey:@"else"]];
2619 BOOL success = YES;
2620 if (conditions == nil)
2621 {
2622 OOLog(@"script.scene.couplet.badConditions", @"***** SCENE ERROR: %@ - conditions not %@, returning %@.", [couplet description], @" found",@"YES and performing 'do' actions");
2623 }
2624 else
2625 {
2626 if (![conditions isKindOfClass:[NSArray class]])
2627 {
2628 OOLog(@"script.scene.couplet.badConditions", @"***** SCENE ERROR: %@ - conditions not %@, returning %@.", [conditions description], @"an array",@"NO");
2629 return NO;
2630 }
2631 }
2632
2633 // check conditions..
2634 success = TestScriptConditions(OOSanitizeLegacyScriptConditions(conditions, @"<scene dictionary conditions>"));
2635
2636 // perform successful actions...
2637 if ((success) && (actions) && [actions count])
2638 [self addScene: actions atOffset: off];
2639
2640 // perform unsuccessful actions
2641 if ((!success) && (else_actions) && [else_actions count])
2642 [self addScene: else_actions atOffset: off];
2643
2644 return success;
2645}
2646
2647
2648- (BOOL) processSceneString:(NSString*) item atOffset:(Vector) off
2649{
2650 Vector model_p0;
2651 Quaternion model_q;
2652
2653 if (!item)
2654 return NO;
2655 NSArray * i_info = ScanTokensFromString(item);
2656 if (!i_info)
2657 return NO;
2658 NSString* i_key = [(NSString*)[i_info objectAtIndex:0] lowercaseString];
2659
2660 OOLog(kOOLogNoteProcessSceneString, @"..... processing %@ (%@)", i_info, i_key);
2661
2662 //
2663 // recursively add further scenes:
2664 //
2665 if ([i_key isEqualToString:@"scene"])
2666 {
2667 if ([i_info count] != 5) // must be scene_key_x_y_z
2668 return NO; // 0.... 1.. 2 3 4
2669 NSString* scene_key = (NSString*)[i_info objectAtIndex: 1];
2670 Vector scene_offset = {0};
2671 ScanVectorFromString([[i_info subarrayWithRange:NSMakeRange(2, 3)] componentsJoinedByString:@" "], &scene_offset);
2672 scene_offset.x += off.x; scene_offset.y += off.y; scene_offset.z += off.z;
2673 NSArray * scene_items = (NSArray *)[[UNIVERSE descriptions] objectForKey:scene_key];
2674 OOLog(kOOLogDebugProcessSceneStringAddScene, @"::::: adding scene: '%@'", scene_key);
2675 //
2676 if (scene_items)
2677 {
2678 [self addScene: scene_items atOffset: scene_offset];
2679 return YES;
2680 }
2681 else
2682 return NO;
2683 }
2684 //
2685 // Add ship models:
2686 //
2687 if ([i_key isEqualToString:@"ship"]||[i_key isEqualToString:@"model"]||[i_key isEqualToString:@"role"])
2688 {
2689 if ([i_info count] != 10) // must be item_name_x_y_z_W_X_Y_Z_align
2690 {
2691 return NO; // 0... 1... 2 3 4 5 6 7 8 9....
2692 }
2693
2694 ShipEntity* ship = nil;
2695
2696 if ([i_key isEqualToString:@"ship"]||[i_key isEqualToString:@"model"])
2697 {
2698 ship = [UNIVERSE newShipWithName:[i_info oo_stringAtIndex: 1]];
2699 }
2700 else if ([i_key isEqualToString:@"role"])
2701 {
2702 ship = [UNIVERSE newShipWithRole:[i_info oo_stringAtIndex: 1]];
2703 }
2704 if (!ship)
2705 return NO;
2706
2707 ScanVectorAndQuaternionFromString([[i_info subarrayWithRange:NSMakeRange(2, 7)] componentsJoinedByString:@" "], &model_p0, &model_q);
2708
2709 Vector model_offset = positionOffsetForShipInRotationToAlignment(ship, model_q, [i_info oo_stringAtIndex:9]);
2710 model_p0 = vector_add(model_p0, vector_subtract(off, model_offset));
2711
2712 OOLog(kOOLogDebugProcessSceneStringAddModel, @"::::: adding model to scene:'%@'", ship);
2713 [ship setOrientation: model_q];
2714 [ship setPosition: vectorToHPVector(model_p0)];
2715 [UNIVERSE setMainLightPosition:(Vector){ DEMO_LIGHT_POSITION }]; // set light origin
2716 [ship setScanClass: CLASS_NO_DRAW];
2717 [ship switchAITo: @"nullAI.plist"];
2718 [UNIVERSE addEntity: ship]; // STATUS_IN_FLIGHT, AI state GLOBAL
2719 [ship setStatus: STATUS_COCKPIT_DISPLAY];
2720 [ship setRoll: 0.0];
2721 [ship setPitch: 0.0];
2722 [ship setVelocity: kZeroVector];
2723 [ship setBehaviour: BEHAVIOUR_STOP_STILL];
2724
2725 [ship release];
2726 return YES;
2727 }
2728 //
2729 // Add player ship model:
2730 //
2731 if ([i_key isEqualToString:@"player"])
2732 {
2733 if ([i_info count] != 9) // must be player_x_y_z_W_X_Y_Z_align
2734 return NO; // 0..... 1 2 3 4 5 6 7 8....
2735
2736 ShipEntity* doppelganger = [UNIVERSE newShipWithName:[self shipDataKey]]; // retain count = 1
2737 if (!doppelganger)
2738 return NO;
2739
2740 ScanVectorAndQuaternionFromString([[i_info subarrayWithRange:NSMakeRange( 1, 7)] componentsJoinedByString:@" "], &model_p0, &model_q);
2741
2742 Vector model_offset = positionOffsetForShipInRotationToAlignment( doppelganger, model_q, (NSString*)[i_info objectAtIndex:8]);
2743 model_p0.x += off.x - model_offset.x;
2744 model_p0.y += off.y - model_offset.y;
2745 model_p0.z += off.z - model_offset.z;
2746
2747 OOLog(kOOLogDebugProcessSceneStringAddModel, @"::::: adding model to scene:'%@'", doppelganger);
2748 [doppelganger setOrientation: model_q];
2749 [doppelganger setPosition: vectorToHPVector(model_p0)];
2750 [UNIVERSE setMainLightPosition:(Vector){ DEMO_LIGHT_POSITION }]; // set light origin
2751 [doppelganger setScanClass: CLASS_NO_DRAW];
2752 [doppelganger switchAITo: @"nullAI.plist"];
2753 [UNIVERSE addEntity: doppelganger];
2754 [doppelganger setStatus: STATUS_COCKPIT_DISPLAY];
2755 [doppelganger setRoll: 0.0];
2756 [doppelganger setPitch: 0.0];
2757 [doppelganger setVelocity: kZeroVector];
2758 [doppelganger setBehaviour: BEHAVIOUR_STOP_STILL];
2759
2760 [doppelganger release];
2761 return YES;
2762 }
2763 //
2764 // Add planet model: selected via gui-scene-show-planet/-local-planet
2765 //
2766 if ([i_key isEqualToString:@"local-planet"] || [i_key isEqualToString:@"target-planet"])
2767 {
2768 if ([i_info count] != 4) // must be xxxxx-planet_x_y_z
2769 return NO; // 0........... 1 2 3
2770
2771 // sunlight position for F7 screen is chosen pseudo randomly from 4 different positions.
2772 if (info_system_id & 8)
2773 {
2774 _sysInfoLight = (info_system_id & 2) ? (Vector){ -10000.0, 4000.0, -10000.0 } : (Vector){ -12000.0, -5000.0, -10000.0 };
2775 }
2776 else
2777 {
2778 _sysInfoLight = (info_system_id & 2) ? (Vector){ 6000.0, -5000.0, -10000.0 } : (Vector){ 6000.0, 4000.0, -10000.0 };
2779 }
2780
2781 [UNIVERSE setMainLightPosition:_sysInfoLight]; // set light origin
2782
2783#if NEW_PLANETS
2784 OOPlanetEntity *originalPlanet = nil;
2785 if ([i_key isEqualToString:@"local-planet"])
2786 {
2787 originalPlanet = [UNIVERSE planet];
2788 }
2789 else
2790 {
2791 originalPlanet = [[[OOPlanetEntity alloc] initAsMainPlanetForSystem:info_system_id] autorelease];
2792 }
2793 OOPlanetEntity *doppelganger = [originalPlanet miniatureVersion];
2794 if (doppelganger == nil) return NO;
2795
2796#else
2797 OOPlanetEntity* doppelganger = nil;
2798 NSMutableDictionary *planetInfo = [NSMutableDictionary dictionaryWithDictionary:[UNIVERSE generateSystemData:target_system_seed]];
2799
2800 if ([i_key isEqualToString:@"local-planet"] && [UNIVERSE sun])
2801 {
2802 OOPlanetEntity *mainPlanet = [UNIVERSE planet];
2803 OOTexture *texture = [mainPlanet texture];
2804 if (texture != nil)
2805 {
2806 [planetInfo setObject:texture forKey:@"_oo_textureObject"];
2807 [planetInfo oo_setBool:[mainPlanet isExplicitlyTextured] forKey:@"_oo_isExplicitlyTextured"];
2808 [planetInfo oo_setBool:YES forKey:@"mainForLocalSystem"];
2809 //[planetInfo oo_setQuaternion:[mainPlanet orientation] forKey:@"orientation"]; // the orientation is overwritten later on, without regard for the real planet's orientation.
2810 }
2811 }
2812
2813 doppelganger = [[OOPlanetEntity alloc] initFromDictionary:planetInfo withAtmosphere:YES andSeed:target_system_seed];
2814 [doppelganger miniaturize];
2815 [doppelganger autorelease];
2816
2817 if (doppelganger == nil) return NO;
2818#endif
2819
2820 ScanVectorFromString([[i_info subarrayWithRange:NSMakeRange(1, 3)] componentsJoinedByString:@" "], &model_p0);
2821
2822 // miniature radii are roughly between 60 and 120. Place miniatures with a radius bigger than 60 a bit futher away.
2823 model_p0 = vector_multiply_scalar(model_p0, 1 - 0.5 * ((60 - [doppelganger radius]) / 60));
2824
2825 model_p0 = vector_add(model_p0, off);
2826
2827 // TODO: find better quaternion values.
2828#if NEW_PLANETS
2829 //Quaternion model_q = { 0.83, 0.365148, 0.182574, 0.0 }; // shows new planets' north pole.
2830 //Quaternion model_q = { 0.83, -0.365148, 0.182574, 0.0 }; // shows new planets' south pole.
2831 Quaternion model_q = { 0.83, 0.12, 0.44, 0.0 }; // new planets - default orientation.
2832#else
2833 //model_q = make_quaternion( M_SQRT1_2, 0.314, M_SQRT1_2, 0.0 );
2834 Quaternion model_q = { 0.833492, 0.333396, 0.440611, 0.0 };
2835#endif
2836 OOLog(kOOLogDebugProcessSceneStringAddMiniPlanet, @"::::: adding %@ to scene:'%@'", i_key, doppelganger);
2837 [doppelganger setOrientation: model_q];
2838 // HPVect: mission screen coordinates are small enough that we don't need high-precision for calculations
2839 [doppelganger setPosition: vectorToHPVector(model_p0)];
2840 /* MKW - add rotation based on current time
2841 * - necessary to duplicate the rotation already performed in PlanetEntity.m since we reset the orientation above. */
2842 int deltaT = floor(fmod([self clockTimeAdjusted], 86400));
2843 [doppelganger update: deltaT];
2844 [UNIVERSE addEntity:doppelganger];
2845
2846 return YES;
2847 }
2848
2849 return NO;
2850}
2851
2852
2853- (BOOL) addEqScriptForKey:(NSString *)eq_key
2854{
2855 if (eq_key == nil) return NO;
2856
2857 NSString *scriptName = [[OOEquipmentType equipmentTypeWithIdentifier:eq_key] scriptName];
2858
2859 OOLog(@"player.equipmentScript", @"Added equipment %@, with the following script property: '%@'.", eq_key, scriptName);
2860
2861 if (scriptName == nil) return NO;
2862
2863 NSMutableDictionary *properties = [NSMutableDictionary dictionary];
2864
2865 // no duplicates!
2866 NSArray *eqScript = nil;
2867 foreach (eqScript, eqScripts)
2868 {
2869 NSString *key = [eqScript oo_stringAtIndex:0];
2870 if ([key isEqualToString: eq_key]) return NO;
2871 }
2872
2873 [properties setObject:self forKey:@"ship"];
2874 [properties setObject:eq_key forKey:@"equipmentKey"];
2875 OOScript *s = [OOScript jsScriptFromFileNamed:scriptName properties:properties];
2876 if (s == nil) return NO;
2877
2878 OOLog(@"player.equipmentScript", @"Script '%@': installation %@successful.", scriptName,(s == nil ? @"un" : @""));
2879
2880 [eqScripts addObject:[NSArray arrayWithObjects:eq_key,s,nil]];
2881 if (primedEquipment == [eqScripts count] - 1) primedEquipment++; // if primed-none, keep it as primed-none.
2882 OOLog(@"player.equipmentScript", @"Scriptable equipment available: %lu.", [eqScripts count]);
2883 return YES;
2884}
2885
2886
2887- (void) removeEqScriptForKey:(NSString *)eq_key
2888{
2889 if (eq_key == nil) return;
2890
2891 NSString *key = nil;
2892 NSUInteger i, count = [eqScripts count];
2893
2894 for (i = 0; i < count; i++)
2895 {
2896 key = [[eqScripts oo_arrayAtIndex:i] oo_stringAtIndex:0];
2897 if ([key isEqualToString: eq_key])
2898 {
2899 [eqScripts removeObjectAtIndex:i];
2900
2901 if (i == primedEquipment) primedEquipment = count; // primed-none
2902 else if (i < primedEquipment) primedEquipment--; // track the primed equipment
2903 if (count == primedEquipment) primedEquipment--; // the array has shrunk by one!
2904
2905 OOLog(@"player.equipmentScript", @"Removed equipment %@, with the following script property: '%@'.", eq_key, [[OOEquipmentType equipmentTypeWithIdentifier:eq_key] scriptName]);
2906 }
2907 }
2908}
2909
2910
2911- (NSUInteger) eqScriptIndexForKey:(NSString *)eq_key
2912{
2913 NSUInteger i, count = [eqScripts count];
2914
2915 if (eq_key != nil)
2916 {
2917 for (i = 0; i < count; i++)
2918 {
2919 NSString *key = [[eqScripts oo_arrayAtIndex:i] oo_stringAtIndex:0];
2920 if ([key isEqualToString: eq_key]) return i;
2921 }
2922 }
2923
2924 return count;
2925}
2926
2927
2928- (void) targetNearestHostile
2929{
2930 [self scanForHostiles];
2931 Entity *ent = [self foundTarget];
2932 if (ent != nil)
2933 {
2934 ident_engaged = YES;
2935 missile_status = MISSILE_STATUS_TARGET_LOCKED;
2936 [self addTarget:ent];
2937 }
2938}
2939
2940
2941- (void) targetNearestIncomingMissile
2942{
2943 [self scanForNearestIncomingMissile];
2944 Entity *ent = [self foundTarget];
2945 if (ent != nil)
2946 {
2947 ident_engaged = YES;
2948 missile_status = MISSILE_STATUS_TARGET_LOCKED;
2949 [self addTarget:ent];
2950 }
2951}
2952
2953
2954- (void) setGalacticHyperspaceBehaviourTo:(NSString *)galacticHyperspaceBehaviourString
2955{
2956 OOGalacticHyperspaceBehaviour ghBehaviour = OOGalacticHyperspaceBehaviourFromString(galacticHyperspaceBehaviourString);
2957 if (ghBehaviour == GALACTIC_HYPERSPACE_BEHAVIOUR_UNKNOWN)
2958 {
2959 OOLog(@"player.setGalacticHyperspaceBehaviour.invalidInput",
2960 @"setGalacticHyperspaceBehaviourTo: called with unknown behaviour %@.", galacticHyperspaceBehaviourString);
2961 }
2962 [self setGalacticHyperspaceBehaviour:ghBehaviour];
2963}
2964
2965
2966- (void) setGalacticHyperspaceFixedCoordsTo:(NSString *)galacticHyperspaceFixedCoordsString
2967{
2968 NSArray *coord_vals = ScanTokensFromString(galacticHyperspaceFixedCoordsString);
2969 if ([coord_vals count] < 2) // Will be 0 if string is nil
2970 {
2971 OOLog(@"player.setGalacticHyperspaceFixedCoords.invalidInput", @"%@",
2972 @"setGalacticHyperspaceFixedCoords: called with bad specifier. Defaulting to Oolite standard.");
2973 galacticHyperspaceFixedCoords.x = galacticHyperspaceFixedCoords.y = 0x60;
2974 }
2975
2976 [self setGalacticHyperspaceFixedCoordsX:[coord_vals oo_unsignedCharAtIndex:0]
2977 y:[coord_vals oo_unsignedCharAtIndex:1]];
2978}
2979
2980@end
2981
2982
2984{
2985 switch (type)
2986 {
2987 case COMPARISON_EQUAL: return @"equal";
2988 case COMPARISON_NOTEQUAL: return @"notequal";
2989 case COMPARISON_LESSTHAN: return @"lessthan";
2990 case COMPARISON_GREATERTHAN: return @"greaterthan";
2991 case COMPARISON_ONEOF: return @"oneof";
2992 case COMPARISON_UNDEFINED: return @"undefined";
2993 }
2994 return @"<error: invalid comparison type>";
2995}
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:77
#define foreachkey(VAR, DICT)
Definition OOCocoa.h:366
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,...)
BOOL ScanVectorAndQuaternionFromString(NSString *xyzwxyzString, Vector *outVector, Quaternion *outQuaternion)
NSMutableArray * ScanTokensFromString(NSString *values)
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:833
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:757
void setOrientation:(Quaternion quat)
Definition Entity.m:725
OOScanClass scanClass
Definition Entity.h:106
void setScanClass:(OOScanClass sClass)
Definition Entity.m:799
void setPosition:(HPVector posn)
Definition Entity.m:647
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)
id jsScriptFromFileNamed:properties:(NSString *fileName,[properties] NSDictionary *properties)
Definition OOScript.m:192
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()