LCOV - code coverage report
Current view: top level - Core/Entities - PlayerEntityLegacyScriptEngine.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 59 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          Line data    Source code
       1           0 : /*
       2             : 
       3             : PlayerEntityLegacyScriptEngine.m
       4             : 
       5             : Oolite
       6             : Copyright (C) 2004-2013 Giles C Williams and contributors
       7             : 
       8             : This program is free software; you can redistribute it and/or
       9             : modify it under the terms of the GNU General Public License
      10             : as published by the Free Software Foundation; either version 2
      11             : of the License, or (at your option) any later version.
      12             : 
      13             : This program is distributed in the hope that it will be useful,
      14             : but WITHOUT ANY WARRANTY; without even the implied warranty of
      15             : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16             : GNU General Public License for more details.
      17             : 
      18             : You should have received a copy of the GNU General Public License
      19             : along with this program; if not, write to the Free Software
      20             : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
      21             : MA 02110-1301, USA.
      22             : 
      23             : */
      24             : 
      25             : #import "PlayerEntityLegacyScriptEngine.h"
      26             : #import "PlayerEntityScriptMethods.h"
      27             : #import "PlayerEntitySound.h"
      28             : #import "PlayerEntityContracts.h"
      29             : #import "GuiDisplayGen.h"
      30             : #import "Universe.h"
      31             : #import "ResourceManager.h"
      32             : #import "AI.h"
      33             : #import "ShipEntityAI.h"
      34             : #import "ShipEntityScriptMethods.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"
      42             : #import "OOCollectionExtractors.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"
      50             : #import "OOLegacyScriptWhitelist.h"
      51             : #import "OOJavaScriptEngine.h"
      52             : #import "OOEquipmentType.h"
      53             : #import "HeadUpDisplay.h"
      54             : #import "OOSystemDescriptionManager.h"
      55             : #import "OOEntityFilterPredicate.h"
      56             : 
      57             : 
      58           0 : static NSString * const kOOLogScriptAddShipsFailed                      = @"script.addShips.failed";
      59           0 : static NSString * const kOOLogScriptMissionDescNoText           = @"script.missionDescription.noMissionText";
      60           0 : static NSString * const kOOLogScriptMissionDescNoKey            = @"script.missionDescription.noMissionKey";
      61             : 
      62           0 : static NSString * const kOOLogDebugOnMetaClass                          = @"$scriptDebugOn";
      63           0 : static NSString * const kOOLogDebugMessage                                      = @"script.debug.message";
      64           0 : static NSString * const kOOLogDebugOnOff                                        = @"script.debug.onOff";
      65           0 : static NSString * const kOOLogDebugAddPlanet                            = @"script.debug.addPlanet";
      66           0 : static NSString * const kOOLogDebugReplaceVariablesInString     = @"script.debug.replaceVariablesInString";
      67           0 : static NSString * const kOOLogDebugProcessSceneStringAddScene = @"script.debug.processSceneString.addScene";
      68           0 : static NSString * const kOOLogDebugProcessSceneStringAddModel = @"script.debug.processSceneString.addModel";
      69           0 : static NSString * const kOOLogDebugProcessSceneStringAddMiniPlanet = @"script.debug.processSceneString.addMiniPlanet";
      70             : 
      71           0 : static NSString * const kOOLogNoteRemoveAllCargo                        = @"script.debug.note.removeAllCargo";
      72           0 : static NSString * const kOOLogNoteUseSpecialCargo                       = @"script.debug.note.useSpecialCargo";
      73           0 : static NSString * const kOOLogNoteAddShips                                      = @"script.debug.note.addShips";
      74           0 : static NSString * const kOOLogNoteSet                                           = @"script.debug.note.set";
      75           0 : static NSString * const kOOLogNoteShowShipModel                         = @"script.debug.note.showShipModel";
      76           0 : static NSString * const kOOLogNoteFuelLeak                                      = @"script.debug.note.setFuelLeak";
      77           0 : static NSString * const kOOLogNoteAddPlanet                                     = @"script.debug.note.addPlanet";
      78           0 : static NSString * const kOOLogNoteProcessSceneString            = @"script.debug.note.processSceneString";
      79             : 
      80           0 : static NSString * const kOOLogSyntaxSetPlanetInfo                       = @"script.debug.syntax.setPlanetInfo";
      81           0 : static NSString * const kOOLogSyntaxAwardCargo                          = @"script.debug.syntax.awardCargo";
      82           0 : static NSString * const kOOLogSyntaxAwardEquipment                      = @"script.debug.syntax.awardEquipment";
      83           0 : static NSString * const kOOLogSyntaxRemoveEquipment                     = @"script.debug.syntax.removeEquipment";
      84           0 : static NSString * const kOOLogSyntaxMessageShipAIs                      = @"script.debug.syntax.messageShipAIs";
      85           0 : static NSString * const kOOLogSyntaxAddShips                            = @"script.debug.syntax.addShips";
      86           0 : static NSString * const kOOLogSyntaxSet                                         = @"script.debug.syntax.set";
      87           0 : static NSString * const kOOLogSyntaxReset                                       = @"script.debug.syntax.reset";
      88           0 : static NSString * const kOOLogSyntaxIncrement                           = @"script.debug.syntax.increment";
      89           0 : static NSString * const kOOLogSyntaxDecrement                           = @"script.debug.syntax.decrement";
      90           0 : static NSString * const kOOLogSyntaxAdd                                         = @"script.debug.syntax.add";
      91           0 : static NSString * const kOOLogSyntaxSubtract                            = @"script.debug.syntax.subtract";
      92             : 
      93           0 : static NSString * const kOOLogRemoveAllCargoNotDocked           = @"script.error.removeAllCargo.notDocked";
      94             : 
      95             : 
      96           0 : #define ACTIONS_TEMP_PREFIX                                                                     "__oolite_actions_temp"
      97           0 : static NSString * const kActionTempPrefix                                       = @ ACTIONS_TEMP_PREFIX;
      98             : 
      99             : 
     100           0 : static NSString         *sMissionStringValue = nil;
     101           0 : static NSString         *sCurrentMissionKey = nil;
     102           0 : static ShipEntity       *scriptTarget = nil;
     103             : 
     104             : 
     105             : @interface PlayerEntity (ScriptingPrivate)
     106             : 
     107           0 : - (BOOL) scriptTestCondition:(NSArray *)scriptCondition;
     108           0 : - (NSString *) expandScriptRightHandSide:(NSArray *)rhsComponents;
     109             : 
     110           0 : - (void) scriptActions:(NSArray *)actions forTarget:(ShipEntity *)target missionKey:(NSString *)missionKey;
     111           0 : - (NSString *) expandMessage:(NSString *)valueString;
     112             : 
     113             : @end
     114             : 
     115             : 
     116             : @implementation PlayerEntity (Scripting)
     117             : 
     118             : 
     119           0 : static NSString *CurrentScriptNameOr(NSString *alternative)
     120             : {
     121             :         if (sCurrentMissionKey != nil && ![sCurrentMissionKey hasPrefix:kActionTempPrefix])
     122             :         {
     123             :                 return [NSString stringWithFormat:@"\"%@\"", sCurrentMissionKey];
     124             :         }
     125             :         return alternative;
     126             : }
     127             : 
     128             : 
     129           0 : OOINLINE NSString *CurrentScriptDesc(void)
     130             : {
     131             :         return CurrentScriptNameOr(@"<anonymous actions>");
     132             : }
     133             : 
     134             : 
     135           0 : static void PerformScriptActions(NSArray *actions, Entity *target);
     136           0 : static void PerformConditionalStatment(NSArray *actions, Entity *target);
     137           0 : static void PerformActionStatment(NSArray *statement, Entity *target);
     138           0 : static BOOL TestScriptConditions(NSArray *conditions);
     139             : 
     140             : 
     141             : static 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             : 
     158             : static 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             : 
     185             : static 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             : 
     233             : static 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             : 
     254             : - (ShipEntity*) scriptTarget
     255             : {
     256             :         return scriptTarget;
     257             : }
     258             : 
     259             : 
     260           0 : OOINLINE OOEntityStatus RecursiveRemapStatus(OOEntityStatus status)
     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             : 
     278           0 : static BOOL sRunningScript = NO;
     279             : 
     280             : 
     281             : // Return the world scripts that care about -checkScript.
     282           0 : - (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           0 : - (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           0 :         #define DOUBLEVAL(x) ((x != nil) ? [x doubleValue] : 0.0)
     495             :                 
     496             :                 switch (comparator)
     497             :                 {
     498             :                         case COMPARISON_UNDEFINED:
     499             :                                 return lhsString == nil;
     500             :                                 
     501             :                         case COMPARISON_EQUAL:
     502             :                                 return [lhsString isEqualToString:expandedRHS];
     503             :                                 
     504             :                         case COMPARISON_NOTEQUAL:
     505             :                                 return ![lhsString isEqualToString:expandedRHS];
     506             :                                 
     507             :                         case COMPARISON_LESSTHAN:
     508             :                                 return DOUBLEVAL(lhsString) < DOUBLEVAL(expandedRHS);
     509             :                                 
     510             :                         case COMPARISON_GREATERTHAN:
     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             :                                         
     564             :                                 case COMPARISON_NOTEQUAL:
     565             :                                         return lhsValue != rhsValue;
     566             :                                         
     567             :                                 case COMPARISON_LESSTHAN:
     568             :                                         return lhsValue < rhsValue;
     569             :                                         
     570             :                                 case COMPARISON_GREATERTHAN:
     571             :                                         return lhsValue > rhsValue;
     572             :                                         
     573             :                                 case COMPARISON_UNDEFINED:
     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             :                                 
     591             :                         case COMPARISON_NOTEQUAL:
     592             :                                 return lhsFlag != rhsFlag;
     593             :                                 
     594             :                         case COMPARISON_LESSTHAN:
     595             :                         case COMPARISON_GREATERTHAN:
     596             :                         case COMPARISON_UNDEFINED:
     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           0 : - (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             : 
     955           0 : static 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           0 : - (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             :         
    1256             :         OOEquipmentType *eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipString];
    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;
    1912             :         text = OOExpandWithOptions(OOStringExpanderDefaultRandomSeed(), kOOExpandBackslashN, text);
    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             :         }
    2169             :         [[OOMusicController     sharedController] setMissionMusic:value];
    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           0 : - (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           0 : - (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             : {
    2413             :         OOLogSetDisplayMessagesInClass(kOOLogDebugOnMetaClass, YES);
    2414             :         OOLog(kOOLogDebugOnOff, @"%@", @"SCRIPT debug messages ON");
    2415             : }
    2416             : 
    2417             : 
    2418             : - (void) debugOff
    2419             : {
    2420             :         OOLog(kOOLogDebugOnOff, @"%@", @"SCRIPT debug messages OFF");
    2421             :         OOLogSetDisplayMessagesInClass(kOOLogDebugOnMetaClass, NO);
    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;
    2443             :         [[OOJavaScriptEngine sharedEngine] runMissionCallback];
    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             :         
    2561             :         [[OOMusicController sharedController] playMissionMusic];
    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"] && [UNIVERSE sun])
    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             : 
    2983           0 : NSString *OOComparisonTypeToString(OOComparisonType type)
    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             : }

Generated by: LCOV version 1.14