LCOV - code coverage report
Current view: top level - Core - Universe.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 1 119 0.8 %
Date: 2025-08-24 10:25:05 Functions: 0 0 -

          Line data    Source code
       1           0 : /*
       2             : 
       3             : Universe.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             : 
      26             : #import "Universe.h"
      27             : #import "MyOpenGLView.h"
      28             : #import "GameController.h"
      29             : #import "ResourceManager.h"
      30             : #import "AI.h"
      31             : #import "GuiDisplayGen.h"
      32             : #import "HeadUpDisplay.h"
      33             : #import "OOSound.h"
      34             : #import "OOColor.h"
      35             : #import "OOCacheManager.h"
      36             : #import "OOStringExpander.h"
      37             : #import "OOStringParsing.h"
      38             : #import "OOCollectionExtractors.h"
      39             : #import "OOConstToString.h"
      40             : #import "OOConstToJSString.h"
      41             : #import "OOOpenGLExtensionManager.h"
      42             : #import "OOOpenGLMatrixManager.h"
      43             : #import "OOCPUInfo.h"
      44             : #import "OOMaterial.h"
      45             : #import "OOTexture.h"
      46             : #import "OORoleSet.h"
      47             : #import "OOShipGroup.h"
      48             : #import "OODebugSupport.h"
      49             : 
      50             : #import "Octree.h"
      51             : #import "CollisionRegion.h"
      52             : #import "OOGraphicsResetManager.h"
      53             : #import "OODebugSupport.h"
      54             : #import "OOEntityFilterPredicate.h"
      55             : 
      56             : #import "OOCharacter.h"
      57             : #import "OOShipRegistry.h"
      58             : #import "OOProbabilitySet.h"
      59             : #import "OOEquipmentType.h"
      60             : #import "OOShipLibraryDescriptions.h"
      61             : 
      62             : #import "PlayerEntity.h"
      63             : #import "PlayerEntityContracts.h"
      64             : #import "PlayerEntityControls.h"
      65             : #import "PlayerEntityScriptMethods.h"
      66             : #import "StationEntity.h"
      67             : #import "DockEntity.h"
      68             : #import "SkyEntity.h"
      69             : #import "DustEntity.h"
      70             : #import "OOPlanetEntity.h"
      71             : #import "OOVisualEffectEntity.h"
      72             : #import "OOWaypointEntity.h"
      73             : #import "OOSunEntity.h"
      74             : #import "WormholeEntity.h"
      75             : #import "OOBreakPatternEntity.h"
      76             : #import "ShipEntityAI.h"
      77             : #import "ProxyPlayerEntity.h"
      78             : #import "OORingEffectEntity.h"
      79             : #import "OOLightParticleEntity.h"
      80             : #import "OOFlashEffectEntity.h"
      81             : #import "OOExplosionCloudEntity.h"
      82             : #import "OOSystemDescriptionManager.h"
      83             : #import "OOMusicController.h"
      84             : #import "OOAsyncWorkManager.h"
      85             : #import "OODebugFlags.h"
      86             : #import "OODebugStandards.h"
      87             : #import "OOLoggingExtended.h"
      88             : #import "OOJSEngineTimeManagement.h"
      89             : #import "OOJoystickManager.h"
      90             : #import "OOScriptTimer.h"
      91             : #import "OOJSScript.h"
      92             : #import "OOJSFrameCallbacks.h"
      93             : #import "OOJSPopulatorDefinition.h"
      94             : #import "OOOpenGL.h"
      95             : #import "OOShaderProgram.h"
      96             : 
      97             : 
      98             : #if OO_LOCALIZATION_TOOLS
      99             : #import "OOConvertSystemDescriptions.h"
     100             : #endif
     101             : 
     102             : #if OOLITE_ESPEAK
     103             : #include <espeak/speak_lib.h>
     104             : #endif
     105             : 
     106           0 : enum
     107             : {
     108             :         DEMO_FLY_IN                     = 101,
     109             :         DEMO_SHOW_THING,
     110             :         DEMO_FLY_OUT
     111             : };
     112             : 
     113           0 : #define DEMO2_VANISHING_DISTANCE        650.0
     114           0 : #define DEMO2_FLY_IN_STAGE_TIME 0.4
     115             : 
     116             : 
     117           0 : #define MAX_NUMBER_OF_ENTITIES                          200
     118           0 : #define STANDARD_STATION_ROLL                           0.4
     119             : // currently twice scanner radius
     120           0 : #define LANE_WIDTH                      51200.0
     121             : 
     122           0 : static NSString * const kOOLogUniversePopulateError                     = @"universe.populate.error";
     123           0 : static NSString * const kOOLogUniversePopulateWitchspace        = @"universe.populate.witchspace";
     124           0 : static NSString * const kOOLogEntityVerificationError           = @"entity.linkedList.verify.error";
     125           0 : static NSString * const kOOLogEntityVerificationRebuild         = @"entity.linkedList.verify.rebuild";
     126             : 
     127             : 
     128             : 
     129           0 : const GLfloat framebufferQuadVertices[] = {
     130             :         // positions  // texture coords
     131             :          1.0f,  1.0f, 1.0f, 1.0f, // top right
     132             :          1.0f, -1.0f, 1.0f, 0.0f, // bottom right
     133             :         -1.0f, -1.0f, 0.0f, 0.0f, // bottom left
     134             :         -1.0f,  1.0f, 0.0f, 1.0f  // top left 
     135             : };
     136           0 : const GLuint framebufferQuadIndices[] = {
     137             :         0, 1, 3, // first triangle
     138             :         1, 2, 3  // second triangle
     139             : };
     140             : 
     141             : 
     142           0 : Universe *gSharedUniverse = nil;
     143             : 
     144             : extern Entity *gOOJSPlayerIfStale;
     145           0 : Entity *gOOJSPlayerIfStale = nil;
     146             : 
     147             : 
     148           0 : static BOOL MaintainLinkedLists(Universe* uni);
     149           0 : OOINLINE BOOL EntityInRange(HPVector p1, Entity *e2, float range);
     150             : 
     151           0 : static OOComparisonResult compareName(id dict1, id dict2, void * context);
     152           0 : static OOComparisonResult comparePrice(id dict1, id dict2, void * context);
     153             : 
     154             : /* TODO: route calculation is really slow - find a way to safely enable this */
     155             : #undef CACHE_ROUTE_FROM_SYSTEM_RESULTS
     156             : 
     157           0 : @interface RouteElement: NSObject
     158             : {
     159             : @private
     160           0 :         OOSystemID _location, _parent;
     161           0 :         double _cost, _distance, _time;
     162           0 :         int _jumps;
     163             : }
     164             : 
     165           0 : + (instancetype) elementWithLocation:(OOSystemID) location parent:(OOSystemID)parent cost:(double) cost distance:(double) distance time:(double) time jumps:(int) jumps;
     166           0 : - (OOSystemID) parent;
     167           0 : - (OOSystemID) location;
     168           0 : - (double) cost;
     169           0 : - (double) distance;
     170           0 : - (double) time;
     171           0 : - (int) jumps;
     172             : 
     173             : @end
     174             : 
     175             : @implementation RouteElement
     176             : 
     177             : + (instancetype) elementWithLocation:(OOSystemID) location parent:(OOSystemID) parent cost:(double) cost distance:(double) distance time:(double) time jumps:(int) jumps
     178             : {
     179             :         RouteElement *r = [[RouteElement alloc] init];
     180             :         
     181             :         r->_location = location;
     182             :         r->_parent = parent;
     183             :         r->_cost = cost;
     184             :         r->_distance = distance;
     185             :         r->_time = time;
     186             :         r->_jumps = jumps;
     187             :         
     188             :         return [r autorelease];
     189             : }
     190             : 
     191             : - (OOSystemID) parent { return _parent; }
     192             : - (OOSystemID) location { return _location; }
     193             : - (double) cost { return _cost; }
     194             : - (double) distance { return _distance; }
     195             : - (double) time { return _time; }
     196             : - (int) jumps { return _jumps; }
     197             : 
     198             : @end
     199             : 
     200             : 
     201             : @interface Universe (OOPrivate)
     202             : 
     203           0 : - (void) initTargetFramebufferWithViewSize:(NSSize)viewSize;
     204           0 : - (void) deleteOpenGLObjects;
     205           0 : - (void) resizeTargetFramebufferWithViewSize:(NSSize)viewSize;
     206           0 : - (void) prepareToRenderIntoDefaultFramebuffer;
     207           0 : - (void) drawTargetTextureIntoDefaultFramebuffer;
     208             : 
     209           0 : - (BOOL) doRemoveEntity:(Entity *)entity;
     210           0 : - (void) setUpCargoPods;
     211           0 : - (void) setUpInitialUniverse;
     212           0 : - (HPVector) fractionalPositionFrom:(HPVector)point0 to:(HPVector)point1 withFraction:(double)routeFraction;
     213             : 
     214           0 : - (void) populateSpaceFromActiveWormholes;
     215             : 
     216           0 : - (NSString *)chooseStringForKey:(NSString *)key inDictionary:(NSDictionary *)dictionary;
     217             : 
     218             : #if OO_LOCALIZATION_TOOLS
     219             : #if DEBUG_GRAPHVIZ
     220             : - (void) dumpDebugGraphViz;
     221             : - (void) dumpSystemDescriptionGraphViz;
     222             : #endif
     223           0 : - (void) addNumericRefsInString:(NSString *)string toGraphViz:(NSMutableString *)graphViz fromNode:(NSString *)fromNode nodeCount:(NSUInteger)nodeCount;
     224             : 
     225             : /**
     226             :  * \ingroup cli
     227             :  * Scans the command line for --complie-sysdesc, --export-sysdec, --xml and --penstep arguments.
     228             :  */
     229           1 : - (void) runLocalizationTools;
     230             : #endif
     231             : 
     232             : #if NEW_PLANETS
     233             : - (void) prunePreloadingPlanetMaterials;
     234             : #endif
     235             : 
     236             : // Set shader effects level without logging or triggering a reset -- should only be used directly during startup.
     237           0 : - (void) setShaderEffectsLevelDirectly:(OOShaderSetting)value;
     238             : 
     239           0 : - (void) setFirstBeacon:(Entity <OOBeaconEntity> *)beacon;
     240           0 : - (void) setLastBeacon:(Entity <OOBeaconEntity> *)beacon;
     241             : 
     242           0 : - (void) verifyDescriptions;
     243           0 : - (void) loadDescriptions;
     244           0 : - (void) loadScenarios;
     245             : 
     246           0 : - (void) verifyEntitySessionIDs;
     247           0 : - (float) randomDistanceWithinScanner;
     248           0 : - (Vector) randomPlaceWithinScannerFrom:(Vector)pos alongRoute:(Vector)route withOffset:(double)offset;
     249             : 
     250           0 : - (void) setDetailLevelDirectly:(OOGraphicsDetail)value;
     251             : 
     252           0 : - (NSDictionary *)demoShipData;
     253           0 : - (void) setLibraryTextForDemoShip;
     254             : 
     255             : @end
     256             : 
     257             : 
     258             : @implementation Universe
     259             : 
     260             : // Flags needed when JS reset fails.
     261           0 : static int JSResetFlags = 0;
     262             : 
     263             : 
     264             : // track the position and status of the lights
     265           0 : static BOOL             object_light_on = NO;
     266           0 : static BOOL             demo_light_on = NO;
     267           0 : static                  GLfloat sun_off[4] = {0.0, 0.0, 0.0, 1.0};
     268           0 : static GLfloat  demo_light_position[4] = { DEMO_LIGHT_POSITION, 1.0 };
     269             : 
     270           0 : #define DOCKED_AMBIENT_LEVEL    0.2f    // Was 0.05, 'temporarily' set to 0.2.
     271           0 : #define DOCKED_ILLUM_LEVEL              0.7f
     272           0 : static GLfloat  docked_light_ambient[4] = { DOCKED_AMBIENT_LEVEL, DOCKED_AMBIENT_LEVEL, DOCKED_AMBIENT_LEVEL, 1.0f };
     273           0 : static GLfloat  docked_light_diffuse[4] = { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEVEL, 1.0f }; // white
     274           0 : static GLfloat  docked_light_specular[4]        = { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEVEL * 0.75f, (GLfloat) 1.0f };       // yellow-white
     275             : 
     276             : // Weight of sun in ambient light calculation. 1.0 means only sun's diffuse is used for ambient, 0.0 means only sky colour is used.
     277             : // TODO: considering the size of the sun and the number of background stars might be worthwhile. -- Ahruman 20080322
     278           0 : #define SUN_AMBIENT_INFLUENCE           0.75
     279             : // How dark the default ambient level of 1.0 will be
     280           0 : #define SKY_AMBIENT_ADJUSTMENT          0.0625
     281             : 
     282             : - (BOOL) bloom
     283             : {
     284             :         return _bloom && [self detailLevel] >= DETAIL_LEVEL_EXTRAS;
     285             : }
     286             : 
     287             : - (void) setBloom: (BOOL)newBloom
     288             : {
     289             :         _bloom = !!newBloom;
     290             : }
     291             : 
     292             : - (int) currentPostFX
     293             : {
     294             :         return _currentPostFX;
     295             : }
     296             : 
     297             : - (void) setCurrentPostFX: (int) newCurrentPostFX
     298             : {
     299             :         if (newCurrentPostFX < 1 || newCurrentPostFX > OO_POSTFX_ENDOFLIST - 1)
     300             :         {
     301             :                 newCurrentPostFX = OO_POSTFX_NONE;
     302             :         }
     303             :         
     304             :         if      (OO_POSTFX_NONE <= newCurrentPostFX && newCurrentPostFX <= OO_POSTFX_COLORBLINDNESS_TRITAN)
     305             :         {
     306             :                 _colorblindMode = newCurrentPostFX;
     307             :         }               
     308             :         
     309             :         _currentPostFX = newCurrentPostFX;
     310             : }
     311             : 
     312             : 
     313             : - (void) terminatePostFX:(int)postFX
     314             : {
     315             :         if ([self currentPostFX] == postFX)
     316             :         {
     317             :                 [self setCurrentPostFX:[self colorblindMode]];
     318             :         }
     319             : }
     320             : 
     321             : - (int) nextColorblindMode:(int) index
     322             : {
     323             :         if (++index > OO_POSTFX_COLORBLINDNESS_TRITAN)
     324             :                 index = OO_POSTFX_NONE;
     325             :         
     326             :         return index;
     327             : }
     328             : 
     329             : - (int) prevColorblindMode:(int) index
     330             : {
     331             :         if (--index < OO_POSTFX_NONE)
     332             :                 index = OO_POSTFX_COLORBLINDNESS_TRITAN;
     333             :         
     334             :         return index;
     335             : }
     336             : 
     337             : - (int) colorblindMode
     338             : {
     339             :         return _colorblindMode;
     340             : }
     341             : 
     342           0 : - (void) initTargetFramebufferWithViewSize:(NSSize)viewSize
     343             : {
     344             :         // liberate us from the 0.0 to 1.0 rgb range!
     345             :         OOGL(glClampColor(GL_CLAMP_VERTEX_COLOR, GL_FALSE));
     346             :         OOGL(glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE));
     347             :         OOGL(glClampColor(GL_CLAMP_FRAGMENT_COLOR, GL_FALSE));
     348             : 
     349             :         // have to do this because on my machine the default framebuffer is not zero
     350             :         OOGL(glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &defaultDrawFBO));
     351             : 
     352             :         GLint previousProgramID;
     353             :         OOGL(glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgramID));
     354             :         GLint previousTextureID;
     355             :         OOGL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousTextureID));
     356             :         GLint previousVAO;
     357             :         OOGL(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &previousVAO));
     358             :         GLint previousArrayBuffer;
     359             :         OOGL(glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &previousArrayBuffer));
     360             :         GLint previousElementBuffer;
     361             :         OOGL(glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &previousElementBuffer));
     362             : 
     363             :         // create MSAA framebuffer and attach MSAA texture and depth buffer to framebuffer
     364             :         OOGL(glGenFramebuffers(1, &msaaFramebufferID));
     365             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebufferID));
     366             :         
     367             :         // creating MSAA texture that should be rendered into
     368             :         OOGL(glGenTextures(1, &msaaTextureID));
     369             :         OOGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msaaTextureID));
     370             :         OOGL(glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA16F, (GLsizei)viewSize.width, (GLsizei)viewSize.height, GL_TRUE));
     371             :         OOGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0));
     372             :         OOGL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, msaaTextureID, 0));
     373             :         
     374             :         // create necessary MSAA depth render buffer
     375             :         OOGL(glGenRenderbuffers(1, &msaaDepthBufferID));
     376             :         OOGL(glBindRenderbuffer(GL_RENDERBUFFER, msaaDepthBufferID));
     377             :         OOGL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT32F, (GLsizei)viewSize.width, (GLsizei)viewSize.height));
     378             :         OOGL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
     379             :         OOGL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msaaDepthBufferID));
     380             :         
     381             :         if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
     382             :         {
     383             :                 OOLogERR(@"initTargetFramebufferWithViewSize.result", @"%@", @"***** Error: Multisample framebuffer not complete");
     384             :         }
     385             :         
     386             :         // create framebuffer and attach texture and depth buffer to framebuffer
     387             :         OOGL(glGenFramebuffers(1, &targetFramebufferID));
     388             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, targetFramebufferID));
     389             :         
     390             :         // creating texture that should be rendered into
     391             :         OOGL(glGenTextures(1, &targetTextureID));
     392             :         OOGL(glBindTexture(GL_TEXTURE_2D, targetTextureID));
     393             :         OOGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei)viewSize.width, (GLsizei)viewSize.height, 0, GL_RGBA, GL_FLOAT, NULL));
     394             :         OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
     395             :         OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
     396             :         OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
     397             :         OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
     398             :         OOGL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, targetTextureID, 0));
     399             :         
     400             :         // create necessary depth render buffer
     401             :         OOGL(glGenRenderbuffers(1, &targetDepthBufferID));
     402             :         OOGL(glBindRenderbuffer(GL_RENDERBUFFER, targetDepthBufferID));
     403             :         OOGL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, (GLsizei)viewSize.width, (GLsizei)viewSize.height));
     404             :         OOGL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, targetDepthBufferID));
     405             :         
     406             :         GLenum attachment[1] = { GL_COLOR_ATTACHMENT0 };
     407             :         OOGL(glDrawBuffers(1, attachment));
     408             :         
     409             :         if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
     410             :         {
     411             :                 OOLogERR(@"initTargetFramebufferWithViewSize.result", @"%@", @"***** Error: Framebuffer not complete");
     412             :         }
     413             :         
     414             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
     415             :         
     416             :         targetFramebufferSize = viewSize;
     417             :         
     418             :         // passthrough buffer
     419             :         // This is a framebuffer whose sole purpose is to pass on the texture rendered from the game to the blur and the final bloom
     420             :         // shaders. We need it in order to be able to use the OpenGL 3.3 layout (location = x) out vec4 outVector; construct, which allows
     421             :         // us to perform multiple render target operations needed for bloom. The alternative would be to not use this and change all our
     422             :         // shaders to be OpenGL 3.3 compatible, but given how Oolite synthesizes them and the work needed to port them over, well yeah no,
     423             :         // not doing it at this time - Nikos 20220814.
     424             :         OOGL(glGenFramebuffers(1, &passthroughFramebufferID));
     425             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, passthroughFramebufferID));
     426             :         
     427             :         // creating textures that should be rendered into
     428             :         OOGL(glGenTextures(2, passthroughTextureID));
     429             :         for (unsigned int i = 0; i < 2; i++)
     430             :         {
     431             :                 OOGL(glBindTexture(GL_TEXTURE_2D, passthroughTextureID[i]));
     432             :                 OOGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei)viewSize.width, (GLsizei)viewSize.height, 0, GL_RGBA, GL_FLOAT, NULL));
     433             :                 OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
     434             :                 OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
     435             :                 OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
     436             :                 OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
     437             :                 OOGL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, passthroughTextureID[i], 0));
     438             :         }
     439             :         
     440             :         GLenum attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
     441             :         OOGL(glDrawBuffers(2, attachments));
     442             :         
     443             :         if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
     444             :         {
     445             :                 OOLogERR(@"initTargetFramebufferWithViewSize.result", @"%@", @"***** Error: Passthrough framebuffer not complete");
     446             :         }
     447             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
     448             :         
     449             :         // ping-pong-framebuffer for blurring
     450             :     OOGL(glGenFramebuffers(2, pingpongFBO));
     451             :     OOGL(glGenTextures(2, pingpongColorbuffers));
     452             :     for (unsigned int i = 0; i < 2; i++)
     453             :     {
     454             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]));
     455             :         OOGL(glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[i]));
     456             :         OOGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei)viewSize.width, (GLsizei)viewSize.height, 0, GL_RGBA, GL_FLOAT, NULL));
     457             :         OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
     458             :         OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
     459             :         OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); // we clamp to the edge as the blur filter would otherwise sample repeated texture values!
     460             :         OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
     461             :         OOGL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongColorbuffers[i], 0));
     462             :         // check if framebuffers are complete (no need for depth buffer)
     463             :         if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
     464             :                 {
     465             :             OOLogERR(@"initTargetFramebufferWithViewSize.result", @"%@", @"***** Error: Pingpong framebuffers not complete");
     466             :                 }
     467             :     }
     468             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
     469             :         
     470             :         _bloom = [self detailLevel] >= DETAIL_LEVEL_EXTRAS;
     471             :         _currentPostFX = _colorblindMode = OO_POSTFX_NONE;
     472             : 
     473             :         /* TODO: in OOEnvironmentCubeMap.m call these bind functions not with 0 but with "previousXxxID"s:
     474             :           - OOGL(glBindTexture(GL_TEXTURE_CUBE_MAP, 0));
     475             :           - OOGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
     476             :           - OOGL(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0));
     477             :         */
     478             :         
     479             :         // shader for drawing a textured quad on the passthrough framebuffer and preparing it for bloom using MRT
     480             :         if (![[OOOpenGLExtensionManager sharedManager] shadersForceDisabled])
     481             :         {
     482             :                 textureProgram = [[OOShaderProgram shaderProgramWithVertexShaderName:@"oolite-texture.vertex"
     483             :                                                                                                         fragmentShaderName:@"oolite-texture.fragment"
     484             :                                                                                                         prefix:@"#version 330\n"
     485             :                                                                                                         attributeBindings:[NSDictionary dictionary]] retain];
     486             :                 // shader for blurring the over-threshold brightness image generated from the previous step using Gaussian filter
     487             :                 blurProgram = [[OOShaderProgram shaderProgramWithVertexShaderName:@"oolite-blur.vertex"
     488             :                                                                                                         fragmentShaderName:@"oolite-blur.fragment"
     489             :                                                                                                         prefix:@"#version 330\n"
     490             :                                                                                                         attributeBindings:[NSDictionary dictionary]] retain];
     491             :                 // shader for applying bloom and any necessary post-proc fx, tonemapping and gamma correction
     492             :                 finalProgram = [[OOShaderProgram shaderProgramWithVertexShaderName:@"oolite-final.vertex"
     493             : #if OOLITE_WINDOWS
     494             :                                                                                                         fragmentShaderName:[[UNIVERSE gameView] hdrOutput] ? @"oolite-final-hdr.fragment" : @"oolite-final.fragment"
     495             : #else
     496             :                                                                                                         fragmentShaderName:@"oolite-final.fragment"
     497             : #endif
     498             :                                                                                                         prefix:@"#version 330\n"
     499             :                                                                                                         attributeBindings:[NSDictionary dictionary]] retain];
     500             :         }
     501             :         
     502             :         OOGL(glGenVertexArrays(1, &quadTextureVAO));
     503             :         OOGL(glGenBuffers(1, &quadTextureVBO));
     504             :         OOGL(glGenBuffers(1, &quadTextureEBO));
     505             : 
     506             :         OOGL(glBindVertexArray(quadTextureVAO));
     507             : 
     508             :         OOGL(glBindBuffer(GL_ARRAY_BUFFER, quadTextureVBO));
     509             :         OOGL(glBufferData(GL_ARRAY_BUFFER, sizeof(framebufferQuadVertices), framebufferQuadVertices, GL_STATIC_DRAW));
     510             : 
     511             :         OOGL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadTextureEBO));
     512             :         OOGL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(framebufferQuadIndices), framebufferQuadIndices, GL_STATIC_DRAW));
     513             : 
     514             :         OOGL(glEnableVertexAttribArray(0));
     515             :         // position attribute
     516             :         OOGL(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0));
     517             :         OOGL(glEnableVertexAttribArray(1));
     518             :         // texture coord attribute
     519             :         OOGL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))));
     520             : 
     521             : 
     522             :         // restoring previous bindings
     523             :         OOGL(glUseProgram(previousProgramID));
     524             :         OOGL(glBindTexture(GL_TEXTURE_2D, previousTextureID));
     525             :         OOGL(glBindVertexArray(previousVAO));
     526             :         OOGL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, previousElementBuffer));
     527             :         OOGL(glBindBuffer(GL_ARRAY_BUFFER, previousArrayBuffer));
     528             : 
     529             : }
     530             : 
     531             : 
     532           0 : - (void) deleteOpenGLObjects
     533             : {
     534             :         OOGL(glDeleteTextures(1, &msaaTextureID));
     535             :         OOGL(glDeleteTextures(1, &targetTextureID));
     536             :         OOGL(glDeleteTextures(2, passthroughTextureID));
     537             :         OOGL(glDeleteTextures(2, pingpongColorbuffers));
     538             :         OOGL(glDeleteRenderbuffers(1, &msaaDepthBufferID));
     539             :         OOGL(glDeleteRenderbuffers(1, &targetDepthBufferID));
     540             :         OOGL(glDeleteFramebuffers(1, &msaaFramebufferID));
     541             :         OOGL(glDeleteFramebuffers(1, &targetFramebufferID));
     542             :         OOGL(glDeleteFramebuffers(2, pingpongFBO));
     543             :         OOGL(glDeleteFramebuffers(1, &passthroughFramebufferID));
     544             :         OOGL(glDeleteVertexArrays(1, &quadTextureVAO));
     545             :         OOGL(glDeleteBuffers(1, &quadTextureVBO));
     546             :         OOGL(glDeleteBuffers(1, &quadTextureEBO));
     547             :         [textureProgram release];
     548             :         [blurProgram release];
     549             :         [finalProgram release];
     550             : }
     551             : 
     552             : 
     553           0 : - (void) resizeTargetFramebufferWithViewSize:(NSSize)viewSize
     554             : {
     555             :         int i;
     556             :         // resize MSAA color attachment
     557             :         OOGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msaaTextureID));
     558             :         OOGL(glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA16F, (GLsizei)viewSize.width, (GLsizei)viewSize.height, GL_TRUE));
     559             :         OOGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0));
     560             :         
     561             :         // resize MSAA depth attachment
     562             :         OOGL(glBindRenderbuffer(GL_RENDERBUFFER, msaaDepthBufferID));
     563             :         OOGL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT32F, (GLsizei)viewSize.width, (GLsizei)viewSize.height));
     564             :         OOGL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
     565             :         
     566             :         // resize color attachments
     567             :         OOGL(glBindTexture(GL_TEXTURE_2D, targetTextureID));
     568             :         OOGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei)viewSize.width, (GLsizei)viewSize.height, 0, GL_RGBA, GL_FLOAT, NULL));
     569             :         OOGL(glBindTexture(GL_TEXTURE_2D, 0));
     570             :         
     571             :         for (i = 0; i < 2; i++)
     572             :         {
     573             :                 OOGL(glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[i]));
     574             :                 OOGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei)viewSize.width, (GLsizei)viewSize.height, 0, GL_RGBA, GL_FLOAT, NULL));
     575             :                 OOGL(glBindTexture(GL_TEXTURE_2D, 0));
     576             :         }
     577             :         
     578             :         for (i = 0; i < 2; i++)
     579             :         {
     580             :                 OOGL(glBindTexture(GL_TEXTURE_2D, passthroughTextureID[i]));
     581             :                 OOGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei)viewSize.width, (GLsizei)viewSize.height, 0, GL_RGBA, GL_FLOAT, NULL));
     582             :                 OOGL(glBindTexture(GL_TEXTURE_2D, 0));
     583             :         }
     584             :         
     585             :         // resize depth attachment
     586             :         OOGL(glBindRenderbuffer(GL_RENDERBUFFER, targetDepthBufferID));
     587             :         OOGL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, (GLsizei)viewSize.width, (GLsizei)viewSize.height));
     588             :         OOGL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
     589             :         
     590             :         targetFramebufferSize.width = viewSize.width;
     591             :         targetFramebufferSize.height = viewSize.height;
     592             : }
     593             : 
     594             : 
     595           0 : - (void) drawTargetTextureIntoDefaultFramebuffer
     596             : {
     597             :         // save previous bindings state
     598             :         GLint previousFBO;
     599             :         OOGL(glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previousFBO));
     600             :         GLint previousProgramID;
     601             :         OOGL(glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgramID));
     602             :         GLint previousTextureID;
     603             :         OOGL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousTextureID));
     604             :         GLint previousVAO;
     605             :         OOGL(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &previousVAO));
     606             :         GLint previousActiveTexture;
     607             :         OOGL(glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture));     
     608             :         
     609             :         OOGL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
     610             :         // fixes transparency issue for some reason
     611             :         OOGL(glDisable(GL_BLEND));
     612             :         
     613             :         GLhandleARB program = [textureProgram program];
     614             :         GLhandleARB blur = [blurProgram program];
     615             :         GLhandleARB final = [finalProgram program];
     616             :         NSSize viewSize = [gameView viewSize];
     617             :         float fboResolution[2] = {viewSize.width, viewSize.height};
     618             : 
     619             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, passthroughFramebufferID));
     620             :         OOGL(glClear(GL_COLOR_BUFFER_BIT));
     621             : 
     622             :         OOGL(glUseProgram(program));
     623             :         OOGL(glBindTexture(GL_TEXTURE_2D, targetTextureID));
     624             :         OOGL(glUniform1i(glGetUniformLocation(program, "image"), 0));
     625             :         
     626             :         
     627             :         OOGL(glBindVertexArray(quadTextureVAO));
     628             :         OOGL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0));
     629             :         OOGL(glBindVertexArray(0));
     630             :         
     631             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
     632             :         
     633             :                 
     634             :         BOOL horizontal = YES, firstIteration = YES;
     635             :         unsigned int amount = [self bloom] ? 10 : 0; // if not blooming, why bother with the heavy calculations?
     636             :         OOGL(glUseProgram(blur));
     637             :         for (unsigned int i = 0; i < amount; i++)
     638             :         {
     639             :                 OOGL(glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]));
     640             :                 OOGL(glUniform1i(glGetUniformLocation(blur, "horizontal"), horizontal));
     641             :                 OOGL(glActiveTexture(GL_TEXTURE0));
     642             :                 // bind texture of other framebuffer (or scene if first iteration)
     643             :                 OOGL(glBindTexture(GL_TEXTURE_2D, firstIteration ? passthroughTextureID[1] : pingpongColorbuffers[!horizontal]));  
     644             :                 OOGL(glUniform1i(glGetUniformLocation([blurProgram program], "imageIn"), 0));
     645             :                 OOGL(glBindVertexArray(quadTextureVAO));
     646             :                 OOGL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0));
     647             :                 OOGL(glBindVertexArray(0));
     648             :                 horizontal = !horizontal;
     649             :                 firstIteration = NO;
     650             :         }
     651             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
     652             :         
     653             :         
     654             :         OOGL(glUseProgram(final));
     655             : 
     656             :         OOGL(glActiveTexture(GL_TEXTURE0));
     657             :         OOGL(glBindTexture(GL_TEXTURE_2D, passthroughTextureID[0]));
     658             :         OOGL(glUniform1i(glGetUniformLocation(final, "scene"), 0));
     659             :         OOGL(glUniform1i(glGetUniformLocation(final, "bloom"), [self bloom]));
     660             :         OOGL(glUniform1f(glGetUniformLocation(final, "uTime"), [self getTime]));
     661             :         OOGL(glUniform2fv(glGetUniformLocation(final, "uResolution"), 1, fboResolution));
     662             :         OOGL(glUniform1i(glGetUniformLocation(final, "uPostFX"), [self currentPostFX]));
     663             : #if OOLITE_WINDOWS
     664             :         if([gameView hdrOutput])
     665             :         {
     666             :                 OOGL(glUniform1f(glGetUniformLocation(final, "uMaxBrightness"), [gameView hdrMaxBrightness]));
     667             :                 OOGL(glUniform1f(glGetUniformLocation(final, "uPaperWhiteBrightness"), [gameView hdrPaperWhiteBrightness]));
     668             :                 OOGL(glUniform1i(glGetUniformLocation(final, "uHDRToneMapper"), [gameView hdrToneMapper]));
     669             :         }
     670             : #endif
     671             :         OOGL(glUniform1i(glGetUniformLocation(final, "uSDRToneMapper"), [gameView sdrToneMapper]));
     672             :         
     673             :         OOGL(glActiveTexture(GL_TEXTURE1));
     674             :         OOGL(glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[!horizontal]));
     675             :         OOGL(glUniform1i(glGetUniformLocation(final, "bloomBlur"), 1));
     676             :         OOGL(glUniform1f(glGetUniformLocation(final, "uSaturation"), [gameView colorSaturation]));
     677             :         
     678             :         OOGL(glBindVertexArray(quadTextureVAO));
     679             :         OOGL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0));
     680             :         
     681             :         // restore GL_TEXTURE1 to 0, just in case we are returning from a
     682             :         // DETAIL_LEVEL_NORMAL to DETAIL_LEVEL_SHADERS
     683             :         OOGL(glBindTexture(GL_TEXTURE_2D, 0));
     684             : 
     685             :         // restore previous bindings
     686             :         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, previousFBO));
     687             :         OOGL(glActiveTexture(previousActiveTexture));
     688             :         OOGL(glBindTexture(GL_TEXTURE_2D, previousTextureID));
     689             :         OOGL(glUseProgram(previousProgramID));
     690             :         OOGL(glBindVertexArray(previousVAO));
     691             :         OOGL(glEnable(GL_BLEND));
     692             : }
     693             : 
     694             : - (id) initWithGameView:(MyOpenGLView *)inGameView
     695             : {
     696             :         if (gSharedUniverse != nil)
     697             :         {
     698             :                 [self release];
     699             :                 [NSException raise:NSInternalInconsistencyException format:@"%s: expected only one Universe to exist at a time.", __PRETTY_FUNCTION__];
     700             :         }
     701             :         
     702             :         OO_DEBUG_PROGRESS(@"%@", @"Universe initWithGameView:");
     703             :         
     704             :         self = [super init];
     705             :         if (self == nil)  return nil;
     706             :         
     707             :         _doingStartUp = YES;
     708             : 
     709             :         OOInitReallyRandom([NSDate timeIntervalSinceReferenceDate] * 1e9);
     710             :         
     711             :         NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
     712             :         
     713             :         // prefs value no longer used - per save game but startup needs to
     714             :         // be non-strict
     715             :         useAddOns = [[NSString alloc] initWithString:SCENARIO_OXP_DEFINITION_ALL];
     716             :         
     717             :         [self setGameView:inGameView];
     718             :         gSharedUniverse = self;
     719             :         
     720             :         allPlanets = [[NSMutableArray alloc] init];
     721             :         allStations = [[NSMutableSet alloc] init];
     722             :         
     723             :         OOCPUInfoInit();
     724             :         [OOJoystickManager sharedStickHandler];
     725             :         
     726             :         // init OpenGL extension manager (must be done before any other threads might use it)
     727             :         [OOOpenGLExtensionManager sharedManager];
     728             :         [self setDetailLevelDirectly:[prefs oo_intForKey:@"detailLevel"
     729             :                                                                 defaultValue:[[OOOpenGLExtensionManager sharedManager] defaultDetailLevel]]];
     730             :                                                                 
     731             :         [self initTargetFramebufferWithViewSize:[gameView viewSize]];
     732             :         
     733             :         [OOMaterial setUp];
     734             :         
     735             :         // Preload cache
     736             :         [OOCacheManager sharedCache];
     737             :         
     738             : #if OOLITE_SPEECH_SYNTH
     739             :         OOLog(@"speech.synthesis", @"Spoken messages are %@.", ([prefs oo_boolForKey:@"speech_on" defaultValue:NO] ? @"on" :@"off"));
     740             : #endif
     741             :         
     742             :         // init the Resource Manager
     743             :         [ResourceManager setUseAddOns:useAddOns];       // also logs the paths if changed
     744             :         
     745             :         // Set up the internal game strings
     746             :         [self loadDescriptions];
     747             :         // DESC expansion is now possible!
     748             :         
     749             :         // load starting saves
     750             :         [self loadScenarios];
     751             : 
     752             :         autoSave = [prefs oo_boolForKey:@"autosave" defaultValue:NO];
     753             :         wireframeGraphics = [prefs oo_boolForKey:@"wireframe-graphics" defaultValue:NO];
     754             :         doProcedurallyTexturedPlanets = [prefs oo_boolForKey:@"procedurally-textured-planets" defaultValue:YES];
     755             :         [inGameView setGammaValue:[prefs oo_floatForKey:@"gamma-value" defaultValue:1.0f]];
     756             :         [inGameView setMsaa:[prefs oo_boolForKey:@"anti-aliasing" defaultValue:NO]];
     757             :         OOLog(@"MSAA.setup", @"Multisample anti-aliasing %@requested.", [inGameView msaa] ? @"" : @"not ");
     758             :         [inGameView setFov:OOClamp_0_max_f([prefs oo_floatForKey:@"fov-value" defaultValue:57.2f], MAX_FOV_DEG) fromFraction:NO];
     759             :         if ([inGameView fov:NO] < MIN_FOV_DEG)  [inGameView setFov:MIN_FOV_DEG fromFraction:NO];
     760             : 
     761             :         [self setECMVisualFXEnabled:[prefs oo_boolForKey:@"ecm-visual-fx" defaultValue:YES]];
     762             :         
     763             :         // Set up speech synthesizer.
     764             : #if OOLITE_SPEECH_SYNTH
     765             : #if OOLITE_MAC_OS_X
     766             :         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
     767             :         ^{
     768             :                 /*
     769             :                         NSSpeechSynthesizer can take over a second on an SSD and several
     770             :                         seconds on an HDD for a cold start, and a third of a second upward
     771             :                         for a warm start. There are no particular thread safety consider-
     772             :                         ations documented for NSSpeechSynthesizer, so I'm assuming the
     773             :                         default one-thread-at-a-time access rule applies.
     774             :                         -- Ahruman 2012-09-13
     775             :                 */
     776             :                 OOLog(@"speech.setup.begin", @"Starting to set up speech synthesizer.");
     777             :                 NSSpeechSynthesizer *synth = [[NSSpeechSynthesizer alloc] init];
     778             :                 OOLog(@"speech.setup.end", @"Finished setting up speech synthesizer.");
     779             :                 speechSynthesizer = synth;
     780             :         });
     781             : #elif OOLITE_ESPEAK
     782             :         int volume = [OOSound masterVolume] * 100;
     783             :         espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 100, NULL, 0);
     784             :         espeak_SetParameter(espeakPUNCTUATION, espeakPUNCT_NONE, 0);
     785             :         espeak_SetParameter(espeakVOLUME, volume, 0);
     786             :         espeak_voices = espeak_ListVoices(NULL);
     787             :         for (espeak_voice_count = 0;
     788             :              espeak_voices[espeak_voice_count];
     789             :              ++espeak_voice_count)
     790             :                 /**/;
     791             : #endif
     792             : #endif
     793             :         
     794             :         [[GameController sharedController] logProgress:DESC(@"loading-ships")];
     795             :         // Load ship data
     796             :         
     797             :         [OOShipRegistry sharedRegistry];
     798             :         
     799             :         entities = [[NSMutableArray arrayWithCapacity:MAX_NUMBER_OF_ENTITIES] retain];
     800             :         
     801             :         [[GameController sharedController] logProgress:OOExpandKeyRandomized(@"loading-miscellany")];
     802             :         
     803             :         // this MUST have the default no. of rows else the GUI_ROW macros in PlayerEntity.h need modification
     804             :         gui = [[GuiDisplayGen alloc] init]; // alloc retains
     805             :         comm_log_gui = [[GuiDisplayGen alloc] init]; // alloc retains
     806             :         
     807             :         missiontext = [[ResourceManager dictionaryFromFilesNamed:@"missiontext.plist" inFolder:@"Config" andMerge:YES] retain];
     808             :         
     809             :         waypoints = [[NSMutableDictionary alloc] init];
     810             :         
     811             :         [self setUpSettings];
     812             :         
     813             :         // can't do this here as it might lock an OXZ open
     814             :         // [self preloadSounds];        // Must be after setUpSettings.
     815             :         
     816             :         // Preload particle effect textures:
     817             :         [OOLightParticleEntity setUpTexture];
     818             :         [OOFlashEffectEntity setUpTexture];
     819             : 
     820             :         
     821             :         // set up cargopod templates
     822             :         [self setUpCargoPods];
     823             : 
     824             :         PlayerEntity *player = [PlayerEntity sharedPlayer];
     825             :         [player deferredInit];
     826             :         [self addEntity:player];
     827             :         
     828             :         [player setStatus:STATUS_START_GAME];
     829             :         [player setShowDemoShips: YES];
     830             :         
     831             :         [self setUpInitialUniverse];
     832             :         
     833             :         universeRegion = [[CollisionRegion alloc] initAsUniverse];
     834             :         entitiesDeadThisUpdate = [[NSMutableSet alloc] init];
     835             :         framesDoneThisUpdate = 0;
     836             :         
     837             :         [[GameController sharedController] logProgress:DESC(@"initializing-debug-support")];
     838             :         OOInitDebugSupport();
     839             :         
     840             :         [[GameController sharedController] logProgress:DESC(@"running-scripts")];
     841             :         [player completeSetUp];
     842             :         
     843             :         [[GameController sharedController] logProgress:DESC(@"populating-space")];
     844             :         [self populateNormalSpace];
     845             :         
     846             :         [[GameController sharedController] logProgress:OOExpandKeyRandomized(@"loading-miscellany")];
     847             :         
     848             : #if OO_LOCALIZATION_TOOLS
     849             :         [self runLocalizationTools];
     850             : #if DEBUG_GRAPHVIZ
     851             :         [self dumpDebugGraphViz];
     852             : #endif
     853             : #endif
     854             :         
     855             :         [player startUpComplete];
     856             :         _doingStartUp = NO;
     857             :         
     858             :         return self;
     859             : }
     860             : 
     861             : 
     862           0 : - (void) dealloc
     863             : {
     864             :         gSharedUniverse = nil;
     865             :         
     866             :         [currentMessage release];
     867             :         
     868             :         [gui release];
     869             :         [message_gui release];
     870             :         [comm_log_gui release];
     871             :         
     872             :         [entities release];
     873             :         
     874             :         [commodities release];
     875             :         
     876             :         [_descriptions release];
     877             :         [characters release];
     878             :         [customSounds release];
     879             :         [globalSettings release];
     880             :         [systemManager release];
     881             :         [missiontext release];
     882             :         [equipmentData release];
     883             :         [equipmentDataOutfitting release];
     884             :         [demo_ships release];
     885             :         [autoAIMap release];
     886             :         [screenBackgrounds release];
     887             :         [gameView release];
     888             :         [populatorSettings release];
     889             :         [system_repopulator release];
     890             :         [allPlanets release];
     891             :         [allStations release];
     892             :         [explosionSettings release];
     893             :         
     894             :         [activeWormholes release];                              
     895             :         [characterPool release];
     896             :         [universeRegion release];
     897             :         [cargoPods release];
     898             : 
     899             :         DESTROY(_firstBeacon);
     900             :         DESTROY(_lastBeacon);
     901             :         DESTROY(waypoints);
     902             :         
     903             :         unsigned i;
     904             :         for (i = 0; i < 256; i++)  [system_names[i] release];
     905             :         
     906             :         [entitiesDeadThisUpdate release];
     907             :         
     908             :         [[OOCacheManager sharedCache] flush];
     909             :         
     910             : #if OOLITE_SPEECH_SYNTH
     911             :         [speechArray release];
     912             : #if OOLITE_MAC_OS_X
     913             :         [speechSynthesizer release];
     914             : #elif OOLITE_ESPEAK
     915             :         espeak_Cancel();
     916             : #endif
     917             : #endif
     918             :         [conditionScripts release];
     919             : 
     920             :         [self deleteOpenGLObjects];
     921             :         
     922             :         [super dealloc];
     923             : }
     924             : 
     925             : 
     926             : - (NSUInteger) sessionID
     927             : {
     928             :         return _sessionID;
     929             : }
     930             : 
     931             : 
     932             : - (BOOL) doingStartUp
     933             : {
     934             :         return _doingStartUp;
     935             : }
     936             : 
     937             : 
     938             : - (BOOL) doProcedurallyTexturedPlanets
     939             : {
     940             :         return doProcedurallyTexturedPlanets;
     941             : }
     942             : 
     943             : 
     944             : - (void) setDoProcedurallyTexturedPlanets:(BOOL) value
     945             : {
     946             :         doProcedurallyTexturedPlanets = !!value;        // ensure yes or no
     947             :         [[NSUserDefaults standardUserDefaults] setBool:doProcedurallyTexturedPlanets forKey:@"procedurally-textured-planets"];
     948             : }
     949             : 
     950             : 
     951             : - (NSString *) useAddOns
     952             : {
     953             :         return useAddOns;
     954             : }
     955             : 
     956             : 
     957             : - (BOOL) setUseAddOns:(NSString *) newUse fromSaveGame:(BOOL) saveGame
     958             : {
     959             :         return [self setUseAddOns:newUse fromSaveGame:saveGame forceReinit:NO];
     960             : }
     961             : 
     962             : 
     963             : - (BOOL) setUseAddOns:(NSString *) newUse fromSaveGame:(BOOL) saveGame forceReinit:(BOOL)force
     964             : {
     965             :         if (!force && [newUse isEqualToString:useAddOns])
     966             :         {
     967             :                 return YES;
     968             :         } 
     969             :         DESTROY(useAddOns);
     970             :         useAddOns = [newUse retain];
     971             : 
     972             :         return [self reinitAndShowDemo:!saveGame];
     973             : }
     974             : 
     975             : 
     976             : 
     977             : - (NSUInteger) entityCount
     978             : {
     979             :         return [entities count];
     980             : }
     981             : 
     982             : 
     983             : #ifndef NDEBUG
     984             : - (void) debugDumpEntities
     985             : {
     986             :         int                             i;
     987             :         int                             show_count = n_entities;
     988             :         
     989             :         if (!OOLogWillDisplayMessagesInClass(@"universe.objectDump"))  return;
     990             :         
     991             :         OOLog(@"universe.objectDump", @"DEBUG: Entity Dump - [entities count] = %lu,\tn_entities = %u", [entities count], n_entities);
     992             :         
     993             :         OOLogIndent();
     994             :         for (i = 0; i < show_count; i++)
     995             :         {
     996             :                 OOLog(@"universe.objectDump", @"Ent:%4u  %@", i, [sortedEntities[i] descriptionForObjDump]);
     997             :         }
     998             :         OOLogOutdent();
     999             :         
    1000             :         if ([entities count] != n_entities)
    1001             :         {
    1002             :                 OOLog(@"universe.objectDump", @"entities = %@", [entities description]);
    1003             :         }
    1004             : }
    1005             : 
    1006             : 
    1007             : - (NSArray *) entityList
    1008             : {
    1009             :         return [NSArray arrayWithArray:entities];
    1010             : }
    1011             : #endif
    1012             : 
    1013             : 
    1014             : - (void) pauseGame
    1015             : {
    1016             :         // deal with the machine going to sleep, or player pressing 'p'.
    1017             :         PlayerEntity    *player = PLAYER;
    1018             :         
    1019             :         [self setPauseMessageVisible:NO];
    1020             :         NSString *pauseKey = [PLAYER keyBindingDescription2:@"key_pausebutton"];
    1021             :         
    1022             :         if ([player status] == STATUS_DOCKED)
    1023             :         {
    1024             :                 if ([gui setForegroundTextureKey:@"paused_docked_overlay"])
    1025             :                 {
    1026             :                         [gui drawGUI:1.0 drawCursor:NO];
    1027             :                 }
    1028             :                 else
    1029             :                 {
    1030             :                         [self setPauseMessageVisible:YES];
    1031             :                         [self addMessage:OOExpandKey(@"game-paused-docked", pauseKey) forCount:1.0];
    1032             :                 }
    1033             :         }
    1034             :         else
    1035             :         {
    1036             :                 if ([player guiScreen] != GUI_SCREEN_MAIN && [gui setForegroundTextureKey:@"paused_overlay"])
    1037             :                 {
    1038             :                         [gui drawGUI:1.0 drawCursor:NO];
    1039             :                 }
    1040             :                 else
    1041             :                 {
    1042             :                         [self setPauseMessageVisible:YES];
    1043             :                         [self addMessage:OOExpandKey(@"game-paused", pauseKey) forCount:1.0];
    1044             :                 }
    1045             :         }
    1046             :         
    1047             :         [[self gameController] setGamePaused:YES];
    1048             : }
    1049             : 
    1050             : 
    1051             : - (void) carryPlayerOn:(StationEntity*)carrier inWormhole:(WormholeEntity*)wormhole
    1052             : {
    1053             :                 PlayerEntity    *player = PLAYER;
    1054             :                 OOSystemID dest = [wormhole destination];
    1055             : 
    1056             :                 [player setWormhole:wormhole];
    1057             :                 [player addScannedWormhole:wormhole];
    1058             :                 JSContext *context = OOJSAcquireContext();
    1059             :                 [player setJumpCause:@"carried"];
    1060             :                 [player setPreviousSystemID:[player systemID]];
    1061             :                 ShipScriptEvent(context, player, "shipWillEnterWitchspace", STRING_TO_JSVAL(JS_InternString(context, [[player jumpCause] UTF8String])), INT_TO_JSVAL(dest));
    1062             :                 OOJSRelinquishContext(context);
    1063             :         
    1064             :                 [self allShipsDoScriptEvent:OOJSID("playerWillEnterWitchspace") andReactToAIMessage:@"PLAYER WITCHSPACE"];
    1065             : 
    1066             :                 [player setRandom_factor:(ranrot_rand() & 255)];                                            // random factor for market values is reset
    1067             : 
    1068             : // misjump on wormhole sets correct travel time if needed
    1069             :                 [player addToAdjustTime:[wormhole travelTime]];
    1070             : // clear old entities
    1071             :                 [self removeAllEntitiesExceptPlayer];
    1072             : 
    1073             : // should we add wear-and-tear to the player ship if they're not doing
    1074             : // the jump themselves? Left out for now. - CIM
    1075             : 
    1076             :                 if (![wormhole withMisjump])
    1077             :                 {
    1078             :                         [player setSystemID:dest];
    1079             :                         [self setSystemTo: dest];
    1080             :                         
    1081             :                         [self setUpSpace];
    1082             :                         [self populateNormalSpace];
    1083             :                         [player setBounty:([player legalStatus]/2) withReason:kOOLegalStatusReasonNewSystem];
    1084             :                         if ([player random_factor] < 8) [player erodeReputation];            // every 32 systems or so, dro
    1085             :                 }
    1086             :                 else
    1087             :                 {
    1088             :                         [player setGalaxyCoordinates:[wormhole destinationCoordinates]];
    1089             : 
    1090             :                         [self setUpWitchspaceBetweenSystem:[wormhole origin] andSystem:[wormhole destination]];
    1091             : 
    1092             :                         if (randf() < 0.1) [player erodeReputation];         // once every 10 misjumps - should be much rarer than successful jumps!
    1093             :                 }
    1094             :                 // which will kick the ship out of the wormhole with the
    1095             :                 // player still aboard
    1096             :                 [wormhole disgorgeShips];
    1097             : 
    1098             :                 //reset atmospherics in case carrier was in atmosphere
    1099             :                 [UNIVERSE setSkyColorRed:0.0f           // back to black
    1100             :                                                                                          green:0.0f
    1101             :                                                                                                 blue:0.0f
    1102             :                                                                                          alpha:0.0f];
    1103             : 
    1104             :                 [self setWitchspaceBreakPattern:YES];
    1105             :                 [player doScriptEvent:OOJSID("shipWillExitWitchspace") withArgument:[player jumpCause]];
    1106             :                 [player doScriptEvent:OOJSID("shipExitedWitchspace") withArgument:[player jumpCause]];
    1107             :                 [player setWormhole:nil];
    1108             : 
    1109             : }
    1110             : 
    1111             : 
    1112             : - (void) setUpUniverseFromStation
    1113             : {
    1114             :         if (![self sun])
    1115             :         {
    1116             :                 // we're in witchspace...               
    1117             :                 
    1118             :                 PlayerEntity    *player = PLAYER;
    1119             :                 StationEntity   *dockedStation = [player dockedStation];
    1120             :                 NSPoint                 coords = [player galaxy_coordinates];
    1121             :                 // check the nearest system
    1122             :                 OOSystemID sys = [self findSystemNumberAtCoords:coords withGalaxy:[player galaxyNumber] includingHidden:YES];
    1123             :                 BOOL interstel =[dockedStation interstellarUndockingAllowed];// && (s_seed.d != coords.x || s_seed.b != coords.y); - Nikos 20110623: Do we really need the commented out check?
    1124             :                 [player setPreviousSystemID:[player currentSystemID]];
    1125             : 
    1126             :                 // remove everything except the player and the docked station
    1127             :                 if (dockedStation && !interstel)
    1128             :                 {       // jump to the nearest system
    1129             :                         [player setSystemID:sys];
    1130             :                         closeSystems = nil;
    1131             :                         [self setSystemTo: sys];
    1132             :                         int index = 0;
    1133             :                         while ([entities count] > 2)
    1134             :                         {
    1135             :                                 Entity *ent = [entities objectAtIndex:index];
    1136             :                                 if ((ent != player)&&(ent != dockedStation))
    1137             :                                 {
    1138             :                                         if (ent->isStation)  // clear out queues
    1139             :                                                 [(StationEntity *)ent clear];
    1140             :                                         [self removeEntity:ent];
    1141             :                                 }
    1142             :                                 else
    1143             :                                 {
    1144             :                                         index++;        // leave that one alone
    1145             :                                 }
    1146             :                         }
    1147             :                 }
    1148             :                 else
    1149             :                 {
    1150             :                         if (dockedStation == nil)  [self removeAllEntitiesExceptPlayer];        // get rid of witchspace sky etc. if still extant
    1151             :                 }
    1152             :                 
    1153             :                 if (!dockedStation || !interstel) 
    1154             :                 {
    1155             :                         [self setUpSpace];      // launching from station that jumped from interstellar space to normal space.
    1156             :                         [self populateNormalSpace];
    1157             :                         if (dockedStation)
    1158             :                         {
    1159             :                                 if ([dockedStation maxFlightSpeed] > 0) // we are a carrier: exit near the WitchspaceExitPosition
    1160             :                                 {
    1161             :                                         float           d1 = [self randomDistanceWithinScanner];
    1162             :                                         HPVector                pos = [UNIVERSE getWitchspaceExitPosition];             // no need to reset the PRNG
    1163             :                                         Quaternion      q1;
    1164             :                                         
    1165             :                                         quaternion_set_random(&q1);
    1166             :                                         if (abs((int)d1) < 2750)     
    1167             :                                         {
    1168             :                                                 d1 += ((d1 > 0.0)? 2750.0f: -2750.0f); // no closer than 2750m. Carriers are bigger than player ships.
    1169             :                                         }
    1170             :                                         Vector          v1 = vector_forward_from_quaternion(q1);
    1171             :                                         pos.x += v1.x * d1; // randomise exit position
    1172             :                                         pos.y += v1.y * d1;
    1173             :                                         pos.z += v1.z * d1;
    1174             :                                         
    1175             :                                         [dockedStation setPosition: pos];
    1176             :                                 }
    1177             :                                 [self setWitchspaceBreakPattern:YES];
    1178             :                                 [player setJumpCause:@"carried"];
    1179             :                                 [player doScriptEvent:OOJSID("shipWillExitWitchspace") withArgument:[player jumpCause]];
    1180             :                                 [player doScriptEvent:OOJSID("shipExitedWitchspace") withArgument:[player jumpCause]];
    1181             :                         }
    1182             :                 }
    1183             :         }
    1184             :         
    1185             :         if(!autoSaveNow) [self setViewDirection:VIEW_FORWARD];
    1186             :         displayGUI = NO;
    1187             :         
    1188             :         //reset atmospherics in case we ejected while we were in the atmophere
    1189             :         [UNIVERSE setSkyColorRed:0.0f           // back to black
    1190             :                                            green:0.0f
    1191             :                                                 blue:0.0f
    1192             :                                            alpha:0.0f];
    1193             : }
    1194             : 
    1195             : 
    1196             : - (void) setUpUniverseFromWitchspace
    1197             : {
    1198             :         PlayerEntity            *player;
    1199             :         
    1200             :         //
    1201             :         // check the player is still around!
    1202             :         //
    1203             :         if ([entities count] == 0)
    1204             :         {
    1205             :                 /*- the player ship -*/
    1206             :                 player = [[PlayerEntity alloc] init];   // alloc retains!
    1207             :                 
    1208             :                 [self addEntity:player];
    1209             :                 
    1210             :                 /*--*/
    1211             :         }
    1212             :         else
    1213             :         {
    1214             :                 player = [PLAYER retain];       // retained here
    1215             :         }
    1216             :         
    1217             :         [self setUpSpace];
    1218             :         [self populateNormalSpace];
    1219             :         
    1220             :         [player leaveWitchspace];
    1221             :         [player release];                                                                                       // released here
    1222             : 
    1223             :         [self setViewDirection:VIEW_FORWARD];
    1224             :         
    1225             :         [comm_log_gui printLongText:[NSString stringWithFormat:@"%@ %@", [self getSystemName:systemID], [player dial_clock_adjusted]]
    1226             :                 align:GUI_ALIGN_CENTER color:[OOColor whiteColor] fadeTime:0 key:nil addToArray:[player commLog]];
    1227             :         
    1228             :         displayGUI = NO;
    1229             : }
    1230             : 
    1231             : 
    1232             : - (void) setUpUniverseFromMisjump
    1233             : {
    1234             :         PlayerEntity            *player;
    1235             :         
    1236             :         //
    1237             :         // check the player is still around!
    1238             :         //
    1239             :         if ([entities count] == 0)
    1240             :         {
    1241             :                 /*- the player ship -*/
    1242             :                 player = [[PlayerEntity alloc] init];   // alloc retains!
    1243             :                 
    1244             :                 [self addEntity:player];
    1245             :                 
    1246             :                 /*--*/
    1247             :         }
    1248             :         else
    1249             :         {
    1250             :                 player = [PLAYER retain];       // retained here
    1251             :         }
    1252             :         
    1253             :         [self setUpWitchspace];
    1254             :         // ensure that if we got here from a jump within a planet's atmosphere,
    1255             :         // we don't get any residual air friction
    1256             :         [self setAirResistanceFactor:0.0f];
    1257             :         
    1258             :         [player leaveWitchspace];
    1259             :         [player release];                                                                                       // released here
    1260             :         
    1261             :         [self setViewDirection:VIEW_FORWARD];
    1262             :         
    1263             :         displayGUI = NO;
    1264             : }
    1265             : 
    1266             : 
    1267             : - (void) setUpWitchspace
    1268             : {
    1269             :         [self setUpWitchspaceBetweenSystem:[PLAYER systemID] andSystem:[PLAYER nextHopTargetSystemID]];
    1270             : }
    1271             : 
    1272             : 
    1273             : - (void) setUpWitchspaceBetweenSystem:(OOSystemID)s1 andSystem:(OOSystemID)s2
    1274             : {
    1275             :         // new system is hyper-centric : witchspace exit point is origin
    1276             :         
    1277             :         Entity                          *thing;
    1278             :         PlayerEntity*           player = PLAYER;
    1279             :         Quaternion                      randomQ;
    1280             :         
    1281             :         NSString*               override_key = [self keyForInterstellarOverridesForSystems:s1 :s2 inGalaxy:galaxyID];
    1282             : 
    1283             :         NSDictionary *systeminfo = [systemManager getPropertiesForSystemKey:override_key];
    1284             :         
    1285             :         [universeRegion clearSubregions];
    1286             :         
    1287             :         // fixed entities (part of the graphics system really) come first...
    1288             :         
    1289             :         /*- the sky backdrop -*/
    1290             :         OOColor *col1 = [OOColor colorWithRed:0.0 green:1.0 blue:0.5 alpha:1.0];
    1291             :         OOColor *col2 = [OOColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0];
    1292             :         thing = [[SkyEntity alloc] initWithColors:col1:col2 andSystemInfo: systeminfo]; // alloc retains!
    1293             :         [thing setScanClass: CLASS_NO_DRAW];
    1294             :         quaternion_set_random(&randomQ);
    1295             :         [thing setOrientation:randomQ];
    1296             :         [self addEntity:thing];
    1297             :         [thing release];
    1298             :         
    1299             :         /*- the dust particle system -*/
    1300             :         thing = [[DustEntity alloc] init];
    1301             :         [thing setScanClass: CLASS_NO_DRAW];
    1302             :         [self addEntity:thing];
    1303             :         [thing release];
    1304             :         
    1305             :         ambientLightLevel = [systeminfo oo_floatForKey:@"ambient_level" defaultValue:1.0];
    1306             :         [self setLighting];     // also sets initial lights positions.
    1307             :         
    1308             :         OOLog(kOOLogUniversePopulateWitchspace, @"%@", @"Populating witchspace ...");
    1309             :         OOLogIndentIf(kOOLogUniversePopulateWitchspace);
    1310             :         
    1311             :         [self clearSystemPopulator];
    1312             :         NSString *populator = [systeminfo oo_stringForKey:@"populator" defaultValue:@"interstellarSpaceWillPopulate"];
    1313             :         [system_repopulator release];
    1314             :         system_repopulator = [[systeminfo oo_stringForKey:@"repopulator" defaultValue:@"interstellarSpaceWillRepopulate"] retain];
    1315             :         JSContext *context = OOJSAcquireContext();
    1316             :         [PLAYER doWorldScriptEvent:OOJSIDFromString(populator) inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit];
    1317             :         OOJSRelinquishContext(context);
    1318             :         [self populateSystemFromDictionariesWithSun:nil andPlanet:nil];
    1319             : 
    1320             :         // systeminfo might have a 'script_actions' resource we want to activate now...
    1321             :         NSArray *script_actions = [systeminfo oo_arrayForKey:@"script_actions"];
    1322             :         if (script_actions != nil)
    1323             :         {
    1324             :                 OOStandardsDeprecated([NSString stringWithFormat:@"The script_actions system info key is deprecated for %@.",override_key]);
    1325             :                 if (!OOEnforceStandards()) 
    1326             :                 {
    1327             :                         [player runUnsanitizedScriptActions:script_actions
    1328             :                                                           allowingAIMethods:NO
    1329             :                                                                 withContextName:@"<witchspace script_actions>"
    1330             :                                                                           forTarget:nil];
    1331             :                 }
    1332             :         }
    1333             :         
    1334             :         next_repopulation = randf() * SYSTEM_REPOPULATION_INTERVAL;
    1335             : 
    1336             :         OOLogOutdentIf(kOOLogUniversePopulateWitchspace);
    1337             : }
    1338             : 
    1339             : 
    1340             : - (OOPlanetEntity *) setUpPlanet
    1341             : {
    1342             :         // set the system seed for random number generation
    1343             :         Random_Seed systemSeed = [systemManager getRandomSeedForCurrentSystem];
    1344             :         seed_for_planet_description(systemSeed);
    1345             : 
    1346             :         NSMutableDictionary *planetDict = [NSMutableDictionary dictionaryWithDictionary:[systemManager getPropertiesForCurrentSystem]];
    1347             :         [planetDict oo_setBool:YES forKey:@"mainForLocalSystem"];
    1348             :         OOPlanetEntity *a_planet = [[OOPlanetEntity alloc] initFromDictionary:planetDict withAtmosphere:[planetDict oo_boolForKey:@"has_atmosphere" defaultValue:YES] andSeed:systemSeed forSystem:systemID];
    1349             :         
    1350             :         double planet_zpos = [planetDict oo_floatForKey:@"planet_distance" defaultValue:500000];
    1351             :         planet_zpos *= [planetDict oo_floatForKey:@"planet_distance_multiplier" defaultValue:1.0];
    1352             :         
    1353             : #ifdef OO_DUMP_PLANETINFO
    1354             :         OOLog(@"planetinfo.record",@"planet zpos = %f",planet_zpos);
    1355             : #endif
    1356             :         [a_planet setPosition:(HPVector){ 0, 0, planet_zpos }];
    1357             :         [a_planet setEnergy:1000000.0];
    1358             :         
    1359             :         if ([allPlanets count]>0)    // F7 sets [UNIVERSE planet], which can lead to some trouble! TODO: track down where exactly that happens!
    1360             :         {
    1361             :                 OOPlanetEntity *tmp=[allPlanets objectAtIndex:0];
    1362             :                 [self addEntity:a_planet];
    1363             :                 [allPlanets removeObject:a_planet];
    1364             :                 cachedPlanet=a_planet;
    1365             :                 [allPlanets replaceObjectAtIndex:0 withObject:a_planet];
    1366             :                 [self removeEntity:(Entity *)tmp];
    1367             :         }
    1368             :         else
    1369             :         {
    1370             :                 [self addEntity:a_planet];
    1371             :         }
    1372             :         return [a_planet autorelease];
    1373             : }
    1374             : 
    1375             : /* At any time other than game start, any call to this must be followed
    1376             :  * by [self populateNormalSpace]. However, at game start, they need to be
    1377             :  * separated to allow Javascript startUp routines to be run in-between */
    1378             : - (void) setUpSpace
    1379             : {
    1380             :         Entity                          *thing;
    1381             : //      ShipEntity                      *nav_buoy;
    1382             :         StationEntity           *a_station;
    1383             :         OOSunEntity                     *a_sun;
    1384             :         OOPlanetEntity          *a_planet;
    1385             :         
    1386             :         HPVector                                stationPos;
    1387             :         
    1388             :         Vector                          vf;
    1389             :         id                      dict_object;
    1390             : 
    1391             :         NSDictionary            *systeminfo = [systemManager getPropertiesForCurrentSystem];
    1392             :         unsigned                        techlevel = [systeminfo oo_unsignedIntForKey:KEY_TECHLEVEL];
    1393             :         NSString                        *stationDesc = nil, *defaultStationDesc = nil;
    1394             :         OOColor                         *bgcolor;
    1395             :         OOColor                         *pale_bgcolor;
    1396             :         BOOL                            sunGoneNova;
    1397             :         
    1398             :         Random_Seed systemSeed = [systemManager getRandomSeedForCurrentSystem];
    1399             : 
    1400             :         [[GameController sharedController] logProgress:DESC(@"populating-space")];
    1401             :         
    1402             :         sunGoneNova = [systeminfo oo_boolForKey:@"sun_gone_nova" defaultValue:NO];
    1403             :         
    1404             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"setUpSpace - clearSubRegions, sky, dust");
    1405             :         [universeRegion clearSubregions];
    1406             :         
    1407             :         // fixed entities (part of the graphics system really) come first...
    1408             :         [self setSkyColorRed:0.0f
    1409             :                                    green:0.0f
    1410             :                                         blue:0.0f
    1411             :                                    alpha:0.0f];
    1412             : 
    1413             :         
    1414             : #ifdef OO_DUMP_PLANETINFO
    1415             :         OOLog(@"planetinfo.record",@"seed = %d %d %d %d",system_seed.c,system_seed.d,system_seed.e,system_seed.f);
    1416             :         OOLog(@"planetinfo.record",@"coordinates = %d %d",system_seed.d,system_seed.b);
    1417             : 
    1418             : #define SPROP(PROP)     OOLog(@"planetinfo.record",@#PROP " = \"%@\";",[systeminfo oo_stringForKey:@"" #PROP]);
    1419             : #define IPROP(PROP)     OOLog(@"planetinfo.record",@#PROP " = %d;",[systeminfo oo_intForKey:@#PROP]);
    1420             : #define FPROP(PROP)     OOLog(@"planetinfo.record",@#PROP " = %f;",[systeminfo oo_floatForKey:@"" #PROP]);
    1421             :         IPROP(government);
    1422             :         IPROP(economy);
    1423             :         IPROP(techlevel);
    1424             :         IPROP(population);
    1425             :         IPROP(productivity);
    1426             :         SPROP(name);
    1427             :         SPROP(inhabitant);
    1428             :         SPROP(inhabitants);
    1429             :         SPROP(description);
    1430             : #endif
    1431             : 
    1432             :         // set the system seed for random number generation
    1433             :         seed_for_planet_description(systemSeed);
    1434             :         
    1435             :         /*- the sky backdrop -*/
    1436             :         // colors...
    1437             :         float h1 = randf();
    1438             :         float h2 = h1 + 1.0 / (1.0 + (Ranrot() % 5));
    1439             :         while (h2 > 1.0)
    1440             :                 h2 -= 1.0;
    1441             :         OOColor *col1 = [OOColor colorWithHue:h1 saturation:randf() brightness:0.5 + randf()/2.0 alpha:1.0];
    1442             :         OOColor *col2 = [OOColor colorWithHue:h2 saturation:0.5 + randf()/2.0 brightness:0.5 + randf()/2.0 alpha:1.0];
    1443             :         
    1444             :         thing = [[SkyEntity alloc] initWithColors:col1:col2 andSystemInfo: systeminfo]; // alloc retains!
    1445             :         [thing setScanClass: CLASS_NO_DRAW];
    1446             :         [self addEntity:thing];
    1447             : //      bgcolor = [(SkyEntity *)thing skyColor];
    1448             : //
    1449             :         h1 = randf()/3.0;
    1450             :         if (h1 > 0.17)
    1451             :         {
    1452             :                 h1 += 0.33;
    1453             :         }
    1454             :         
    1455             :         ambientLightLevel = [systeminfo oo_floatForKey:@"ambient_level" defaultValue:1.0];
    1456             :         
    1457             :         // pick a main sequence colour
    1458             : 
    1459             :         dict_object=[systeminfo objectForKey:@"sun_color"];
    1460             :         if (dict_object!=nil) 
    1461             :         {
    1462             :                 bgcolor = [OOColor colorWithDescription:dict_object];
    1463             :         }
    1464             :         else
    1465             :         {
    1466             :                 bgcolor = [OOColor colorWithHue:h1 saturation:0.75*randf() brightness:0.65+randf()/5.0 alpha:1.0];
    1467             :         }
    1468             : 
    1469             :         pale_bgcolor = [bgcolor blendedColorWithFraction:0.5 ofColor:[OOColor whiteColor]];
    1470             :         [thing release];
    1471             :         /*--*/
    1472             :         
    1473             :         /*- the dust particle system -*/
    1474             :         thing = [[DustEntity alloc] init];      // alloc retains!
    1475             :         [thing setScanClass: CLASS_NO_DRAW];
    1476             :         [self addEntity:thing];
    1477             :         [(DustEntity *)thing setDustColor:pale_bgcolor]; 
    1478             :         [thing release];
    1479             :         /*--*/
    1480             : 
    1481             :         float defaultSunFlare = randf()*0.1;
    1482             :         float defaultSunHues = 0.5+randf()*0.5;
    1483             :         OO_DEBUG_POP_PROGRESS();
    1484             :         
    1485             :         // actual entities next...
    1486             :         
    1487             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"setUpSpace - planet");
    1488             :         a_planet=[self setUpPlanet]; // resets RNG when called
    1489             :         double planet_radius = [a_planet radius];
    1490             :         OO_DEBUG_POP_PROGRESS();
    1491             :         
    1492             :         // set the system seed for random number generation
    1493             :         seed_for_planet_description(systemSeed);
    1494             :         
    1495             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"setUpSpace - sun");
    1496             :         /*- space sun -*/
    1497             :         double          sun_radius;
    1498             :         double          sun_distance;
    1499             :         double          sunDistanceModifier;
    1500             :         double          safeDistance;
    1501             :         HPVector                sunPos;
    1502             :         
    1503             :         sunDistanceModifier = [systeminfo oo_nonNegativeDoubleForKey:@"sun_distance_modifier" defaultValue:0.0];
    1504             :         if (sunDistanceModifier < 6.0) // <6 isn't valid
    1505             :         {
    1506             :                 sun_distance = [systeminfo oo_nonNegativeDoubleForKey:@"sun_distance" defaultValue:(planet_radius*20)];
    1507             :                 // note, old property was _modifier, new property is _multiplier
    1508             :                 sun_distance *= [systeminfo oo_nonNegativeDoubleForKey:@"sun_distance_multiplier" defaultValue:1];
    1509             :         } 
    1510             :         else
    1511             :         {
    1512             :                 sun_distance = planet_radius * sunDistanceModifier;
    1513             :         }
    1514             : 
    1515             :         sun_radius = [systeminfo oo_nonNegativeDoubleForKey:@"sun_radius" defaultValue:2.5 * planet_radius];
    1516             :         // clamp the sun radius
    1517             :         if ((sun_radius < 1000.0) || (sun_radius > sun_distance / 2  && !sunGoneNova))
    1518             :         {
    1519             :                 OOLogWARN(@"universe.setup.badSun",@"Sun radius of %f is not valid for this system",sun_radius);
    1520             :                 sun_radius = sun_radius < 1000.0 ? 1000.0 : (sun_distance / 2);
    1521             :         }
    1522             : #ifdef OO_DUMP_PLANETINFO
    1523             :         OOLog(@"planetinfo.record",@"sun_radius = %f",sun_radius);
    1524             : #endif
    1525             :         safeDistance=36 * sun_radius * sun_radius; // 6 times the sun radius
    1526             :         
    1527             :         // here we need to check if the sun collides with (or is too close to) the witchpoint
    1528             :         // otherwise at (for example) Maregais in Galaxy 1 we go BANG!
    1529             :         HPVector sun_dir = [systeminfo oo_hpvectorForKey:@"sun_vector"];
    1530             :         sun_distance /= 2.0;
    1531             :         do
    1532             :         {
    1533             :                 sun_distance *= 2.0;
    1534             :                 sunPos = HPvector_subtract([a_planet position],
    1535             :                                                           HPvector_multiply_scalar(sun_dir,sun_distance));
    1536             :                 
    1537             :                 // if not in the safe distance, multiply by two and try again
    1538             :         } 
    1539             :         while (HPmagnitude2(sunPos) < safeDistance);
    1540             : 
    1541             :         // set planetary axial tilt to 0 degrees
    1542             :         // TODO: allow this to vary
    1543             :         [a_planet setOrientation:quaternion_rotation_betweenHP(sun_dir,make_HPvector(1.0,0.0,0.0))];
    1544             : 
    1545             : #ifdef OO_DUMP_PLANETINFO
    1546             :         OOLog(@"planetinfo.record",@"sun_vector = %.3f %.3f %.3f",vf.x,vf.y,vf.z);
    1547             :         OOLog(@"planetinfo.record",@"sun_distance = %.0f",sun_distance);
    1548             : #endif
    1549             :         
    1550             : 
    1551             :         
    1552             :         NSMutableDictionary *sun_dict = [NSMutableDictionary dictionaryWithCapacity:5];
    1553             :         [sun_dict setObject:[NSNumber numberWithDouble:sun_radius] forKey:@"sun_radius"];
    1554             :         dict_object=[systeminfo objectForKey: @"corona_shimmer"];
    1555             :         if (dict_object!=nil) [sun_dict setObject:dict_object forKey:@"corona_shimmer"];
    1556             :         dict_object=[systeminfo objectForKey: @"corona_hues"];
    1557             :         if (dict_object!=nil)
    1558             :         {
    1559             :                 [sun_dict setObject:dict_object forKey:@"corona_hues"];
    1560             :         }
    1561             :         else
    1562             :         {
    1563             :                 [sun_dict setObject:[NSNumber numberWithFloat:defaultSunHues] forKey:@"corona_hues"];
    1564             :         }
    1565             :         dict_object=[systeminfo objectForKey: @"corona_flare"];
    1566             :         if (dict_object!=nil) 
    1567             :         {
    1568             :                 [sun_dict setObject:dict_object forKey:@"corona_flare"];
    1569             :         }
    1570             :         else
    1571             :         {
    1572             :                 [sun_dict setObject:[NSNumber numberWithFloat:defaultSunFlare] forKey:@"corona_flare"];
    1573             :         }
    1574             :         dict_object=[systeminfo objectForKey:KEY_SUNNAME];
    1575             :         if (dict_object!=nil) 
    1576             :         {
    1577             :                 [sun_dict setObject:dict_object forKey:KEY_SUNNAME];
    1578             :         }
    1579             : #ifdef OO_DUMP_PLANETINFO
    1580             :         OOLog(@"planetinfo.record",@"corona_flare = %f",[sun_dict oo_floatForKey:@"corona_flare"]);
    1581             :         OOLog(@"planetinfo.record",@"corona_hues = %f",[sun_dict oo_floatForKey:@"corona_hues"]);
    1582             :         OOLog(@"planetinfo.record",@"sun_color = %@",[bgcolor descriptionComponents]);
    1583             : #endif
    1584             :         a_sun = [[OOSunEntity alloc] initSunWithColor:bgcolor andDictionary:sun_dict];  // alloc retains!
    1585             :         
    1586             :         [a_sun setStatus:STATUS_ACTIVE];
    1587             :         [a_sun setPosition:sunPos]; // sets also light origin
    1588             :         [a_sun setEnergy:1000000.0];
    1589             :         [self addEntity:a_sun];
    1590             :         
    1591             :         if (sunGoneNova)
    1592             :         {
    1593             :                 [a_sun setRadius: sun_radius andCorona:0.3];
    1594             :                 [a_sun setThrowSparks:YES];
    1595             :                 [a_sun setVelocity: kZeroVector];
    1596             :         }
    1597             :         
    1598             :         // set the lighting only after we know which sun we have.
    1599             :         [self setLighting];
    1600             :         OO_DEBUG_POP_PROGRESS();
    1601             :         
    1602             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"setUpSpace - main station");
    1603             :         /*- space station -*/
    1604             :         stationPos = [a_planet position];
    1605             : 
    1606             :         vf = [systeminfo oo_vectorForKey:@"station_vector"];
    1607             : #ifdef OO_DUMP_PLANETINFO
    1608             :         OOLog(@"planetinfo.record",@"station_vector = %.3f %.3f %.3f",vf.x,vf.y,vf.z);
    1609             : #endif
    1610             :         stationPos = HPvector_subtract(stationPos, vectorToHPVector(vector_multiply_scalar(vf, 2.0 * planet_radius)));
    1611             :         
    1612             : 
    1613             :         //// possibly systeminfo has an override for the station
    1614             :         stationDesc = [systeminfo oo_stringForKey:@"station" defaultValue:@"coriolis"];
    1615             : #ifdef OO_DUMP_PLANETINFO
    1616             :         OOLog(@"planetinfo.record",@"station = %@",stationDesc);
    1617             : #endif
    1618             :         
    1619             :         a_station = (StationEntity *)[self newShipWithRole:stationDesc];                        // retain count = 1
    1620             :         
    1621             :         /*      Sanity check: ensure that only stations are generated here. This is an
    1622             :                 attempt to fix exceptions of the form:
    1623             :                         NSInvalidArgumentException : *** -[ShipEntity setPlanet:]: selector
    1624             :                         not recognized [self = 0x19b7e000] *****
    1625             :                 which I presume to be originating here since all other uses of
    1626             :                 setPlanet: are guarded by isStation checks. This error could happen if
    1627             :                 a ship that is not a station has a station role, or equivalently if an
    1628             :                 OXP sets a system's station role to a role used by non-stations.
    1629             :                 -- Ahruman 20080303
    1630             :         */
    1631             :         if (![a_station isStation] || ![a_station validForAddToUniverse])
    1632             :         {
    1633             :                 if (a_station == nil)
    1634             :                 {
    1635             :                         // Should have had a more specific error already, just specify context
    1636             :                         OOLog(@"universe.setup.badStation", @"Failed to set up a ship for role \"%@\" as system station, trying again with \"%@\".", stationDesc, defaultStationDesc);
    1637             :                 }
    1638             :                 else
    1639             :                 {
    1640             :                         OOLog(@"universe.setup.badStation", @"***** ERROR: Attempt to use non-station ship of type \"%@\" for role \"%@\" as system station, trying again with \"%@\".", [a_station name], stationDesc, defaultStationDesc);
    1641             :                 }
    1642             :                 [a_station release];
    1643             :                 stationDesc = defaultStationDesc;
    1644             :                 a_station = (StationEntity *)[self newShipWithRole:stationDesc];                 // retain count = 1
    1645             :                 
    1646             :                 if (![a_station isStation] || ![a_station validForAddToUniverse])
    1647             :                 {
    1648             :                         if (a_station == nil)
    1649             :                         {
    1650             :                                 OOLog(@"universe.setup.badStation", @"On retry, failed to set up a ship for role \"%@\" as system station. Trying to fall back to built-in Coriolis station.", stationDesc);
    1651             :                         }
    1652             :                         else
    1653             :                         {
    1654             :                                 OOLog(@"universe.setup.badStation", @"***** ERROR: On retry, rolled non-station ship of type \"%@\" for role \"%@\". Non-station ships should not have this role! Trying to fall back to built-in Coriolis station.", [a_station name], stationDesc);
    1655             :                         }
    1656             :                         [a_station release];
    1657             :                         
    1658             :                         a_station = (StationEntity *)[self newShipWithName:@"coriolis-station"];
    1659             :                         if (![a_station isStation] || ![a_station validForAddToUniverse])
    1660             :                         {
    1661             :                                 OOLog(@"universe.setup.badStation", @"%@", @"Could not create built-in Coriolis station! Generating a stationless system.");
    1662             :                                 DESTROY(a_station);
    1663             :                         }
    1664             :                 }
    1665             :         }
    1666             :         
    1667             :         if (a_station != nil)
    1668             :         {
    1669             :                 [a_station setOrientation:quaternion_rotation_between(vf,make_vector(0.0,0.0,1.0))];
    1670             :                 [a_station setPosition: stationPos];
    1671             :                 [a_station setPitch: 0.0];
    1672             :                 [a_station setScanClass: CLASS_STATION];
    1673             :                 //[a_station setPlanet:[self planet]];  // done inside addEntity.
    1674             :                 [a_station setEquivalentTechLevel:techlevel];
    1675             :                 [self addEntity:a_station];             // STATUS_IN_FLIGHT, AI state GLOBAL
    1676             :                 [a_station setStatus:STATUS_ACTIVE];    // For backward compatibility. Might not be needed.
    1677             :                 [a_station setAllowsFastDocking:true];  // Main stations always allow fast docking.
    1678             :                 [a_station setAllegiance:@"galcop"]; // Main station is galcop controlled
    1679             :         }
    1680             :         OO_DEBUG_POP_PROGRESS();
    1681             :         
    1682             :         cachedSun = a_sun;
    1683             :         cachedPlanet = a_planet;
    1684             :         cachedStation = a_station;
    1685             :         closeSystems = nil;
    1686             :         OO_DEBUG_POP_PROGRESS();
    1687             :         
    1688             :         
    1689             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"setUpSpace - populate from wormholes");
    1690             :         [self populateSpaceFromActiveWormholes];
    1691             :         OO_DEBUG_POP_PROGRESS();
    1692             : 
    1693             :         [a_sun release];
    1694             :         [a_station release];
    1695             : }
    1696             : 
    1697             : 
    1698             : - (void) populateNormalSpace
    1699             : {       
    1700             :         NSDictionary            *systeminfo = [systemManager getPropertiesForCurrentSystem];
    1701             : 
    1702             :         BOOL sunGoneNova = [systeminfo oo_boolForKey:@"sun_gone_nova"];
    1703             :         // check for nova
    1704             :         if (sunGoneNova)
    1705             :         {
    1706             :                 OO_DEBUG_PUSH_PROGRESS(@"%@", @"setUpSpace - post-nova");
    1707             :                 
    1708             :                 HPVector v0 = make_HPvector(0,0,34567.89);
    1709             :                 double min_safe_dist2 = 6000000.0 * 6000000.0;
    1710             :                 HPVector sunPos = [cachedSun position];
    1711             :                 while (HPmagnitude2(cachedSun->position) < min_safe_dist2)        // back off the planetary bodies
    1712             :                 {
    1713             :                         v0.z *= 2.0;
    1714             :                         
    1715             :                         sunPos = HPvector_add(sunPos, v0);
    1716             :                         [cachedSun setPosition:sunPos];  // also sets light origin
    1717             :                         
    1718             :                 }
    1719             :                 
    1720             :                 [self removeEntity:cachedPlanet];       // and Poof! it's gone
    1721             :                 cachedPlanet = nil;     
    1722             :                 [self removeEntity:cachedStation];      // also remove main station
    1723             :                 cachedStation = nil;    
    1724             :         }
    1725             : 
    1726             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"setUpSpace - populate from hyperpoint");
    1727             : //      [self populateSpaceFromHyperPoint:witchPos toPlanetPosition: a_planet->position andSunPosition: a_sun->position];
    1728             :         [self clearSystemPopulator];
    1729             : 
    1730             :         if ([PLAYER status] != STATUS_START_GAME)
    1731             :         {
    1732             :                 NSString *populator = [systeminfo oo_stringForKey:@"populator" defaultValue:(sunGoneNova)?@"novaSystemWillPopulate":@"systemWillPopulate"];
    1733             :                 [system_repopulator release];
    1734             :                 system_repopulator = [[systeminfo oo_stringForKey:@"repopulator" defaultValue:(sunGoneNova)?@"novaSystemWillRepopulate":@"systemWillRepopulate"] retain];
    1735             : 
    1736             :                 JSContext *context = OOJSAcquireContext();
    1737             :                 [PLAYER doWorldScriptEvent:OOJSIDFromString(populator) inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit];
    1738             :                 OOJSRelinquishContext(context);
    1739             :                 [self populateSystemFromDictionariesWithSun:cachedSun andPlanet:cachedPlanet];
    1740             :         }
    1741             : 
    1742             :         OO_DEBUG_POP_PROGRESS();
    1743             : 
    1744             :         // systeminfo might have a 'script_actions' resource we want to activate now...
    1745             :         NSArray *script_actions = [systeminfo oo_arrayForKey:@"script_actions"];
    1746             :         if (script_actions != nil)
    1747             :         {
    1748             :                 OOStandardsDeprecated([NSString stringWithFormat:@"The script_actions system info key is deprecated for %@.",[self getSystemName:systemID]]);
    1749             :                 if (!OOEnforceStandards()) 
    1750             :                 {
    1751             :                         OO_DEBUG_PUSH_PROGRESS(@"%@", @"setUpSpace - legacy script_actions");
    1752             :                         [PLAYER runUnsanitizedScriptActions:script_actions
    1753             :                                                           allowingAIMethods:NO
    1754             :                                                                 withContextName:@"<system script_actions>"
    1755             :                                                                           forTarget:nil];
    1756             :                         OO_DEBUG_POP_PROGRESS();
    1757             :                 }
    1758             :         }
    1759             : 
    1760             :         next_repopulation = randf() * SYSTEM_REPOPULATION_INTERVAL;
    1761             : }
    1762             : 
    1763             : 
    1764             : - (void) clearSystemPopulator
    1765             : {
    1766             :         [populatorSettings release];
    1767             :         populatorSettings = [[NSMutableDictionary alloc] initWithCapacity:128];
    1768             : }
    1769             : 
    1770             : 
    1771             : - (NSDictionary *) getPopulatorSettings
    1772             : {
    1773             :         return populatorSettings;
    1774             : }
    1775             : 
    1776             : 
    1777             : - (void) setPopulatorSetting:(NSString *)key to:(NSDictionary *)setting
    1778             : {
    1779             :         if (setting == nil)
    1780             :         {
    1781             :                 [populatorSettings removeObjectForKey:key];
    1782             :         } 
    1783             :         else
    1784             :         {
    1785             :                 [populatorSettings setObject:setting forKey:key];
    1786             :         }
    1787             : }
    1788             : 
    1789             : 
    1790             : - (BOOL) deterministicPopulation
    1791             : {
    1792             :         return deterministic_population;
    1793             : }
    1794             : 
    1795             : 
    1796             : - (void) populateSystemFromDictionariesWithSun:(OOSunEntity *)sun andPlanet:(OOPlanetEntity *)planet
    1797             : {
    1798             :         Random_Seed systemSeed = [systemManager getRandomSeedForCurrentSystem];
    1799             :         NSArray *blocks = [populatorSettings allValues];
    1800             :         NSEnumerator *enumerator = [[blocks sortedArrayUsingFunction:populatorPrioritySort context:nil] objectEnumerator];
    1801             :         NSDictionary *populator = nil;
    1802             :         HPVector location = kZeroHPVector;
    1803             :         uint32_t i, locationSeed, groupCount, rndvalue;
    1804             :         RANROTSeed rndcache = RANROTGetFullSeed();
    1805             :         RANROTSeed rndlocal = RANROTGetFullSeed();
    1806             :         NSString *locationCode = nil;
    1807             :         OOJSPopulatorDefinition *pdef = nil;
    1808             :         while ((populator = [enumerator nextObject]))
    1809             :         {
    1810             :                 deterministic_population = [populator oo_boolForKey:@"deterministic" defaultValue:NO];
    1811             :                 if (EXPECT_NOT(sun == nil || planet == nil))
    1812             :                 {
    1813             :                         // needs to be a non-nova system, and not interstellar space
    1814             :                         deterministic_population = NO;
    1815             :                 }
    1816             : 
    1817             :                 locationSeed = [populator oo_unsignedIntForKey:@"locationSeed" defaultValue:0];
    1818             :                 groupCount = [populator oo_unsignedIntForKey:@"groupCount" defaultValue:1];
    1819             :                 
    1820             :                 for (i = 0; i < groupCount; i++)
    1821             :                 {
    1822             :                         locationCode = [populator oo_stringForKey:@"location" defaultValue:@"COORDINATES"];
    1823             :                         if ([locationCode isEqualToString:@"COORDINATES"])
    1824             :                         {
    1825             :                                 location = [populator oo_hpvectorForKey:@"coordinates" defaultValue:kZeroHPVector];
    1826             :                         }
    1827             :                         else
    1828             :                         {
    1829             :                                 if (locationSeed != 0)
    1830             :                                 {
    1831             :                                         rndcache = RANROTGetFullSeed();
    1832             :                                         // different place for each system
    1833             :                                         rndlocal = RanrotSeedFromRandomSeed(systemSeed);
    1834             :                                         rndvalue = RanrotWithSeed(&rndlocal);
    1835             :                                         // ...for location seed
    1836             :                                         rndlocal = MakeRanrotSeed(rndvalue+locationSeed);
    1837             :                                         rndvalue = RanrotWithSeed(&rndlocal);
    1838             :                                         // ...for iteration (63647 is nothing special, just a largish prime)
    1839             :                                         RANROTSetFullSeed(MakeRanrotSeed(rndvalue+(i*63647)));
    1840             :                                 }
    1841             :                                 else
    1842             :                                 {
    1843             :                                         // not fixed coordinates and not seeded RNG; can't
    1844             :                                         // be deterministic
    1845             :                                         deterministic_population = NO;
    1846             :                                 }
    1847             :                                 if (sun == nil || planet == nil)
    1848             :                                 {
    1849             :                                         // all interstellar space and nova locations equal to WITCHPOINT
    1850             :                                         location = [self locationByCode:@"WITCHPOINT" withSun:nil andPlanet:nil];
    1851             :                                 }
    1852             :                                 else
    1853             :                                 {
    1854             :                                         location = [self locationByCode:locationCode withSun:sun andPlanet:planet];
    1855             :                                 }
    1856             :                                 if(locationSeed != 0)
    1857             :                                 {
    1858             :                                         // go back to the main random sequence
    1859             :                                         RANROTSetFullSeed(rndcache);
    1860             :                                 }                       
    1861             :                         }
    1862             :                         // location now contains a Vector coordinate, one way or another
    1863             :                         pdef = [populator objectForKey:@"callbackObj"];
    1864             :                         [pdef runCallback:location];
    1865             :                 }
    1866             :         }
    1867             :         // nothing is deterministic once the populator is done
    1868             :         deterministic_population = NO;
    1869             : }
    1870             : 
    1871             : 
    1872             : /* Generates a position within one of the named regions:
    1873             :  *
    1874             :  * WITCHPOINT: within scanner of witchpoint
    1875             :  * LANE_*: within two scanner of lane, not too near each end
    1876             :  * STATION_AEGIS: within two scanner of main station, not in planet
    1877             :  * *_ORBIT_*: around the object, in a shell relative to object radius
    1878             :  * TRIANGLE: somewhere in the triangle defined by W, P, S
    1879             :  * INNER_SYSTEM: closer to the sun than the planet is
    1880             :  * OUTER_SYSTEM: further from the sun than the planet is
    1881             :  * *_OFFPLANE: like the above, but not on the orbital plane
    1882             :  *
    1883             :  * Can be called with nil sun or planet, but if so the calling function
    1884             :  * must make sure the location code is WITCHPOINT.
    1885             :  */
    1886             : - (HPVector) locationByCode:(NSString *)code withSun:(OOSunEntity *)sun andPlanet:(OOPlanetEntity *)planet
    1887             : {
    1888             :         HPVector result = kZeroHPVector;
    1889             :         if ([code isEqualToString:@"WITCHPOINT"] || sun == nil || planet == nil || [sun goneNova])
    1890             :         {
    1891             :                 result = OOHPVectorRandomSpatial(SCANNER_MAX_RANGE);
    1892             :         }
    1893             :         // past this point, can assume non-nil sun, planet
    1894             :         else
    1895             :         { 
    1896             :                 if ([code isEqualToString:@"LANE_WPS"])
    1897             :                 {       
    1898             :                         // pick position on one of the lanes, weighted by lane length
    1899             :                         double l1 = HPmagnitude([planet position]);
    1900             :                         double l2 = HPmagnitude(HPvector_subtract([sun position],[planet position]));
    1901             :                         double l3 = HPmagnitude([sun position]);
    1902             :                         double total = l1+l2+l3;
    1903             :                         float choice = randf();
    1904             :                         if (choice < l1/total)
    1905             :                         {
    1906             :                                 return [self locationByCode:@"LANE_WP" withSun:sun andPlanet:planet];
    1907             :                         }
    1908             :                         else if (choice < (l1+l2)/total)
    1909             :                         {
    1910             :                                 return [self locationByCode:@"LANE_PS" withSun:sun andPlanet:planet];
    1911             :                         }
    1912             :                         else
    1913             :                         {
    1914             :                                 return [self locationByCode:@"LANE_WS" withSun:sun andPlanet:planet];
    1915             :                         }
    1916             :                 }
    1917             :                 else if ([code isEqualToString:@"LANE_WP"])
    1918             :                 {
    1919             :                         result = OORandomPositionInCylinder(kZeroHPVector,SCANNER_MAX_RANGE,[planet position],[planet radius]*3,LANE_WIDTH);
    1920             :                 }
    1921             :                 else if ([code isEqualToString:@"LANE_WS"])
    1922             :                 {
    1923             :                         result = OORandomPositionInCylinder(kZeroHPVector,SCANNER_MAX_RANGE,[sun position],[sun radius]*3,LANE_WIDTH);
    1924             :                 }
    1925             :                 else if ([code isEqualToString:@"LANE_PS"])
    1926             :                 {
    1927             :                         result = OORandomPositionInCylinder([planet position],[planet radius]*3,[sun position],[sun radius]*3,LANE_WIDTH);
    1928             :                 }
    1929             :                 else if ([code isEqualToString:@"STATION_AEGIS"])
    1930             :                 {
    1931             :                         do 
    1932             :                         {
    1933             :                                 result = OORandomPositionInShell([[self station] position],[[self station] collisionRadius]*1.2,SCANNER_MAX_RANGE*2.0);
    1934             :                         } while(HPdistance2(result,[planet position])<[planet radius]*[planet radius]*1.5);
    1935             :                         // loop to make sure not generated too close to the planet's surface
    1936             :                 }
    1937             :                 else if ([code isEqualToString:@"PLANET_ORBIT_LOW"])
    1938             :                 {
    1939             :                         result = OORandomPositionInShell([planet position],[planet radius]*1.1,[planet radius]*2.0);
    1940             :                 }
    1941             :                 else if ([code isEqualToString:@"PLANET_ORBIT"])
    1942             :                 {
    1943             :                         result = OORandomPositionInShell([planet position],[planet radius]*2.0,[planet radius]*4.0);
    1944             :                 }
    1945             :                 else if ([code isEqualToString:@"PLANET_ORBIT_HIGH"])
    1946             :                 {
    1947             :                         result = OORandomPositionInShell([planet position],[planet radius]*4.0,[planet radius]*8.0);
    1948             :                 }
    1949             :                 else if ([code isEqualToString:@"STAR_ORBIT_LOW"])
    1950             :                 {
    1951             :                         result = OORandomPositionInShell([sun position],[sun radius]*1.1,[sun radius]*2.0);
    1952             :                 }
    1953             :                 else if ([code isEqualToString:@"STAR_ORBIT"])
    1954             :                 {
    1955             :                         result = OORandomPositionInShell([sun position],[sun radius]*2.0,[sun radius]*4.0);
    1956             :                 }
    1957             :                 else if ([code isEqualToString:@"STAR_ORBIT_HIGH"])
    1958             :                 {
    1959             :                         result = OORandomPositionInShell([sun position],[sun radius]*4.0,[sun radius]*8.0);
    1960             :                 }
    1961             :                 else if ([code isEqualToString:@"TRIANGLE"])
    1962             :                 {
    1963             :                         do {
    1964             :                                 // pick random point in triangle by algorithm at
    1965             :                                 // http://adamswaab.wordpress.com/2009/12/11/random-point-in-a-triangle-barycentric-coordinates/
    1966             :                                 // simplified by using the origin as A
    1967             :                                 OOScalar r = randf();
    1968             :                                 OOScalar s = randf();
    1969             :                                 if (r+s >= 1)
    1970             :                                 {
    1971             :                                         r = 1-r;
    1972             :                                         s = 1-s;
    1973             :                                 }
    1974             :                                 result = HPvector_add(HPvector_multiply_scalar([planet position],r),HPvector_multiply_scalar([sun position],s));
    1975             :                         }
    1976             :                         // make sure at least 3 radii from vertices
    1977             :                         while(HPdistance2(result,[sun position]) < [sun radius]*[sun radius]*9.0 || HPdistance2(result,[planet position]) < [planet radius]*[planet radius]*9.0 || HPmagnitude2(result) < SCANNER_MAX_RANGE2 * 9.0);
    1978             :                 }
    1979             :                 else if ([code isEqualToString:@"INNER_SYSTEM"])
    1980             :                 {
    1981             :                         do {
    1982             :                                 result = OORandomPositionInShell([sun position],[sun radius]*3.0,HPdistance([sun position],[planet position]));
    1983             :                                 result = OOProjectHPVectorToPlane(result,kZeroHPVector,HPcross_product([sun position],[planet position]));
    1984             :                                 result = HPvector_add(result,OOHPVectorRandomSpatial([planet radius]));
    1985             :                                 // projection to plane could bring back too close to sun
    1986             :                         } while (HPdistance2(result,[sun position]) < [sun radius]*[sun radius]*9.0);
    1987             :                 }
    1988             :                 else if ([code isEqualToString:@"INNER_SYSTEM_OFFPLANE"])
    1989             :                 {
    1990             :                         result = OORandomPositionInShell([sun position],[sun radius]*3.0,HPdistance([sun position],[planet position]));
    1991             :                 }
    1992             :                 else if ([code isEqualToString:@"OUTER_SYSTEM"])
    1993             :                 {
    1994             :                         result = OORandomPositionInShell([sun position],HPdistance([sun position],[planet position]),HPdistance([sun position],[planet position])*10.0); // no more than 10 AU out
    1995             :                         result = OOProjectHPVectorToPlane(result,kZeroHPVector,HPcross_product([sun position],[planet position]));
    1996             :                         result = HPvector_add(result,OOHPVectorRandomSpatial(0.01*HPdistance(result,[sun position]))); // within 1% of plane
    1997             :                 }
    1998             :                 else if ([code isEqualToString:@"OUTER_SYSTEM_OFFPLANE"])
    1999             :                 {
    2000             :                         result = OORandomPositionInShell([sun position],HPdistance([sun position],[planet position]),HPdistance([sun position],[planet position])*10.0); // no more than 10 AU out
    2001             :                 }
    2002             :                 else
    2003             :                 {
    2004             :                         OOLog(kOOLogUniversePopulateError,@"Named populator region %@ is not implemented, falling back to WITCHPOINT",code); 
    2005             :                         result = OOHPVectorRandomSpatial(SCANNER_MAX_RANGE);
    2006             :                 }
    2007             :         }
    2008             :         return result;
    2009             : }
    2010             : 
    2011             : 
    2012             : - (void) setAmbientLightLevel:(float)newValue
    2013             : {
    2014             :         NSAssert(UNIVERSE != nil, @"Attempt to set ambient light level with a non yet existent universe.");
    2015             :         
    2016             :         ambientLightLevel = OOClamp_0_max_f(newValue, 10.0f);
    2017             :         return;
    2018             : }
    2019             : 
    2020             : 
    2021             : - (float) ambientLightLevel
    2022             : {
    2023             :         return ambientLightLevel;
    2024             : }
    2025             : 
    2026             : 
    2027             : - (void) setLighting
    2028             : {
    2029             :         /*
    2030             :         
    2031             :         GL_LIGHT1 is the sun and is active while a sun exists in space
    2032             :         where there is no sun (witch/interstellar space) this is placed at the origin
    2033             :         
    2034             :         Shaders: this light is also used inside the station and needs to have its position reset
    2035             :         relative to the player whenever demo ships or background scenes are to be shown -- 20100111
    2036             :         
    2037             :         
    2038             :         GL_LIGHT0 is the light for inside the station and needs to have its position reset
    2039             :         relative to the player whenever demo ships or background scenes are to be shown
    2040             :         
    2041             :         Shaders: this light is not used.  -- 20100111
    2042             :         
    2043             :         */
    2044             :         
    2045             :         OOSunEntity             *the_sun = [self sun];
    2046             :         SkyEntity               *the_sky = nil;
    2047             :         GLfloat                 sun_pos[] = {0.0, 0.0, 0.0, 1.0};       // equivalent to kZeroVector - for interstellar space.
    2048             :         GLfloat                 sun_ambient[] = {0.0, 0.0, 0.0, 1.0};   // overridden later in code
    2049             :         int i;
    2050             :         
    2051             :         for (i = n_entities - 1; i > 0; i--)
    2052             :                 if ((sortedEntities[i]) && ([sortedEntities[i] isKindOfClass:[SkyEntity class]]))
    2053             :                         the_sky = (SkyEntity*)sortedEntities[i];
    2054             :         
    2055             :         if (the_sun)
    2056             :         {
    2057             :                 [the_sun getDiffuseComponents:sun_diffuse];
    2058             :                 [the_sun getSpecularComponents:sun_specular];
    2059             :                 OOGL(glLightfv(GL_LIGHT1, GL_AMBIENT, sun_ambient));
    2060             :                 OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_diffuse));
    2061             :                 OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_specular));
    2062             :                 sun_pos[0] = the_sun->position.x;
    2063             :                 sun_pos[1] = the_sun->position.y;
    2064             :                 sun_pos[2] = the_sun->position.z;
    2065             :         }
    2066             :         else
    2067             :         {
    2068             :                 // witchspace
    2069             :                 stars_ambient[0] = 0.05;        stars_ambient[1] = 0.20;        stars_ambient[2] = 0.05;        stars_ambient[3] = 1.0;
    2070             :                 sun_diffuse[0] = 0.85;  sun_diffuse[1] = 1.0;   sun_diffuse[2] = 0.85;  sun_diffuse[3] = 1.0;
    2071             :                 sun_specular[0] = 0.95; sun_specular[1] = 1.0;  sun_specular[2] = 0.95; sun_specular[3] = 1.0;
    2072             :                 OOGL(glLightfv(GL_LIGHT1, GL_AMBIENT, sun_ambient));
    2073             :                 OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_diffuse));
    2074             :                 OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_specular));
    2075             :         }
    2076             :         
    2077             :         OOGL(glLightfv(GL_LIGHT1, GL_POSITION, sun_pos));
    2078             :         
    2079             :         if (the_sky)
    2080             :         {
    2081             :                 // ambient lighting!
    2082             :                 GLfloat r,g,b,a;
    2083             :                 [[the_sky skyColor] getRed:&r green:&g blue:&b alpha:&a];
    2084             :                 r = r * (1.0 - SUN_AMBIENT_INFLUENCE) + sun_diffuse[0] * SUN_AMBIENT_INFLUENCE;
    2085             :                 g = g * (1.0 - SUN_AMBIENT_INFLUENCE) + sun_diffuse[1] * SUN_AMBIENT_INFLUENCE;
    2086             :                 b = b * (1.0 - SUN_AMBIENT_INFLUENCE) + sun_diffuse[2] * SUN_AMBIENT_INFLUENCE;
    2087             :                 GLfloat ambient_level = [self ambientLightLevel];
    2088             :                 stars_ambient[0] = ambient_level * SKY_AMBIENT_ADJUSTMENT * (1.0 + r) * (1.0 + r);
    2089             :                 stars_ambient[1] = ambient_level * SKY_AMBIENT_ADJUSTMENT * (1.0 + g) * (1.0 + g);
    2090             :                 stars_ambient[2] = ambient_level * SKY_AMBIENT_ADJUSTMENT * (1.0 + b) * (1.0 + b);
    2091             :                 stars_ambient[3] = 1.0;
    2092             :         }
    2093             :         
    2094             :         // light for demo ships display..
    2095             :         OOGL(glLightfv(GL_LIGHT0, GL_AMBIENT, docked_light_ambient));
    2096             :         OOGL(glLightfv(GL_LIGHT0, GL_DIFFUSE, docked_light_diffuse));
    2097             :         OOGL(glLightfv(GL_LIGHT0, GL_SPECULAR, docked_light_specular));
    2098             :         OOGL(glLightfv(GL_LIGHT0, GL_POSITION, demo_light_position));   
    2099             :         OOGL(glLightModelfv(GL_LIGHT_MODEL_AMBIENT, stars_ambient));
    2100             : }
    2101             : 
    2102             : 
    2103             : // Call this method to avoid lighting glich after windowed/fullscreen transition on macs.
    2104             : - (void) forceLightSwitch
    2105             : {
    2106             :         demo_light_on = !demo_light_on;
    2107             : }
    2108             : 
    2109             : 
    2110             : - (void) setMainLightPosition: (Vector) sunPos
    2111             : {
    2112             :         main_light_position[0] = sunPos.x;
    2113             :         main_light_position[1] = sunPos.y;
    2114             :         main_light_position[2] = sunPos.z;
    2115             :         main_light_position[3] = 1.0;
    2116             : }
    2117             : 
    2118             : 
    2119           0 : - (ShipEntity *) addShipWithRole:(NSString *)desc launchPos:(HPVector)launchPos rfactor:(GLfloat)rfactor
    2120             : {
    2121             :         if (rfactor != 0.0)
    2122             :         {
    2123             :                 // Calculate the position as soon as possible, to minimise 'lollipop flash'
    2124             :                 launchPos.x += 2 * rfactor * (randf() - 0.5);
    2125             :                 launchPos.y += 2 * rfactor * (randf() - 0.5);
    2126             :                 launchPos.z += 2 * rfactor * (randf() - 0.5);
    2127             :         }
    2128             :         
    2129             :         ShipEntity  *ship = [self newShipWithRole:desc];   // retain count = 1
    2130             :         
    2131             :         if (ship)
    2132             :         {
    2133             :                 [ship setPosition:launchPos];   // minimise 'lollipop flash'
    2134             :                 
    2135             :                 // Deal with scripted cargopods and ensure they are filled with something.
    2136             :                 if ([ship hasRole:@"cargopod"])  [self fillCargopodWithRandomCargo:ship];
    2137             :                 
    2138             :                 // Ensure piloted ships have pilots.
    2139             :                 if (![ship crew] && ![ship isUnpiloted])
    2140             :                         [ship setCrew:[NSArray arrayWithObject:
    2141             :                                                    [OOCharacter randomCharacterWithRole:desc
    2142             :                                                                                           andOriginalSystem:Ranrot() & 255]]];
    2143             :                 
    2144             :                 if ([ship scanClass] == CLASS_NOT_SET)
    2145             :                 {
    2146             :                         [ship setScanClass: CLASS_NEUTRAL];
    2147             :                 }
    2148             :                 [self addEntity:ship];  // STATUS_IN_FLIGHT, AI state GLOBAL
    2149             :                 [ship release];
    2150             :                 return ship;
    2151             :         }
    2152             :         return nil;
    2153             : }
    2154             : 
    2155             : 
    2156             : - (void) addShipWithRole:(NSString *) desc nearRouteOneAt:(double) route_fraction
    2157             : {
    2158             :         // adds a ship within scanner range of a point on route 1
    2159             :         
    2160             :         Entity  *theStation = [self station];
    2161             :         if (!theStation)
    2162             :         {
    2163             :                 return;
    2164             :         }
    2165             :         
    2166             :         HPVector        launchPos = OOHPVectorInterpolate([self getWitchspaceExitPosition], [theStation position], route_fraction);
    2167             :         
    2168             :         [self addShipWithRole:desc launchPos:launchPos rfactor:SCANNER_MAX_RANGE];
    2169             : }
    2170             : 
    2171             : 
    2172             : - (HPVector) coordinatesForPosition:(HPVector) pos withCoordinateSystem:(NSString *) system returningScalar:(GLfloat*) my_scalar
    2173             : {
    2174             :         /*      the point is described using a system selected by a string
    2175             :                 consisting of a three letter code.
    2176             :                 
    2177             :                 The first letter indicates the feature that is the origin of the coordinate system.
    2178             :                         w => witchpoint
    2179             :                         s => sun
    2180             :                         p => planet
    2181             :                         
    2182             :                 The next letter indicates the feature on the 'z' axis of the coordinate system.
    2183             :                         w => witchpoint
    2184             :                         s => sun
    2185             :                         p => planet
    2186             :                         
    2187             :                 Then the 'y' axis of the system is normal to the plane formed by the planet, sun and witchpoint.
    2188             :                 And the 'x' axis of the system is normal to the y and z axes.
    2189             :                 So:
    2190             :                         ps:             z axis = (planet -> sun)             y axis = normal to (planet - sun - witchpoint)  x axis = normal to y and z axes
    2191             :                         pw:             z axis = (planet -> witchpoint)      y axis = normal to (planet - witchpoint - sun)  x axis = normal to y and z axes
    2192             :                         sp:             z axis = (sun -> planet)             y axis = normal to (sun - planet - witchpoint)  x axis = normal to y and z axes
    2193             :                         sw:             z axis = (sun -> witchpoint) y axis = normal to (sun - witchpoint - planet)  x axis = normal to y and z axes
    2194             :                         wp:             z axis = (witchpoint -> planet)      y axis = normal to (witchpoint - planet - sun)  x axis = normal to y and z axes
    2195             :                         ws:             z axis = (witchpoint -> sun) y axis = normal to (witchpoint - sun - planet)  x axis = normal to y and z axes
    2196             :                         
    2197             :                 The third letter denotes the units used:
    2198             :                         m:              meters
    2199             :                         p:              planetary radii
    2200             :                         s:              solar radii
    2201             :                         u:              distance between first two features indicated (eg. spu means that u = distance from sun to the planet)
    2202             :                 
    2203             :                 in interstellar space (== no sun) coordinates are absolute irrespective of the system used.
    2204             :                 
    2205             :                 [1.71] The position code "abs" can also be used for absolute coordinates.
    2206             :                 
    2207             :         */
    2208             :         
    2209             :         NSString* l_sys = [system lowercaseString];
    2210             :         if ([l_sys length] != 3)
    2211             :                 return kZeroHPVector;
    2212             :         OOPlanetEntity* the_planet = [self planet];
    2213             :         OOSunEntity* the_sun = [self sun];
    2214             :         if (the_planet == nil || the_sun == nil || [l_sys isEqualToString:@"abs"])
    2215             :         {
    2216             :                 if (my_scalar)  *my_scalar = 1.0;
    2217             :                 return pos;
    2218             :         }
    2219             :         HPVector  w_pos = [self getWitchspaceExitPosition];     // don't reset PRNG
    2220             :         HPVector  p_pos = the_planet->position;
    2221             :         HPVector  s_pos = the_sun->position;
    2222             :         
    2223             :         const char* c_sys = [l_sys UTF8String];
    2224             :         HPVector p0, p1, p2;
    2225             :         
    2226             :         switch (c_sys[0])
    2227             :         {
    2228             :                 case 'w':
    2229             :                         p0 = w_pos;
    2230             :                         switch (c_sys[1])
    2231             :                         {
    2232             :                                 case 'p':
    2233             :                                         p1 = p_pos;     p2 = s_pos;     break;
    2234             :                                 case 's':
    2235             :                                         p1 = s_pos;     p2 = p_pos;     break;
    2236             :                                 default:
    2237             :                                         return kZeroHPVector;
    2238             :                         }
    2239             :                         break;
    2240             :                 case 'p':               
    2241             :                         p0 = p_pos;
    2242             :                         switch (c_sys[1])
    2243             :                         {
    2244             :                                 case 'w':
    2245             :                                         p1 = w_pos;     p2 = s_pos;     break;
    2246             :                                 case 's':
    2247             :                                         p1 = s_pos;     p2 = w_pos;     break;
    2248             :                                 default:
    2249             :                                         return kZeroHPVector;
    2250             :                         }
    2251             :                         break;
    2252             :                 case 's':
    2253             :                         p0 = s_pos;
    2254             :                         switch (c_sys[1])
    2255             :                         {
    2256             :                                 case 'w':
    2257             :                                         p1 = w_pos;     p2 = p_pos;     break;
    2258             :                                 case 'p':
    2259             :                                         p1 = p_pos;     p2 = w_pos;     break;
    2260             :                                 default:
    2261             :                                         return kZeroHPVector;
    2262             :                         }
    2263             :                         break;
    2264             :                 default:
    2265             :                         return kZeroHPVector;
    2266             :         }
    2267             :         HPVector k = HPvector_normal_or_zbasis(HPvector_subtract(p1, p0));      // 'forward'
    2268             :         HPVector v = HPvector_normal_or_xbasis(HPvector_subtract(p2, p0));      // temporary vector in plane of 'forward' and 'right'
    2269             :         
    2270             :         HPVector j = HPcross_product(k, v);     // 'up'
    2271             :         HPVector i = HPcross_product(j, k);     // 'right'
    2272             :         
    2273             :         GLfloat scale = 1.0;
    2274             :         switch (c_sys[2])
    2275             :         {
    2276             :                 case 'p':
    2277             :                         scale = [the_planet radius];
    2278             :                         break;
    2279             :                         
    2280             :                 case 's':
    2281             :                         scale = [the_sun radius];
    2282             :                         break;
    2283             :                         
    2284             :                 case 'u':
    2285             :                         scale = HPmagnitude(HPvector_subtract(p1, p0));
    2286             :                         break;
    2287             :                         
    2288             :                 case 'm':
    2289             :                         scale = 1.0f;
    2290             :                         break;
    2291             :                         
    2292             :                 default:
    2293             :                         return kZeroHPVector;
    2294             :         }
    2295             :         if (my_scalar)
    2296             :                 *my_scalar = scale;
    2297             :         
    2298             :         // result = p0 + ijk
    2299             :         HPVector result = p0;   // origin
    2300             :         result.x += scale * (pos.x * i.x + pos.y * j.x + pos.z * k.x);
    2301             :         result.y += scale * (pos.x * i.y + pos.y * j.y + pos.z * k.y);
    2302             :         result.z += scale * (pos.x * i.z + pos.y * j.z + pos.z * k.z);
    2303             :         
    2304             :         return result;
    2305             : }
    2306             : 
    2307             : 
    2308             : - (NSString *) expressPosition:(HPVector) pos inCoordinateSystem:(NSString *) system
    2309             : {
    2310             :         HPVector result = [self legacyPositionFrom:pos asCoordinateSystem:system];
    2311             :         return [NSString stringWithFormat:@"%@ %.2f %.2f %.2f", system, result.x, result.y, result.z];
    2312             : }
    2313             : 
    2314             : 
    2315             : - (HPVector) legacyPositionFrom:(HPVector) pos asCoordinateSystem:(NSString *) system
    2316             : {
    2317             :         NSString* l_sys = [system lowercaseString];
    2318             :         if ([l_sys length] != 3)
    2319             :                 return kZeroHPVector;
    2320             :         OOPlanetEntity* the_planet = [self planet];
    2321             :         OOSunEntity* the_sun = [self sun];
    2322             :         if (the_planet == nil || the_sun == nil || [l_sys isEqualToString:@"abs"])
    2323             :         {
    2324             :                 return pos;
    2325             :         }
    2326             :         HPVector  w_pos = [self getWitchspaceExitPosition];     // don't reset PRNG
    2327             :         HPVector  p_pos = the_planet->position;
    2328             :         HPVector  s_pos = the_sun->position;
    2329             :         
    2330             :         const char* c_sys = [l_sys UTF8String];
    2331             :         HPVector p0, p1, p2;
    2332             :         
    2333             :         switch (c_sys[0])
    2334             :         {
    2335             :                 case 'w':
    2336             :                         p0 = w_pos;
    2337             :                         switch (c_sys[1])
    2338             :                         {
    2339             :                                 case 'p':
    2340             :                                         p1 = p_pos;     p2 = s_pos;     break;
    2341             :                                 case 's':
    2342             :                                         p1 = s_pos;     p2 = p_pos;     break;
    2343             :                                 default:
    2344             :                                         return kZeroHPVector;
    2345             :                         }
    2346             :                         break;
    2347             :                 case 'p':               
    2348             :                         p0 = p_pos;
    2349             :                         switch (c_sys[1])
    2350             :                         {
    2351             :                                 case 'w':
    2352             :                                         p1 = w_pos;     p2 = s_pos;     break;
    2353             :                                 case 's':
    2354             :                                         p1 = s_pos;     p2 = w_pos;     break;
    2355             :                                 default:
    2356             :                                         return kZeroHPVector;
    2357             :                         }
    2358             :                         break;
    2359             :                 case 's':
    2360             :                         p0 = s_pos;
    2361             :                         switch (c_sys[1])
    2362             :                         {
    2363             :                                 case 'w':
    2364             :                                         p1 = w_pos;     p2 = p_pos;     break;
    2365             :                                 case 'p':
    2366             :                                         p1 = p_pos;     p2 = w_pos;     break;
    2367             :                                 default:
    2368             :                                         return kZeroHPVector;
    2369             :                         }
    2370             :                         break;
    2371             :                 default:
    2372             :                         return kZeroHPVector;
    2373             :         }
    2374             :         HPVector k = HPvector_normal_or_zbasis(HPvector_subtract(p1, p0));      // 'z' axis in m
    2375             :         HPVector v = HPvector_normal_or_xbasis(HPvector_subtract(p2, p0));      // temporary vector in plane of 'forward' and 'right'
    2376             :         
    2377             :         HPVector j = HPcross_product(k, v);     // 'y' axis in m
    2378             :         HPVector i = HPcross_product(j, k);     // 'x' axis in m
    2379             :         
    2380             :         GLfloat scale = 1.0;
    2381             :         switch (c_sys[2])
    2382             :         {
    2383             :                 case 'p':
    2384             :                 {
    2385             :                         scale = 1.0f / [the_planet radius];
    2386             :                         break;
    2387             :                 }
    2388             :                 case 's':
    2389             :                 {
    2390             :                         scale = 1.0f / [the_sun radius];
    2391             :                         break;
    2392             :                 }
    2393             :                         
    2394             :                 case 'u':
    2395             :                         scale = 1.0f / HPdistance(p1, p0);
    2396             :                         break;
    2397             :                         
    2398             :                 case 'm':
    2399             :                         scale = 1.0f;
    2400             :                         break;
    2401             :                         
    2402             :                 default:
    2403             :                         return kZeroHPVector;
    2404             :         }
    2405             :         
    2406             :         // result = p0 + ijk
    2407             :         HPVector r_pos = HPvector_subtract(pos, p0);
    2408             :         HPVector result = make_HPvector(scale * (r_pos.x * i.x + r_pos.y * i.y + r_pos.z * i.z),
    2409             :                                                                 scale * (r_pos.x * j.x + r_pos.y * j.y + r_pos.z * j.z),
    2410             :                                                                 scale * (r_pos.x * k.x + r_pos.y * k.y + r_pos.z * k.z) ); // scale * dot_products
    2411             :         
    2412             :         return result;
    2413             : }
    2414             : 
    2415             : 
    2416             : - (HPVector) coordinatesFromCoordinateSystemString:(NSString *) system_x_y_z
    2417             : {
    2418             :         NSArray* tokens = ScanTokensFromString(system_x_y_z);
    2419             :         if ([tokens count] != 4)
    2420             :         {
    2421             :                 // Not necessarily an error.
    2422             :                 return make_HPvector(0,0,0);
    2423             :         }
    2424             :         GLfloat dummy;
    2425             :         return [self coordinatesForPosition:make_HPvector([tokens oo_floatAtIndex:1], [tokens oo_floatAtIndex:2], [tokens oo_floatAtIndex:3]) withCoordinateSystem:[tokens oo_stringAtIndex:0] returningScalar:&dummy];
    2426             : }
    2427             : 
    2428             : 
    2429             : - (BOOL) addShipWithRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system
    2430             : {
    2431             :         // initial position
    2432             :         GLfloat scalar = 1.0;
    2433             :         HPVector launchPos = [self coordinatesForPosition:pos withCoordinateSystem:system returningScalar:&scalar];
    2434             :         //      randomise
    2435             :         GLfloat rfactor = scalar;
    2436             :         if (rfactor > SCANNER_MAX_RANGE)
    2437             :                 rfactor = SCANNER_MAX_RANGE;
    2438             :         if (rfactor < 1000)
    2439             :                 rfactor = 1000;
    2440             :         
    2441             :         return ([self addShipWithRole:desc launchPos:launchPos rfactor:rfactor] != nil);
    2442             : }
    2443             : 
    2444             : 
    2445             : - (BOOL) addShips:(int) howMany withRole:(NSString *) desc atPosition:(HPVector) pos withCoordinateSystem:(NSString *) system
    2446             : {
    2447             :         // initial bounding box
    2448             :         GLfloat scalar = 1.0;
    2449             :         HPVector launchPos = [self coordinatesForPosition:pos withCoordinateSystem:system returningScalar:&scalar];
    2450             :         GLfloat distance_from_center = 0.0;
    2451             :         HPVector v_from_center, ship_pos;
    2452             :         HPVector ship_positions[howMany];
    2453             :         int i = 0;
    2454             :         int     scale_up_after = 0;
    2455             :         int     current_shell = 0;
    2456             :         GLfloat walk_factor = 2.0;
    2457             :         while (i < howMany)
    2458             :         {
    2459             :                 ShipEntity  *ship = [self addShipWithRole:desc launchPos:launchPos rfactor:0.0];
    2460             :                 if (ship == nil) return NO;
    2461             :                 OOScanClass scanClass = [ship scanClass];
    2462             :                 [ship setScanClass:CLASS_NO_DRAW];      // avoid lollipop flash
    2463             :                 
    2464             :                 GLfloat         safe_distance2 = ship->collision_radius * ship->collision_radius * SAFE_ADDITION_FACTOR2;
    2465             :                 BOOL            safe;
    2466             :                 int                     limit_count = 8;
    2467             :                 
    2468             :                 v_from_center = kZeroHPVector;
    2469             :                 do
    2470             :                 {
    2471             :                         do
    2472             :                         {
    2473             :                                 v_from_center.x += walk_factor * (randf() - 0.5);
    2474             :                                 v_from_center.y += walk_factor * (randf() - 0.5);
    2475             :                                 v_from_center.z += walk_factor * (randf() - 0.5);       // drunkards walk
    2476             :                         } while ((v_from_center.x == 0.0)&&(v_from_center.y == 0.0)&&(v_from_center.z == 0.0));
    2477             :                         v_from_center = HPvector_normal(v_from_center); // guaranteed non-zero
    2478             :                         
    2479             :                         ship_pos = make_HPvector(       launchPos.x + distance_from_center * v_from_center.x,
    2480             :                                                                         launchPos.y + distance_from_center * v_from_center.y,
    2481             :                                                                         launchPos.z + distance_from_center * v_from_center.z);
    2482             :                         
    2483             :                         // check this position against previous ship positions in this shell
    2484             :                         safe = YES;
    2485             :                         int j = i - 1;
    2486             :                         while (safe && (j >= current_shell))
    2487             :                         {
    2488             :                                 safe = (safe && (HPdistance2(ship_pos, ship_positions[j]) > safe_distance2));
    2489             :                                 j--;
    2490             :                         }
    2491             :                         if (!safe)
    2492             :                         {
    2493             :                                 limit_count--;
    2494             :                                 if (!limit_count)       // give up and expand the shell
    2495             :                                 {
    2496             :                                         limit_count = 8;
    2497             :                                         distance_from_center += sqrt(safe_distance2);   // expand to the next distance
    2498             :                                 }
    2499             :                         }
    2500             :                         
    2501             :                 } while (!safe);
    2502             :                 
    2503             :                 [ship setPosition:ship_pos];
    2504             :                 [ship setScanClass:scanClass == CLASS_NOT_SET ? CLASS_NEUTRAL : scanClass];
    2505             :                 
    2506             :                 Quaternion qr;
    2507             :                 quaternion_set_random(&qr);
    2508             :                 [ship setOrientation:qr];
    2509             :                 
    2510             :                 // [self addEntity:ship];       // STATUS_IN_FLIGHT, AI state GLOBAL
    2511             :                 
    2512             :                 ship_positions[i] = ship_pos;
    2513             :                 i++;
    2514             :                 if (i > scale_up_after)
    2515             :                 {
    2516             :                         current_shell = i;
    2517             :                         scale_up_after += 1 + 2 * i;
    2518             :                         distance_from_center += sqrt(safe_distance2);   // fill the next shell
    2519             :                 }
    2520             :         }
    2521             :         return YES;
    2522             : }
    2523             : 
    2524             : 
    2525             : - (BOOL) addShips:(int) howMany withRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system
    2526             : {
    2527             :         // initial bounding box
    2528             :         GLfloat scalar = 1.0;
    2529             :         HPVector launchPos = [self coordinatesForPosition:pos withCoordinateSystem:system returningScalar:&scalar];
    2530             :         GLfloat rfactor = scalar;
    2531             :         if (rfactor > SCANNER_MAX_RANGE)
    2532             :                 rfactor = SCANNER_MAX_RANGE;
    2533             :         if (rfactor < 1000)
    2534             :                 rfactor = 1000;
    2535             :         BoundingBox     launch_bbox;
    2536             :         bounding_box_reset_to_vector(&launch_bbox, make_vector(launchPos.x - rfactor, launchPos.y - rfactor, launchPos.z - rfactor));
    2537             :         bounding_box_add_xyz(&launch_bbox, launchPos.x + rfactor, launchPos.y + rfactor, launchPos.z + rfactor);
    2538             :         
    2539             :         return [self addShips: howMany withRole: desc intoBoundingBox: launch_bbox];
    2540             : }
    2541             : 
    2542             : 
    2543             : - (BOOL) addShips:(int) howMany withRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system withinRadius:(GLfloat) radius
    2544             : {
    2545             :         // initial bounding box
    2546             :         GLfloat scalar = 1.0;
    2547             :         HPVector launchPos = [self coordinatesForPosition:pos withCoordinateSystem:system returningScalar:&scalar];
    2548             :         GLfloat rfactor = radius;
    2549             :         if (rfactor < 1000)
    2550             :                 rfactor = 1000;
    2551             :         BoundingBox     launch_bbox;
    2552             :         bounding_box_reset_to_vector(&launch_bbox, make_vector(launchPos.x - rfactor, launchPos.y - rfactor, launchPos.z - rfactor));
    2553             :         bounding_box_add_xyz(&launch_bbox, launchPos.x + rfactor, launchPos.y + rfactor, launchPos.z + rfactor);
    2554             :         
    2555             :         return [self addShips: howMany withRole: desc intoBoundingBox: launch_bbox];
    2556             : }
    2557             : 
    2558             : 
    2559             : - (BOOL) addShips:(int) howMany withRole:(NSString *) desc intoBoundingBox:(BoundingBox) bbox
    2560             : {
    2561             :         if (howMany < 1)
    2562             :                 return YES;
    2563             :         if (howMany > 1)
    2564             :         {
    2565             :                 // divide the number of ships in two
    2566             :                 int h0 = howMany / 2;
    2567             :                 int h1 = howMany - h0;
    2568             :                 // split the bounding box into two along its longest dimension
    2569             :                 GLfloat lx = bbox.max.x - bbox.min.x;
    2570             :                 GLfloat ly = bbox.max.y - bbox.min.y;
    2571             :                 GLfloat lz = bbox.max.z - bbox.min.z;
    2572             :                 BoundingBox bbox0 = bbox;
    2573             :                 BoundingBox bbox1 = bbox;
    2574             :                 if ((lx > lz)&&(lx > ly)) // longest dimension is x
    2575             :                 {
    2576             :                         bbox0.min.x += 0.5 * lx;
    2577             :                         bbox1.max.x -= 0.5 * lx;
    2578             :                 }
    2579             :                 else
    2580             :                 {
    2581             :                         if (ly > lz) // longest dimension is y
    2582             :                         {
    2583             :                                 bbox0.min.y += 0.5 * ly;
    2584             :                                 bbox1.max.y -= 0.5 * ly;
    2585             :                         }
    2586             :                         else                    // longest dimension is z
    2587             :                         {
    2588             :                                 bbox0.min.z += 0.5 * lz;
    2589             :                                 bbox1.max.z -= 0.5 * lz;
    2590             :                         }
    2591             :                 }
    2592             :                 // place half the ships into each bounding box
    2593             :                 return ([self addShips: h0 withRole: desc intoBoundingBox: bbox0] && [self addShips: h1 withRole: desc intoBoundingBox: bbox1]);
    2594             :         }
    2595             :         
    2596             :         //      randomise within the bounding box (biased towards the center of the box)
    2597             :         HPVector pos = make_HPvector(bbox.min.x, bbox.min.y, bbox.min.z);
    2598             :         pos.x += 0.5 * (randf() + randf()) * (bbox.max.x - bbox.min.x);
    2599             :         pos.y += 0.5 * (randf() + randf()) * (bbox.max.y - bbox.min.y);
    2600             :         pos.z += 0.5 * (randf() + randf()) * (bbox.max.z - bbox.min.z);
    2601             :         
    2602             :         return ([self addShipWithRole:desc launchPos:pos rfactor:0.0] != nil);
    2603             : }
    2604             : 
    2605             : 
    2606             : - (BOOL) spawnShip:(NSString *) shipdesc
    2607             : {
    2608             :         // no need to do any more than log - enforcing modes wouldn't even have
    2609             :         // loaded the legacy script
    2610             :         OOStandardsDeprecated([NSString stringWithFormat:@"'spawn' via legacy script is deprecated as a way of adding ships for %@",shipdesc]);
    2611             : 
    2612             :         ShipEntity              *ship;
    2613             :         NSDictionary    *shipdict = nil;
    2614             :         
    2615             :         shipdict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipdesc];
    2616             :         if (shipdict == nil)  return NO;
    2617             :         
    2618             :         ship = [self newShipWithName:shipdesc]; // retain count is 1
    2619             :         
    2620             :         if (ship == nil)  return NO;
    2621             :         
    2622             :         // set any spawning characteristics
    2623             :         NSDictionary    *spawndict = [shipdict oo_dictionaryForKey:@"spawn"];
    2624             :         HPVector                        pos, rpos, spos;
    2625             :         NSString                *positionString = nil;
    2626             :         
    2627             :         // position
    2628             :         positionString = [spawndict oo_stringForKey:@"position"];
    2629             :         if (positionString != nil)
    2630             :         {
    2631             :                 if([positionString hasPrefix:@"abs "] && ([self planet] != nil || [self sun] !=nil))
    2632             :                 {
    2633             :                         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",@"entity",shipdesc);
    2634             :                 }
    2635             :                 
    2636             :                 pos = [self coordinatesFromCoordinateSystemString:positionString];
    2637             :         }
    2638             :         else
    2639             :         {
    2640             :                 // without position defined, the ship will be added on top of the witchpoint buoy.
    2641             :                 pos = OOHPVectorRandomRadial(SCANNER_MAX_RANGE);
    2642             :                 OOLogERR(@"universe.spawnShip.error", @"***** ERROR: failed to find a spawn position for ship %@.", shipdesc);
    2643             :         }
    2644             :         [ship setPosition:pos];
    2645             :         
    2646             :         // facing_position
    2647             :         positionString = [spawndict oo_stringForKey:@"facing_position"];
    2648             :         if (positionString != nil)
    2649             :         {
    2650             :                 if([positionString hasPrefix:@"abs "] && ([self planet] != nil || [self sun] !=nil))
    2651             :                 {
    2652             :                         OOLogWARN(@"script.deprecated", @"setting %@ for %@ '%@' in 'abs' inside .plists can cause compatibility issues across Oolite versions. Use coordinates relative to main system objects instead.",@"facing_position",@"entity",shipdesc);
    2653             :                 }
    2654             :                 
    2655             :                 spos = [ship position];
    2656             :                 Quaternion q1;
    2657             :                 rpos = [self coordinatesFromCoordinateSystemString:positionString];
    2658             :                 rpos = HPvector_subtract(rpos, spos); // position relative to ship
    2659             :                 
    2660             :                 if (!HPvector_equal(rpos, kZeroHPVector))
    2661             :                 {
    2662             :                         rpos = HPvector_normal(rpos);
    2663             :                         
    2664             :                         if (!HPvector_equal(rpos, HPvector_flip(kBasisZHPVector)))
    2665             :                         {
    2666             :                                 q1 = quaternion_rotation_between(HPVectorToVector(rpos), kBasisZVector);
    2667             :                         }
    2668             :                         else
    2669             :                         {
    2670             :                                 // for the inverse of the kBasisZVector the rotation is undefined, so we select one.
    2671             :                                 q1 = make_quaternion(0,1,0,0);
    2672             :                         }
    2673             :                         
    2674             :                                                 
    2675             :                         [ship setOrientation:q1];
    2676             :                 }
    2677             :         }
    2678             :         
    2679             :         [self addEntity:ship];  // STATUS_IN_FLIGHT, AI state GLOBAL
    2680             :         [ship release];
    2681             :         
    2682             :         return YES;
    2683             : }
    2684             : 
    2685             : 
    2686             : - (void) witchspaceShipWithPrimaryRole:(NSString *)role
    2687             : {
    2688             :         // adds a ship exiting witchspace (corollary of when ships leave the system)
    2689             :         ShipEntity                      *ship = nil;
    2690             :         NSDictionary            *systeminfo = nil;
    2691             :         OOGovernmentID          government;
    2692             :         
    2693             :         systeminfo = [self currentSystemData];
    2694             :         government = [systeminfo oo_unsignedCharForKey:KEY_GOVERNMENT];
    2695             :         
    2696             :         ship = [self newShipWithRole:role];   // retain count = 1
    2697             :         
    2698             :         // Deal with scripted cargopods and ensure they are filled with something.
    2699             :         if (ship && [ship hasRole:@"cargopod"])
    2700             :         {               
    2701             :                 [self fillCargopodWithRandomCargo:ship];
    2702             :         }
    2703             :         
    2704             :         if (ship)
    2705             :         {
    2706             :                 if (([ship scanClass] == CLASS_NO_DRAW)||([ship scanClass] == CLASS_NOT_SET))
    2707             :                         [ship setScanClass: CLASS_NEUTRAL];
    2708             :                 if ([role isEqual:@"trader"])
    2709             :                 {
    2710             :                         [ship setCargoFlag: CARGO_FLAG_FULL_SCARCE];
    2711             :                         if ([ship hasRole:@"sunskim-trader"] && randf() < 0.25) // select 1/4 of the traders suitable for sunskimming.
    2712             :                         {
    2713             :                                 [ship setCargoFlag: CARGO_FLAG_FULL_PLENTIFUL];
    2714             :                                 [self makeSunSkimmer:ship andSetAI:YES];
    2715             :                         }
    2716             :                         else
    2717             :                         {
    2718             :                                 [ship switchAITo:@"oolite-traderAI.js"];
    2719             :                         }
    2720             :                         
    2721             :                         if (([ship pendingEscortCount] > 0)&&((Ranrot() % 7) < government))       // remove escorts if we feel safe
    2722             :                         {
    2723             :                                 int nx = [ship pendingEscortCount] - 2 * (1 + (Ranrot() & 3));      // remove 2,4,6, or 8 escorts
    2724             :                                 [ship setPendingEscortCount:(nx > 0) ? nx : 0];
    2725             :                         }
    2726             :                 }
    2727             :                 if ([role isEqual:@"pirate"])
    2728             :                 {
    2729             :                         [ship setCargoFlag: CARGO_FLAG_PIRATE];
    2730             :                         [ship setBounty: (Ranrot() & 7) + (Ranrot() & 7) + ((randf() < 0.05)? 63 : 23) withReason:kOOLegalStatusReasonSetup];        // they already have a price on their heads
    2731             :                 }
    2732             :                 if ([ship crew] == nil && ![ship isUnpiloted])
    2733             :                         [ship setCrew:[NSArray arrayWithObject:
    2734             :                                 [OOCharacter randomCharacterWithRole:role
    2735             :                                 andOriginalSystem: Ranrot() & 255]]];
    2736             :                 // The following is set inside leaveWitchspace: AI state GLOBAL, STATUS_EXITING_WITCHSPACE, ai message: EXITED_WITCHSPACE, then STATUS_IN_FLIGHT
    2737             :                 [ship leaveWitchspace];
    2738             :                 [ship release];
    2739             :         }
    2740             : }
    2741             : 
    2742             : 
    2743             : // adds a ship within the collision radius of the other entity
    2744             : - (ShipEntity *) spawnShipWithRole:(NSString *) desc near:(Entity *) entity
    2745             : {
    2746             :         if (entity == nil)  return nil;
    2747             :         
    2748             :         ShipEntity  *ship = nil;
    2749             :         HPVector                spawn_pos;
    2750             :         Quaternion      spawn_q;
    2751             :         GLfloat         offset = (randf() + randf()) * entity->collision_radius;
    2752             :         
    2753             :         quaternion_set_random(&spawn_q);
    2754             :         spawn_pos = HPvector_add([entity position], vectorToHPVector(vector_multiply_scalar(vector_forward_from_quaternion(spawn_q), offset)));
    2755             :         
    2756             :         ship = [self addShipWithRole:desc launchPos:spawn_pos rfactor:0.0];
    2757             :         [ship setOrientation:spawn_q];
    2758             :         
    2759             :         return ship;
    2760             : }
    2761             : 
    2762             : 
    2763             : - (OOVisualEffectEntity *) addVisualEffectAt:(HPVector)pos withKey:(NSString *)key
    2764             : {
    2765             :         OOJS_PROFILE_ENTER
    2766             :         
    2767             :         // minimise the time between creating ship & assigning position.
    2768             :         
    2769             :         OOVisualEffectEntity            *vis = [self newVisualEffectWithName:key]; // is retained
    2770             :         BOOL                            success = NO;
    2771             :         if (vis != nil)
    2772             :         {
    2773             :                 [vis setPosition:pos];
    2774             :                 [vis setOrientation:OORandomQuaternion()];
    2775             :                 
    2776             :                 success = [self addEntity:vis]; // retained globally now
    2777             :                 
    2778             :                 [vis release];
    2779             :         }
    2780             :         return success ? vis : (OOVisualEffectEntity *)nil;
    2781             :         
    2782             :         OOJS_PROFILE_EXIT
    2783             : }
    2784             : 
    2785             : 
    2786             : - (ShipEntity *) addShipAt:(HPVector)pos withRole:(NSString *)role withinRadius:(GLfloat)radius
    2787             : {
    2788             :         OOJS_PROFILE_ENTER
    2789             :         
    2790             :         // minimise the time between creating ship & assigning position.
    2791             :         if (radius == NSNotFound)
    2792             :         {
    2793             :                 GLfloat scalar = 1.0;
    2794             :                 [self coordinatesForPosition:pos withCoordinateSystem:@"abs" returningScalar:&scalar];
    2795             :                 //      randomise
    2796             :                 GLfloat rfactor = scalar;
    2797             :                 if (rfactor > SCANNER_MAX_RANGE)
    2798             :                         rfactor = SCANNER_MAX_RANGE;
    2799             :                 if (rfactor < 1000)
    2800             :                         rfactor = 1000;
    2801             :                 pos.x += rfactor*(randf() - randf());
    2802             :                 pos.y += rfactor*(randf() - randf());
    2803             :                 pos.z += rfactor*(randf() - randf());
    2804             :         }
    2805             :         else
    2806             :         {
    2807             :                 pos = HPvector_add(pos, OOHPVectorRandomSpatial(radius));
    2808             :         }
    2809             :         
    2810             :         ShipEntity              *ship = [self newShipWithRole:role]; // is retained
    2811             :         BOOL                            success = NO;
    2812             :         
    2813             :         if (ship != nil)
    2814             :         {
    2815             :                 [ship setPosition:pos];
    2816             :                 if ([ship hasRole:@"cargopod"]) [self fillCargopodWithRandomCargo:ship];
    2817             :                 OOScanClass scanClass = [ship scanClass];
    2818             :                 if (scanClass == CLASS_NOT_SET)
    2819             :                 {
    2820             :                         scanClass = CLASS_NEUTRAL;
    2821             :                         [ship setScanClass:scanClass];
    2822             :                 }
    2823             :                 
    2824             :                 if ([ship crew] == nil && ![ship isUnpiloted])
    2825             :                 {
    2826             :                         [ship setCrew:[NSArray arrayWithObject:
    2827             :                                 [OOCharacter randomCharacterWithRole:role
    2828             :                                 andOriginalSystem:Ranrot() & 255]]];
    2829             :                 }
    2830             :                 
    2831             :                 [ship setOrientation:OORandomQuaternion()];
    2832             :                 
    2833             :                 BOOL trader = [role isEqualToString:@"trader"];
    2834             :                 if (trader)
    2835             :                 {
    2836             :                         // half of traders created anywhere will now have cargo. 
    2837             :                         if (randf() > 0.5f)
    2838             :                         {
    2839             :                                 [ship setCargoFlag:(randf() < 0.66f ? CARGO_FLAG_FULL_PLENTIFUL : CARGO_FLAG_FULL_SCARCE)];  // most of them will carry the cargo produced in-system.
    2840             :                         }
    2841             :                         
    2842             :                         uint8_t pendingEscortCount = [ship pendingEscortCount];
    2843             :                         if (pendingEscortCount > 0)
    2844             :                         {
    2845             :                                 OOGovernmentID government = [[self currentSystemData] oo_unsignedCharForKey:KEY_GOVERNMENT];
    2846             :                                 if ((Ranrot() % 7) < government)     // remove escorts if we feel safe
    2847             :                                 {
    2848             :                                         int nx = pendingEscortCount - 2 * (1 + (Ranrot() & 3));     // remove 2,4,6, or 8 escorts
    2849             :                                         [ship setPendingEscortCount:(nx > 0) ? nx : 0];
    2850             :                                 }
    2851             :                         }
    2852             :                 }
    2853             :                 
    2854             :                 if (HPdistance([self getWitchspaceExitPosition], pos) > SCANNER_MAX_RANGE)
    2855             :                 {
    2856             :                         // nothing extra to do
    2857             :                         success = [self addEntity:ship];        // STATUS_IN_FLIGHT, AI state GLOBAL - ship is retained globally                        
    2858             :                 }
    2859             :                 else    // witchspace incoming traders & pirates need extra settings.
    2860             :                 {
    2861             :                         if (trader)
    2862             :                         {
    2863             :                                 [ship setCargoFlag:CARGO_FLAG_FULL_SCARCE];
    2864             :                                 if ([ship hasRole:@"sunskim-trader"] && randf() < 0.25) 
    2865             :                                 {
    2866             :                                         [ship setCargoFlag:CARGO_FLAG_FULL_PLENTIFUL];
    2867             :                                         [self makeSunSkimmer:ship andSetAI:YES];
    2868             :                                 }
    2869             :                                 else
    2870             :                                 {
    2871             :                                         [ship switchAITo:@"oolite-traderAI.js"];
    2872             :                                 }
    2873             :                         }
    2874             :                         else if ([role isEqual:@"pirate"])
    2875             :                         {
    2876             :                                 [ship setBounty:(Ranrot() & 7) + (Ranrot() & 7) + ((randf() < 0.05)? 63 : 23) withReason:kOOLegalStatusReasonSetup]; // they already have a price on their heads
    2877             :                         }
    2878             :                         
    2879             :                         // Status changes inside the following call: AI state GLOBAL, then STATUS_EXITING_WITCHSPACE, 
    2880             :                         // with the EXITED_WITCHSPACE message sent to the AI. At last we set STATUS_IN_FLIGHT.
    2881             :                         // Includes addEntity, so ship is retained globally.
    2882             :                         success = [ship witchspaceLeavingEffects];
    2883             :                 }
    2884             :                 
    2885             :                 [ship release];
    2886             :         }
    2887             :         return success ? ship : (ShipEntity *)nil;
    2888             :         
    2889             :         OOJS_PROFILE_EXIT
    2890             : }
    2891             : 
    2892             : 
    2893             : - (NSArray *) addShipsAt:(HPVector)pos withRole:(NSString *)role quantity:(unsigned)count withinRadius:(GLfloat)radius asGroup:(BOOL)isGroup
    2894             : {
    2895             :         OOJS_PROFILE_ENTER
    2896             :         
    2897             :         NSMutableArray          *ships = [NSMutableArray arrayWithCapacity:count];
    2898             :         ShipEntity                      *ship = nil;
    2899             :         OOShipGroup                     *group = nil;
    2900             :         
    2901             :         if (isGroup)
    2902             :         {
    2903             :                 group = [OOShipGroup groupWithName:[NSString stringWithFormat:@"%@ group", role]];
    2904             :         }
    2905             :         
    2906             :         while (count--)
    2907             :         {
    2908             :                 ship = [self addShipAt:pos withRole:role withinRadius:radius];
    2909             :                 if (ship != nil)
    2910             :                 {
    2911             :                         // TODO: avoid collisions!!!
    2912             :                         if (isGroup) [ship setGroup:group];
    2913             :                         [ships addObject:ship];
    2914             :                 }
    2915             :         }
    2916             :         
    2917             :         if ([ships count] == 0) return nil;
    2918             :         
    2919             :         return [[ships copy] autorelease];
    2920             :         
    2921             :         OOJS_PROFILE_EXIT
    2922             : }
    2923             : 
    2924             : 
    2925             : - (NSArray *) addShipsToRoute:(NSString *)route withRole:(NSString *)role quantity:(unsigned)count routeFraction:(double)routeFraction asGroup:(BOOL)isGroup
    2926             : {
    2927             :         NSMutableArray                  *ships = [NSMutableArray arrayWithCapacity:count];
    2928             :         ShipEntity                              *ship = nil;
    2929             :         Entity<OOStellarBody>     *entity = nil;
    2930             :         HPVector                                        pos = kZeroHPVector, direction = kZeroHPVector, point0 = kZeroHPVector, point1 = kZeroHPVector;
    2931             :         double                                  radius = 0;
    2932             :         
    2933             :         if ([route isEqualToString:@"pw"] || [route isEqualToString:@"sw"] || [route isEqualToString:@"ps"])
    2934             :         {
    2935             :                 routeFraction = 1.0f - routeFraction; 
    2936             :         }
    2937             :         
    2938             :         // which route is it?
    2939             :         if ([route isEqualTo:@"wp"] || [route isEqualTo:@"pw"])
    2940             :         {
    2941             :                 point0 = [self getWitchspaceExitPosition];
    2942             :                 entity = [self planet];
    2943             :                 if (entity == nil)  return nil;
    2944             :                 point1 = [entity position];
    2945             :                 radius = [entity radius];
    2946             :         }
    2947             :         else if ([route isEqualTo:@"ws"] || [route isEqualTo:@"sw"])
    2948             :         {
    2949             :                 point0 = [self getWitchspaceExitPosition];
    2950             :                 entity = [self sun];
    2951             :                 if (entity == nil)  return nil;
    2952             :                 point1 = [entity position];
    2953             :                 radius = [entity radius];
    2954             :         }
    2955             :         else if ([route isEqualTo:@"sp"] || [route isEqualTo:@"ps"])
    2956             :         {
    2957             :                 entity = [self sun];
    2958             :                 if (entity == nil)  return nil;
    2959             :                 point0 = [entity position];
    2960             :                 double radius0 = [entity radius];
    2961             :                 
    2962             :                 entity = [self planet];
    2963             :                 if (entity == nil)  return nil;
    2964             :                 point1 = [entity position];
    2965             :                 radius = [entity radius];
    2966             :                 
    2967             :                 // shorten the route by scanner range & sun radius, otherwise ships could be created inside it.
    2968             :                 direction = HPvector_normal(HPvector_subtract(point0, point1));
    2969             :                 point0 = HPvector_subtract(point0, HPvector_multiply_scalar(direction, radius0 + SCANNER_MAX_RANGE * 1.1f));
    2970             :         }
    2971             :         else if ([route isEqualTo:@"st"])
    2972             :         {
    2973             :                 point0 = [self getWitchspaceExitPosition];
    2974             :                 if ([self station] == nil)  return nil;
    2975             :                 point1 = [[self station] position];
    2976             :                 radius = [[self station] collisionRadius];
    2977             :         }
    2978             :         else return nil;        // no route specifier? We shouldn't be here!
    2979             :         
    2980             :         // shorten the route by scanner range & radius, otherwise ships could be created inside the route destination.
    2981             :         direction = HPvector_normal(HPvector_subtract(point1, point0));
    2982             :         point1 = HPvector_subtract(point1, HPvector_multiply_scalar(direction, radius + SCANNER_MAX_RANGE * 1.1f));
    2983             :         
    2984             :         pos = [self fractionalPositionFrom:point0 to:point1 withFraction:routeFraction];
    2985             :         if(isGroup)
    2986             :         {       
    2987             :                 return [self addShipsAt:pos withRole:role quantity:count withinRadius:(SCANNER_MAX_RANGE / 10.0f) asGroup:YES];
    2988             :         }
    2989             :         else
    2990             :         {
    2991             :                 while (count--)
    2992             :                 {
    2993             :                         ship = [self addShipAt:pos withRole:role withinRadius:0]; // no radius because pos is already randomised with SCANNER_MAX_RANGE.
    2994             :                         if (ship != nil) [ships addObject:ship];
    2995             :                         if (count > 0) pos = [self fractionalPositionFrom:point0 to:point1 withFraction:routeFraction];
    2996             :                 }
    2997             :                 
    2998             :                 if ([ships count] == 0) return nil;
    2999             :         }
    3000             :         
    3001             :         return [[ships copy] autorelease];
    3002             : }
    3003             : 
    3004             : 
    3005             : - (BOOL) roleIsPirateVictim:(NSString *)role
    3006             : {
    3007             :         return [self role:role isInCategory:@"oolite-pirate-victim"];
    3008             : }
    3009             : 
    3010             : 
    3011             : - (BOOL) role:(NSString *)role isInCategory:(NSString *)category
    3012             : {
    3013             :         NSSet *categoryInfo = [roleCategories objectForKey:category];
    3014             :         if (categoryInfo == nil)
    3015             :         {
    3016             :                 return NO;
    3017             :         }
    3018             :         return [categoryInfo containsObject:role];
    3019             : }
    3020             : 
    3021             : 
    3022             : // used to avoid having lost escorts when player advances clock while docked
    3023             : - (void) forceWitchspaceEntries
    3024             : {
    3025             :         unsigned i;
    3026             :         for (i = 0; i < n_entities; i++)
    3027             :         {
    3028             :                 if (sortedEntities[i]->isShip)
    3029             :                 {
    3030             :                         ShipEntity *my_ship = (ShipEntity*)sortedEntities[i];
    3031             :                         Entity* my_target = [my_ship primaryTarget];
    3032             :                         if ([my_target isWormhole])
    3033             :                         {
    3034             :                                 [my_ship enterTargetWormhole];
    3035             :                         }
    3036             :                         else if ([[[my_ship getAI] state] isEqualToString:@"ENTER_WORMHOLE"])
    3037             :                         {
    3038             :                                 [my_ship enterTargetWormhole];
    3039             :                         }
    3040             :                 }
    3041             :         }
    3042             : }
    3043             : 
    3044             : 
    3045             : - (void) addWitchspaceJumpEffectForShip:(ShipEntity *)ship
    3046             : {
    3047             :         // don't add rings when system is being populated
    3048             :         if ([PLAYER status] != STATUS_ENTERING_WITCHSPACE && [PLAYER status] != STATUS_EXITING_WITCHSPACE)
    3049             :         {
    3050             :                 [self addEntity:[OORingEffectEntity ringFromEntity:ship]];
    3051             :                 [self addEntity:[OORingEffectEntity shrinkingRingFromEntity:ship]];
    3052             :         }
    3053             : }
    3054             : 
    3055             : 
    3056             : - (GLfloat) safeWitchspaceExitDistance
    3057             : {
    3058             :         for (unsigned i = 0; i < n_entities; i++)
    3059             :         {
    3060             :                 Entity *e2 = sortedEntities[i];
    3061             :                 if ([e2 isShip] && [(ShipEntity*)e2 hasPrimaryRole:@"buoy-witchpoint"])
    3062             :                 {
    3063             :                         return [(ShipEntity*)e2 collisionRadius] + MIN_DISTANCE_TO_BUOY;
    3064             :                 }
    3065             :         }
    3066             :         return MIN_DISTANCE_TO_BUOY;
    3067             : }
    3068             : 
    3069             : 
    3070             : - (void) setUpBreakPattern:(HPVector) pos orientation:(Quaternion) q forDocking:(BOOL) forDocking
    3071             : {
    3072             :         int                                             i;
    3073             :         OOBreakPatternEntity    *ring = nil;
    3074             :         id                                              colorDesc = nil;
    3075             :         OOColor                                 *color = nil;
    3076             :         
    3077             :         [self setViewDirection:VIEW_FORWARD];
    3078             :         
    3079             :         q.w = -q.w;             // reverse the quaternion because this is from the player's viewpoint
    3080             :         
    3081             :         Vector                  v = vector_forward_from_quaternion(q);
    3082             :         Vector                  vel = vector_multiply_scalar(v, -BREAK_PATTERN_RING_SPEED);
    3083             :         
    3084             :         // hyperspace colours
    3085             :         
    3086             :         OOColor *col1 = [OOColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5];        //standard tunnel colour
    3087             :         OOColor *col2 = [OOColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:0.25];       //standard tunnel colour
    3088             :         
    3089             :         colorDesc = [[self globalSettings] objectForKey:@"hyperspace_tunnel_color_1"];
    3090             :         if (colorDesc != nil)
    3091             :         {
    3092             :                 color = [OOColor colorWithDescription:colorDesc];
    3093             :                 if (color != nil)  col1 = color;
    3094             :                 else  OOLogWARN(@"hyperspaceTunnel.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc);
    3095             :         }
    3096             : 
    3097             :         colorDesc = [[self globalSettings] objectForKey:@"hyperspace_tunnel_color_2"];
    3098             :         if (colorDesc != nil)
    3099             :         {
    3100             :                 color = [OOColor colorWithDescription:colorDesc];
    3101             :                 if (color != nil)  col2 = color;
    3102             :                 else  OOLogWARN(@"hyperspaceTunnel.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc);
    3103             :         }
    3104             :         
    3105             :         unsigned        sides = kOOBreakPatternMaxSides;
    3106             :         GLfloat         startAngle = 0;
    3107             :         GLfloat         aspectRatio = 1;
    3108             :         
    3109             :         if (forDocking)
    3110             :         {
    3111             :                 NSDictionary *info = [[PLAYER dockedStation] shipInfoDictionary];
    3112             :                 sides = [info oo_unsignedIntForKey:@"tunnel_corners" defaultValue:4];
    3113             :                 startAngle = [info oo_floatForKey:@"tunnel_start_angle" defaultValue:45.0f];
    3114             :                 aspectRatio = [info oo_floatForKey:@"tunnel_aspect_ratio" defaultValue:2.67f];
    3115             :         }
    3116             :         
    3117             :         for (i = 1; i < 11; i++)
    3118             :         {
    3119             :                 ring = [OOBreakPatternEntity breakPatternWithPolygonSides:sides startAngle:startAngle aspectRatio:aspectRatio];
    3120             :                 if (!forDocking)
    3121             :                 {
    3122             :                         [ring setInnerColor:col1 outerColor:col2];
    3123             :                 }
    3124             :                 
    3125             :                 Vector offset = vector_multiply_scalar(v, i * BREAK_PATTERN_RING_SPACING);
    3126             :                 [ring setPosition:HPvector_add(pos, vectorToHPVector(offset))];  // ahead of the player
    3127             :                 [ring setOrientation:q];
    3128             :                 [ring setVelocity:vel];
    3129             :                 [ring setLifetime:i * BREAK_PATTERN_RING_SPACING];
    3130             :                 
    3131             :                 // FIXME: better would be to have break pattern timing not depend on
    3132             :                 // these ring objects existing in the first place. - CIM
    3133             :                 if (forDocking && ![[PLAYER dockedStation] hasBreakPattern])
    3134             :                 {
    3135             :                         ring->isImmuneToBreakPatternHide = NO;
    3136             :                 }
    3137             :                 else if (!forDocking && ![self witchspaceBreakPattern])
    3138             :                 {
    3139             :                         ring->isImmuneToBreakPatternHide = NO;
    3140             :                 }
    3141             :                 [self addEntity:ring];
    3142             :                 breakPatternCounter++;
    3143             :         }
    3144             : }
    3145             : 
    3146             : 
    3147             : - (BOOL) witchspaceBreakPattern
    3148             : {
    3149             :         return _witchspaceBreakPattern;
    3150             : }
    3151             : 
    3152             : 
    3153             : - (void) setWitchspaceBreakPattern:(BOOL)newValue
    3154             : {
    3155             :         _witchspaceBreakPattern = !!newValue;
    3156             : }
    3157             : 
    3158             : 
    3159             : - (BOOL) dockingClearanceProtocolActive
    3160             : {
    3161             :         return _dockingClearanceProtocolActive;
    3162             : }
    3163             : 
    3164             : 
    3165             : - (void) setDockingClearanceProtocolActive:(BOOL)newValue
    3166             : {
    3167             :         OOShipRegistry  *registry = [OOShipRegistry sharedRegistry];
    3168             :         NSEnumerator    *statEnum = [allStations objectEnumerator]; 
    3169             :         StationEntity   *station = nil;
    3170             : 
    3171             :         /* CIM: picking a random ship type which can take the same primary
    3172             :          * role as the station to determine whether it has no set docking
    3173             :          * clearance requirements seems unlikely to work entirely
    3174             :          * correctly. To be fixed. */
    3175             :                                                    
    3176             :         while ((station = [statEnum nextObject]))
    3177             :         {
    3178             :                 NSString        *stationKey = [registry randomShipKeyForRole:[station primaryRole]];
    3179             :                 if (![[[registry shipInfoForKey:stationKey] allKeys] containsObject:@"requires_docking_clearance"])
    3180             :                 {
    3181             :                         [station setRequiresDockingClearance:!!newValue];
    3182             :                 }
    3183             :         }
    3184             :         
    3185             :         _dockingClearanceProtocolActive = !!newValue;
    3186             : }
    3187             : 
    3188             : 
    3189             : - (void) handleGameOver
    3190             : {
    3191             :         if ([[self gameController] playerFileToLoad])
    3192             :         {
    3193             :                 [[self gameController] loadPlayerIfRequired];
    3194             :         }
    3195             :         else
    3196             :         {
    3197             :                 [self setUseAddOns:SCENARIO_OXP_DEFINITION_ALL fromSaveGame:NO forceReinit:YES]; // calls reinitAndShowDemo
    3198             :         } 
    3199             : }
    3200             : 
    3201             : 
    3202             : - (void) setupIntroFirstGo:(BOOL)justCobra
    3203             : {
    3204             :         PlayerEntity    *player = PLAYER;
    3205             :         ShipEntity              *ship = nil;
    3206             :         Quaternion              q2 = { 0.0f, 0.0f, 1.0f, 0.0f }; // w,x,y,z
    3207             :         
    3208             :         // in status demo draw ships and display text
    3209             :         if (!justCobra)
    3210             :         {
    3211             :                 DESTROY(demo_ships);
    3212             :                 demo_ships = [[[OOShipRegistry sharedRegistry] demoShipKeys] retain];
    3213             :                 // always, even if it's the cobra, because it's repositioned
    3214             :                 [self removeDemoShips];
    3215             :         }
    3216             :         if (justCobra)
    3217             :         {
    3218             :                 [player setStatus: STATUS_START_GAME];
    3219             :         }
    3220             :         [player setShowDemoShips: YES];
    3221             :         displayGUI = YES;
    3222             :         
    3223             :         if (justCobra)
    3224             :         {
    3225             :                 /*- cobra - intro1 -*/
    3226             :                 ship = [self newShipWithName:PLAYER_SHIP_DESC usePlayerProxy:YES];
    3227             :         }
    3228             :         else
    3229             :         {
    3230             :                 /*- demo ships - intro2 -*/
    3231             : 
    3232             :                 demo_ship_index = 0;
    3233             :                 demo_ship_subindex = 0;
    3234             : 
    3235             :                 /* Try to set the initial list position to Cobra III if
    3236             :                  * available, and at least the Ships category. */
    3237             :                 NSArray *subList = nil;
    3238             :                 foreach (subList, demo_ships)
    3239             :                 {
    3240             :                         if ([[[subList oo_dictionaryAtIndex:0] oo_stringForKey:kOODemoShipClass] isEqualToString:@"ship"])
    3241             :                         {
    3242             :                                 demo_ship_index = [demo_ships indexOfObject:subList];
    3243             :                                 NSDictionary *shipEntry = nil;
    3244             :                                 foreach (shipEntry, subList)
    3245             :                                 {
    3246             :                                         if ([[shipEntry oo_stringForKey:kOODemoShipKey] isEqualToString:@"cobra3-trader"])
    3247             :                                         {
    3248             :                                                 demo_ship_subindex = [subList indexOfObject:shipEntry];
    3249             :                                                 break;
    3250             :                                         }
    3251             :                                 }
    3252             :                                 break;
    3253             :                         }
    3254             :                 }
    3255             : 
    3256             : 
    3257             :                 if (!demo_ship) ship = [self newShipWithName:[[[demo_ships oo_arrayAtIndex:demo_ship_index] oo_dictionaryAtIndex:demo_ship_subindex] oo_stringForKey:kOODemoShipKey] usePlayerProxy:NO];
    3258             :                 // stop consistency problems on the ship library screen
    3259             :                 [ship removeEquipmentItem:@"EQ_SHIELD_BOOSTER"];
    3260             :                 [ship removeEquipmentItem:@"EQ_SHIELD_ENHANCER"];
    3261             :         }
    3262             :         
    3263             :         if (ship)
    3264             :         {
    3265             :                 [ship setOrientation:q2];
    3266             :                 if (!justCobra)
    3267             :                 {
    3268             :                         [ship setPositionX:0.0f y:0.0f z:DEMO2_VANISHING_DISTANCE * ship->collision_radius * 0.01];
    3269             :                         [ship setDestination: ship->position];       // ideal position
    3270             :                 }
    3271             :                 else
    3272             :                 {
    3273             :                         // main screen Cobra is closer
    3274             :                         [ship setPositionX:0.0f y:0.0f z:3.6 * ship->collision_radius];
    3275             :                 }
    3276             :                 [ship setDemoShip: 1.0f];
    3277             :                 [ship setDemoStartTime: universal_time];
    3278             :                 [ship setScanClass: CLASS_NO_DRAW];
    3279             :                 [ship switchAITo:@"nullAI.plist"];
    3280             :                 if([ship pendingEscortCount] > 0) [ship setPendingEscortCount:0];
    3281             :                 [self addEntity:ship];  // STATUS_IN_FLIGHT, AI state GLOBAL
    3282             :                 // now override status
    3283             :                 [ship setStatus:STATUS_COCKPIT_DISPLAY];
    3284             :                 demo_ship = ship;
    3285             :                 
    3286             :                 [ship release];
    3287             :         }
    3288             :         
    3289             :         if (!justCobra)
    3290             :         {
    3291             : //              [gui setText:[demo_ship displayName] forRow:19 align:GUI_ALIGN_CENTER];
    3292             :                 [self setLibraryTextForDemoShip];
    3293             :         }
    3294             :         
    3295             :         [self enterGUIViewModeWithMouseInteraction:NO];
    3296             :         if (!justCobra)
    3297             :         {
    3298             :                 demo_stage = DEMO_SHOW_THING;
    3299             :                 demo_stage_time = universal_time + 300.0;
    3300             :         }
    3301             : }
    3302             : 
    3303             : 
    3304           0 : - (NSDictionary *)demoShipData
    3305             : {
    3306             :         return [[demo_ships oo_arrayAtIndex:demo_ship_index] oo_dictionaryAtIndex:demo_ship_subindex];
    3307             : }
    3308             : 
    3309             : 
    3310           0 : - (void) setLibraryTextForDemoShip
    3311             : {
    3312             :         OOGUITabSettings tab_stops;
    3313             :         tab_stops[0] = 0;
    3314             :         tab_stops[1] = 170;
    3315             :         tab_stops[2] = 340;
    3316             :         [gui setTabStops:tab_stops];
    3317             : 
    3318             : /*      [gui setText:[demo_ship displayName] forRow:19 align:GUI_ALIGN_CENTER];
    3319             :         [gui setColor:[OOColor whiteColor] forRow:19]; */
    3320             : 
    3321             :         NSDictionary *librarySettings = [self demoShipData];
    3322             :         
    3323             :         OOGUIRow descRow = 7;
    3324             : 
    3325             :         NSString *field1 = nil;
    3326             :         NSString *field2 = nil;
    3327             :         NSString *field3 = nil;
    3328             :         NSString *override = nil;
    3329             : 
    3330             :         // clear rows
    3331             :         for (NSUInteger i=1;i<=26;i++)
    3332             :         {
    3333             :                 [gui setText:@"" forRow:i];
    3334             :         }
    3335             :         
    3336             :         /* Row 1: ScanClass, Name, Summary */
    3337             :         override = [librarySettings oo_stringForKey:kOODemoShipClass defaultValue:@"ship"];
    3338             :         field1 = OOShipLibraryCategorySingular(override);
    3339             : 
    3340             : 
    3341             :         field2 = [demo_ship shipClassName];
    3342             : 
    3343             : 
    3344             :         override = [librarySettings oo_stringForKey:kOODemoShipSummary defaultValue:nil];
    3345             :         if (override != nil)
    3346             :         {
    3347             :                 field3 = OOExpand(override);
    3348             :         }
    3349             :         else
    3350             :         {
    3351             :                 field3 = @"";
    3352             :         }
    3353             :         [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:1];
    3354             :         [gui setColor:[OOColor greenColor] forRow:1];
    3355             : 
    3356             :         // ship_data defaults to true for "ship" class, false for everything else
    3357             :         if (![librarySettings oo_boolForKey:kOODemoShipShipData defaultValue:[[librarySettings oo_stringForKey:kOODemoShipClass defaultValue:@"ship"] isEqualToString:@"ship"]])
    3358             :         {
    3359             :                 descRow = 3;
    3360             :         }
    3361             :         else
    3362             :         {
    3363             :                 /* Row 2: Speed, Turn Rate, Cargo */
    3364             : 
    3365             :                 override = [librarySettings oo_stringForKey:kOODemoShipSpeed defaultValue:nil];
    3366             :                 if (override != nil)
    3367             :                 {
    3368             :                         if ([override length] == 0)
    3369             :                         {
    3370             :                                 field1 = @"";
    3371             :                         }
    3372             :                         else
    3373             :                         {
    3374             :                                 field1 = [NSString stringWithFormat:DESC(@"oolite-ship-library-speed-custom"),OOExpand(override)];
    3375             :                         }
    3376             :                 }
    3377             :                 else
    3378             :                 {
    3379             :                         field1 = OOShipLibrarySpeed(demo_ship);
    3380             :                 }
    3381             :                 
    3382             : 
    3383             :                 override = [librarySettings oo_stringForKey:kOODemoShipTurnRate defaultValue:nil];
    3384             :                 if (override != nil)
    3385             :                 {
    3386             :                         if ([override length] == 0)
    3387             :                         {
    3388             :                                 field2 = @"";
    3389             :                         }
    3390             :                         else
    3391             :                         {
    3392             :                                 field2 = [NSString stringWithFormat:DESC(@"oolite-ship-library-turn-custom"),OOExpand(override)];
    3393             :                         }
    3394             :                 }
    3395             :                 else
    3396             :                 {
    3397             :                         field2 = OOShipLibraryTurnRate(demo_ship);
    3398             :                 }
    3399             : 
    3400             : 
    3401             :                 override = [librarySettings oo_stringForKey:kOODemoShipCargo defaultValue:nil];
    3402             :                 if (override != nil)
    3403             :                 {
    3404             :                         if ([override length] == 0)
    3405             :                         {
    3406             :                                 field3 = @"";
    3407             :                         }
    3408             :                         else
    3409             :                         {
    3410             :                                 field3 = [NSString stringWithFormat:DESC(@"oolite-ship-library-cargo-custom"),OOExpand(override)];
    3411             :                         }
    3412             :                 }
    3413             :                 else
    3414             :                 {
    3415             :                         field3 = OOShipLibraryCargo(demo_ship);
    3416             :                 }
    3417             :         
    3418             : 
    3419             :                 [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:3];
    3420             : 
    3421             :                 /* Row 3: recharge rate, energy banks, witchspace */
    3422             :                 override = [librarySettings oo_stringForKey:kOODemoShipGenerator defaultValue:nil];
    3423             :                 if (override != nil)
    3424             :                 {
    3425             :                         if ([override length] == 0)
    3426             :                         {
    3427             :                                 field1 = @"";
    3428             :                         }
    3429             :                         else
    3430             :                         {
    3431             :                                 field1 = [NSString stringWithFormat:DESC(@"oolite-ship-library-generator-custom"),OOExpand(override)];
    3432             :                         }
    3433             :                 }
    3434             :                 else
    3435             :                 {
    3436             :                         field1 = OOShipLibraryGenerator(demo_ship);
    3437             :                 }
    3438             : 
    3439             : 
    3440             :                 override = [librarySettings oo_stringForKey:kOODemoShipShields defaultValue:nil];
    3441             :                 if (override != nil)
    3442             :                 {
    3443             :                         if ([override length] == 0)
    3444             :                         {
    3445             :                                 field2 = @"";
    3446             :                         }
    3447             :                         else
    3448             :                         {
    3449             :                                 field2 = [NSString stringWithFormat:DESC(@"oolite-ship-library-shields-custom"),OOExpand(override)];
    3450             :                         }
    3451             :                 }
    3452             :                 else
    3453             :                 {
    3454             :                         field2 = OOShipLibraryShields(demo_ship);
    3455             :                 }
    3456             : 
    3457             : 
    3458             :                 override = [librarySettings oo_stringForKey:kOODemoShipWitchspace defaultValue:nil];
    3459             :                 if (override != nil)
    3460             :                 {
    3461             :                         if ([override length] == 0)
    3462             :                         {
    3463             :                                 field3 = @"";
    3464             :                         }
    3465             :                         else
    3466             :                         {
    3467             :                                 field3 = [NSString stringWithFormat:DESC(@"oolite-ship-library-witchspace-custom"),OOExpand(override)];
    3468             :                         }
    3469             :                 }
    3470             :                 else
    3471             :                 {
    3472             :                         field3 = OOShipLibraryWitchspace(demo_ship);
    3473             :                 }
    3474             : 
    3475             : 
    3476             :                 [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:4];
    3477             : 
    3478             : 
    3479             :                 /* Row 4: weapons, turrets, size */
    3480             :                 override = [librarySettings oo_stringForKey:kOODemoShipWeapons defaultValue:nil];
    3481             :                 if (override != nil)
    3482             :                 {
    3483             :                         if ([override length] == 0)
    3484             :                         {
    3485             :                                 field1 = @"";
    3486             :                         }
    3487             :                         else
    3488             :                         {
    3489             :                                 field1 = [NSString stringWithFormat:DESC(@"oolite-ship-library-weapons-custom"),OOExpand(override)];
    3490             :                         }
    3491             :                 }
    3492             :                 else
    3493             :                 {
    3494             :                         field1 = OOShipLibraryWeapons(demo_ship);
    3495             :                 }
    3496             : 
    3497             :                 override = [librarySettings oo_stringForKey:kOODemoShipTurrets defaultValue:nil];
    3498             :                 if (override != nil)
    3499             :                 {
    3500             :                         if ([override length] == 0)
    3501             :                         {
    3502             :                                 field2 = @"";
    3503             :                         }
    3504             :                         else
    3505             :                         {
    3506             :                                 field2 = [NSString stringWithFormat:DESC(@"oolite-ship-library-turrets-custom"),OOExpand(override)];
    3507             :                         }
    3508             :                 }
    3509             :                 else
    3510             :                 {
    3511             :                         field2 = OOShipLibraryTurrets(demo_ship);
    3512             :                 }
    3513             : 
    3514             :                 override = [librarySettings oo_stringForKey:kOODemoShipSize defaultValue:nil];
    3515             :                 if (override != nil)
    3516             :                 {
    3517             :                         if ([override length] == 0)
    3518             :                         {
    3519             :                                 field3 = @"";
    3520             :                         }
    3521             :                         else
    3522             :                         {
    3523             :                                 field3 = [NSString stringWithFormat:DESC(@"oolite-ship-library-size-custom"),OOExpand(override)];
    3524             :                         }
    3525             :                 }
    3526             :                 else
    3527             :                 {
    3528             :                         field3 = OOShipLibrarySize(demo_ship);
    3529             :                 }
    3530             : 
    3531             :                 [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:5];
    3532             :         }
    3533             : 
    3534             :         override = [librarySettings oo_stringForKey:kOODemoShipDescription defaultValue:nil];
    3535             :         if (override != nil)
    3536             :         {
    3537             :                 [gui addLongText:OOExpand(override) startingAtRow:descRow align:GUI_ALIGN_LEFT];
    3538             :         }
    3539             : 
    3540             :         
    3541             :         // line 19: ship categories
    3542             :         field1 = [NSString stringWithFormat:@"<-- %@",OOShipLibraryCategoryPlural([[[demo_ships objectAtIndex:((demo_ship_index+[demo_ships count]-1)%[demo_ships count])] objectAtIndex:0] oo_stringForKey:kOODemoShipClass])];
    3543             :         field2 = OOShipLibraryCategoryPlural([[[demo_ships objectAtIndex:demo_ship_index] objectAtIndex:0] oo_stringForKey:kOODemoShipClass]);
    3544             :         field3 = [NSString stringWithFormat:@"%@ -->",OOShipLibraryCategoryPlural([[[demo_ships objectAtIndex:((demo_ship_index+1)%[demo_ships count])] objectAtIndex:0] oo_stringForKey:kOODemoShipClass])];
    3545             :         
    3546             :         [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:19];
    3547             :         [gui setColor:[OOColor greenColor] forRow:19];
    3548             : 
    3549             :         // lines 21-25: ship names
    3550             :         NSArray *subList = [demo_ships objectAtIndex:demo_ship_index];
    3551             :         NSUInteger i,start = demo_ship_subindex - (demo_ship_subindex%5);
    3552             :         NSUInteger end = start + 4;
    3553             :         if (end >= [subList count])
    3554             :         {
    3555             :                 end = [subList count] - 1;
    3556             :         }
    3557             :         OOGUIRow row = 21;
    3558             :         field1 = @"";
    3559             :         field3 = @"";
    3560             :         for (i = start ; i <= end ; i++)
    3561             :         {
    3562             :                 field2 = [[subList objectAtIndex:i] oo_stringForKey:kOODemoShipName];
    3563             :                 [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:row];
    3564             :                 if (i == demo_ship_subindex)
    3565             :                 {
    3566             :                         [gui setColor:[OOColor yellowColor] forRow:row];
    3567             :                 }
    3568             :                 else
    3569             :                 {
    3570             :                         [gui setColor:[OOColor whiteColor] forRow:row];
    3571             :                 }
    3572             :                 row++;
    3573             :         }
    3574             : 
    3575             :         field2 = @"...";
    3576             :         if (start > 0)
    3577             :         {
    3578             :                 [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:20];
    3579             :                 [gui setColor:[OOColor whiteColor] forRow:20];
    3580             :         }
    3581             :         if (end < [subList count]-1)
    3582             :         {
    3583             :                 [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:26];
    3584             :                 [gui setColor:[OOColor whiteColor] forRow:26];
    3585             :         }
    3586             : 
    3587             : }
    3588             : 
    3589             : 
    3590             : - (void) selectIntro2Previous
    3591             : {
    3592             :         demo_stage = DEMO_SHOW_THING;
    3593             :         NSUInteger subcount = [[demo_ships objectAtIndex:demo_ship_index] count];
    3594             :         demo_ship_subindex = (demo_ship_subindex + subcount - 2) % subcount;
    3595             :         demo_stage_time  = universal_time - 1.0;        // force change
    3596             : }
    3597             : 
    3598             : 
    3599             : - (void) selectIntro2PreviousCategory
    3600             : {
    3601             :         demo_stage = DEMO_SHOW_THING;
    3602             :         demo_ship_index = (demo_ship_index + [demo_ships count] - 1) % [demo_ships count];
    3603             :         demo_ship_subindex = [[demo_ships objectAtIndex:demo_ship_index] count] - 1;
    3604             :         demo_stage_time  = universal_time - 1.0;        // force change
    3605             : }
    3606             : 
    3607             : 
    3608             : - (void) selectIntro2NextCategory
    3609             : {
    3610             :         demo_stage = DEMO_SHOW_THING;
    3611             :         demo_ship_index = (demo_ship_index + 1) % [demo_ships count];
    3612             :         demo_ship_subindex = [[demo_ships objectAtIndex:demo_ship_index] count] - 1;
    3613             :         demo_stage_time  = universal_time - 1.0;        // force change
    3614             : }
    3615             : 
    3616             : 
    3617             : - (void) selectIntro2Next
    3618             : {
    3619             :         demo_stage = DEMO_SHOW_THING;
    3620             :         demo_stage_time  = universal_time - 1.0;        // force change
    3621             : }
    3622             : 
    3623             : 
    3624           0 : static BOOL IsCandidateMainStationPredicate(Entity *entity, void *parameter)
    3625             : {
    3626             :         return [entity isStation] && !entity->isExplicitlyNotMainStation;
    3627             : }
    3628             : 
    3629             : 
    3630           0 : static BOOL IsFriendlyStationPredicate(Entity *entity, void *parameter)
    3631             : {
    3632             :         return [entity isStation] && ![(ShipEntity *)entity isHostileTo:parameter];
    3633             : }
    3634             : 
    3635             : 
    3636             : - (StationEntity *) station
    3637             : {
    3638             :         if (cachedSun != nil && cachedStation == nil)
    3639             :         {
    3640             :                 cachedStation = [self findOneEntityMatchingPredicate:IsCandidateMainStationPredicate
    3641             :                                                                                                    parameter:nil];
    3642             :         }
    3643             :         return cachedStation;
    3644             : }
    3645             : 
    3646             : 
    3647             : - (StationEntity *) stationWithRole:(NSString *)role andPosition:(HPVector)position
    3648             : {
    3649             :         if ([role isEqualToString:@""])
    3650             :         {
    3651             :                 return nil;
    3652             :         }
    3653             : 
    3654             :         float range = 1000000; // allow a little variation in position
    3655             : 
    3656             :         NSArray *stations = [self stations];
    3657             :         StationEntity *station = nil;
    3658             :         foreach (station, stations)
    3659             :         {
    3660             :                 if (HPdistance2(position,[station position]) < range)
    3661             :                 {
    3662             :                         if ([[station primaryRole] isEqualToString:role])
    3663             :                         {
    3664             :                                 return station;
    3665             :                         }
    3666             :                 }
    3667             :         }
    3668             :         return nil;
    3669             : }
    3670             : 
    3671             : 
    3672             : - (StationEntity *) stationFriendlyTo:(ShipEntity *) ship
    3673             : {
    3674             :         // In interstellar space we select a random friendly carrier as mainStation.
    3675             :         // No caching: friendly status can change!
    3676             :         return [self findOneEntityMatchingPredicate:IsFriendlyStationPredicate parameter:ship];
    3677             : }
    3678             : 
    3679             : 
    3680             : - (OOPlanetEntity *) planet
    3681             : {
    3682             :         if (cachedPlanet == nil && [allPlanets count] > 0)
    3683             :         {
    3684             :                 cachedPlanet = [allPlanets objectAtIndex:0];
    3685             :         }
    3686             :         return cachedPlanet;
    3687             : }
    3688             : 
    3689             : 
    3690             : - (OOSunEntity *) sun
    3691             : {
    3692             :         if (cachedSun == nil)
    3693             :         {
    3694             :                 cachedSun = [self findOneEntityMatchingPredicate:IsSunPredicate parameter:nil];
    3695             :         }
    3696             :         return cachedSun;
    3697             : }
    3698             : 
    3699             : 
    3700             : - (NSArray *) planets
    3701             : {
    3702             :         return allPlanets;
    3703             : }
    3704             : 
    3705             : 
    3706             : - (NSArray *) stations
    3707             : {
    3708             :         return [allStations allObjects];
    3709             : }
    3710             : 
    3711             : 
    3712             : - (NSArray *) wormholes
    3713             : {
    3714             :         return activeWormholes;
    3715             : }
    3716             : 
    3717             : 
    3718             : - (void) unMagicMainStation
    3719             : {
    3720             :         /*      During the demo screens, the player must remain docked in order for the
    3721             :                 UI to work. This means either enforcing invulnerability or launching
    3722             :                 the player when the station is destroyed even if on the "new game Y/N"
    3723             :                 screen.
    3724             :                 
    3725             :                 The latter is a) weirder and b) harder. If your OXP relies on being
    3726             :                 able to destroy the main station before the game has even started,
    3727             :                 your OXP sucks.
    3728             :         */
    3729             :         OOEntityStatus playerStatus = [PLAYER status];
    3730             :         if (playerStatus == STATUS_START_GAME)  return;
    3731             :         
    3732             :         StationEntity *theStation = [self station];
    3733             :         if (theStation != nil)  theStation->isExplicitlyNotMainStation = YES;
    3734             :         cachedStation = nil;
    3735             : }
    3736             : 
    3737             : 
    3738             : - (void) resetBeacons
    3739             : {
    3740             :         Entity <OOBeaconEntity> *beaconShip = [self firstBeacon], *next = nil;
    3741             :         while (beaconShip)
    3742             :         {
    3743             :                 next = [beaconShip nextBeacon];
    3744             :                 [beaconShip setPrevBeacon:nil];
    3745             :                 [beaconShip setNextBeacon:nil];
    3746             :                 beaconShip = next;
    3747             :         }
    3748             :         
    3749             :         [self setFirstBeacon:nil];
    3750             :         [self setLastBeacon:nil];
    3751             : }
    3752             : 
    3753             : 
    3754             : - (Entity <OOBeaconEntity> *) firstBeacon
    3755             : {
    3756             :         return [_firstBeacon weakRefUnderlyingObject];
    3757             : }
    3758             : 
    3759             : 
    3760           0 : - (void) setFirstBeacon:(Entity <OOBeaconEntity> *)beacon
    3761             : {
    3762             :         if (beacon != [self firstBeacon])
    3763             :         {
    3764             :                 [beacon setPrevBeacon:nil];
    3765             :                 [beacon setNextBeacon:[self firstBeacon]];
    3766             :                 [[self firstBeacon] setPrevBeacon:beacon];
    3767             :                 [_firstBeacon release];
    3768             :                 _firstBeacon = [beacon weakRetain];
    3769             :         }
    3770             : }
    3771             : 
    3772             : 
    3773             : - (Entity <OOBeaconEntity> *) lastBeacon
    3774             : {
    3775             :         return [_lastBeacon weakRefUnderlyingObject];
    3776             : }
    3777             : 
    3778             : 
    3779           0 : - (void) setLastBeacon:(Entity <OOBeaconEntity> *)beacon
    3780             : {
    3781             :         if (beacon != [self lastBeacon])
    3782             :         {
    3783             :                 [beacon setNextBeacon:nil];
    3784             :                 [beacon setPrevBeacon:[self lastBeacon]];
    3785             :                 [[self lastBeacon] setNextBeacon:beacon];
    3786             :                 [_lastBeacon release];
    3787             :                 _lastBeacon = [beacon weakRetain];
    3788             :         }
    3789             : }
    3790             : 
    3791             : 
    3792             : - (void) setNextBeacon:(Entity <OOBeaconEntity> *) beaconShip
    3793             : {
    3794             :         if ([beaconShip isBeacon])
    3795             :         {
    3796             :                 [self setLastBeacon:beaconShip];
    3797             :                 if ([self firstBeacon] == nil)  [self setFirstBeacon:beaconShip];
    3798             :         }
    3799             :         else
    3800             :         {
    3801             :                 OOLog(@"universe.beacon.error", @"***** ERROR: Universe setNextBeacon '%@'. The ship has no beacon code set.", beaconShip);
    3802             :         }
    3803             : }
    3804             : 
    3805             : 
    3806             : - (void) clearBeacon:(Entity <OOBeaconEntity> *) beaconShip
    3807             : {
    3808             :         Entity <OOBeaconEntity>                           *tmp = nil;
    3809             : 
    3810             :         if ([beaconShip isBeacon])
    3811             :         {
    3812             :                 if ([self firstBeacon] == beaconShip)
    3813             :                 {
    3814             :                         tmp = [[beaconShip nextBeacon] nextBeacon];
    3815             :                         [self setFirstBeacon:[beaconShip nextBeacon]];
    3816             :                         [[beaconShip prevBeacon] setNextBeacon:tmp];
    3817             :                 }
    3818             :                 else if ([self lastBeacon] == beaconShip)
    3819             :                 {
    3820             :                         tmp = [[beaconShip prevBeacon] prevBeacon];
    3821             :                         [self setLastBeacon:[beaconShip prevBeacon]];
    3822             :                         [[beaconShip nextBeacon] setPrevBeacon:tmp];
    3823             :                 }
    3824             :                 else
    3825             :                 {
    3826             :                         [[beaconShip nextBeacon] setPrevBeacon:[beaconShip prevBeacon]];
    3827             :                         [[beaconShip prevBeacon] setNextBeacon:[beaconShip nextBeacon]];
    3828             :                 }
    3829             :                 [beaconShip setBeaconCode:nil];
    3830             :         }
    3831             : }
    3832             : 
    3833             : 
    3834             : - (NSDictionary *) currentWaypoints
    3835             : {
    3836             :         return waypoints;
    3837             : }
    3838             : 
    3839             : 
    3840             : - (void) defineWaypoint:(NSDictionary *)definition forKey:(NSString *)key
    3841             : {
    3842             :         OOWaypointEntity *waypoint = nil;
    3843             :         BOOL preserveCompass = NO;
    3844             :         waypoint = [waypoints objectForKey:key];
    3845             :         if (waypoint != nil)
    3846             :         {
    3847             :                 if ([PLAYER compassTarget] == waypoint) 
    3848             :                 {
    3849             :                         preserveCompass = YES;
    3850             :                 }
    3851             :                 [self removeEntity:waypoint];
    3852             :                 [waypoints removeObjectForKey:key];
    3853             :         }
    3854             :         if (definition != nil)
    3855             :         {
    3856             :                 waypoint = [OOWaypointEntity waypointWithDictionary:definition];
    3857             :                 if (waypoint != nil)
    3858             :                 {
    3859             :                         [self addEntity:waypoint];
    3860             :                         [waypoints setObject:waypoint forKey:key];
    3861             :                         if (preserveCompass)
    3862             :                         {
    3863             :                                 [PLAYER setCompassTarget:waypoint];
    3864             :                                 [PLAYER setNextBeacon:waypoint];
    3865             :                         }
    3866             :                 }
    3867             :         }
    3868             : }
    3869             : 
    3870             : 
    3871             : - (GLfloat *) skyClearColor
    3872             : {
    3873             :         return skyClearColor;
    3874             : }
    3875             : 
    3876             : 
    3877             : - (void) setSkyColorRed:(GLfloat)red green:(GLfloat)green blue:(GLfloat)blue alpha:(GLfloat)alpha
    3878             : {
    3879             :         skyClearColor[0] = red;
    3880             :         skyClearColor[1] = green;
    3881             :         skyClearColor[2] = blue;
    3882             :         skyClearColor[3] = alpha;
    3883             :         [self setAirResistanceFactor:alpha];
    3884             : }
    3885             : 
    3886             : 
    3887             : - (BOOL) breakPatternOver
    3888             : {
    3889             :         return (breakPatternCounter == 0);
    3890             : }
    3891             : 
    3892             : 
    3893             : - (BOOL) breakPatternHide
    3894             : {
    3895             :         Entity* player = PLAYER;
    3896             :         return ((breakPatternCounter > 5)||(!player)||([player status] == STATUS_DOCKING));
    3897             : }
    3898             : 
    3899             : 
    3900           0 : #define PROFILE_SHIP_SELECTION 0
    3901             : 
    3902             : 
    3903           0 : - (BOOL) canInstantiateShip:(NSString *)shipKey
    3904             : {
    3905             :         NSDictionary                    *shipInfo = nil;
    3906             :         NSArray                                 *conditions = nil;
    3907             :         NSString  *condition_script = nil;
    3908             :         shipInfo = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey];
    3909             : 
    3910             :         condition_script = [shipInfo oo_stringForKey:@"condition_script"];
    3911             :         if (condition_script != nil)
    3912             :         {
    3913             :                 OOJSScript *condScript = [self getConditionScript:condition_script];
    3914             :                 if (condScript != nil) // should always be non-nil, but just in case
    3915             :                 {
    3916             :                         JSContext                       *context = OOJSAcquireContext();
    3917             :                         BOOL OK;
    3918             :                         JSBool allow_instantiation;
    3919             :                         jsval result;
    3920             :                         jsval args[] = { OOJSValueFromNativeObject(context, shipKey) };
    3921             :                         
    3922             :                         OK = [condScript callMethod:OOJSID("allowSpawnShip")
    3923             :                                                   inContext:context
    3924             :                                           withArguments:args count:sizeof args / sizeof *args
    3925             :                                                          result:&result];
    3926             : 
    3927             :                         if (OK) OK = JS_ValueToBoolean(context, result, &allow_instantiation);
    3928             :                         
    3929             :                         OOJSRelinquishContext(context);
    3930             : 
    3931             :                         if (OK && !allow_instantiation)
    3932             :                         {
    3933             :                                 /* if the script exists, the function exists, the function
    3934             :                                  * returns a bool, and that bool is false, block
    3935             :                                  * instantiation. Otherwise allow it as default */
    3936             :                                 return NO;
    3937             :                         }
    3938             :                 }
    3939             :         }
    3940             : 
    3941             :         conditions = [shipInfo oo_arrayForKey:@"conditions"];
    3942             :         if (conditions == nil)  return YES;
    3943             :         
    3944             :         // Check conditions
    3945             :         return [PLAYER scriptTestConditions:conditions];
    3946             : }
    3947             : 
    3948             : 
    3949             : - (NSString *) randomShipKeyForRoleRespectingConditions:(NSString *)role
    3950             : {
    3951             :         OOJS_PROFILE_ENTER
    3952             :         
    3953             :         OOShipRegistry                  *registry = [OOShipRegistry sharedRegistry];
    3954             :         NSString                                *shipKey = nil;
    3955             :         OOMutableProbabilitySet *pset = nil;
    3956             :         
    3957             : #if PROFILE_SHIP_SELECTION
    3958             :         static unsigned long    profTotal = 0, profSlowPath = 0;
    3959             :         ++profTotal;
    3960             : #endif
    3961             :         
    3962             :         // Select a ship, check conditions and return it if possible.
    3963             :         shipKey = [registry randomShipKeyForRole:role];
    3964             :         if ([self canInstantiateShip:shipKey])  return shipKey;
    3965             :         
    3966             :         /*      If we got here, condition check failed.
    3967             :                 We now need to keep trying until we either find an acceptable ship or
    3968             :                 run out of candidates.
    3969             :                 This is special-cased because it has more overhead than the more
    3970             :                 common conditionless lookup.
    3971             :         */
    3972             :         
    3973             : #if PROFILE_SHIP_SELECTION
    3974             :         ++profSlowPath;
    3975             :         if ((profSlowPath % 10) == 0)   // Only print every tenth slow path, to reduce spamminess.
    3976             :         {
    3977             :                 OOLog(@"shipRegistry.selection.profile", @"Hit slow path in ship selection for role \"%@\", having selected ship \"%@\". Now %lu of %lu on slow path (%f%%).", role, shipKey, profSlowPath, profTotal, ((double)profSlowPath)/((double)profTotal) * 100.0f);
    3978             :         }
    3979             : #endif
    3980             :         
    3981             :         pset = [[[registry probabilitySetForRole:role] mutableCopy] autorelease];
    3982             :         
    3983             :         while ([pset count] > 0)
    3984             :         {
    3985             :                 // Select a ship, check conditions and return it if possible.
    3986             :                 shipKey = [pset randomObject];
    3987             :                 if ([self canInstantiateShip:shipKey])  return shipKey;
    3988             :                 
    3989             :                 // Condition failed -> remove ship from consideration.
    3990             :                 [pset removeObject:shipKey];
    3991             :         }
    3992             :         
    3993             :         // If we got here, some ships existed but all failed conditions test.
    3994             :         return nil;
    3995             :         
    3996             :         OOJS_PROFILE_EXIT
    3997             : }
    3998             : 
    3999             : 
    4000             : - (ShipEntity *) newShipWithRole:(NSString *)role
    4001             : {
    4002             :         OOJS_PROFILE_ENTER
    4003             :         
    4004             :         ShipEntity                              *ship = nil;
    4005             :         NSString                                *shipKey = nil;
    4006             :         NSDictionary                    *shipInfo = nil;
    4007             :         NSString                                *autoAI = nil;
    4008             :         
    4009             :         shipKey = [self randomShipKeyForRoleRespectingConditions:role];
    4010             :         if (shipKey != nil)
    4011             :         {
    4012             :                 ship = [self newShipWithName:shipKey];
    4013             :                 if (ship != nil)
    4014             :                 {
    4015             :                         [ship setPrimaryRole:role];
    4016             :                         
    4017             :                         shipInfo = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey];
    4018             :                         if ([shipInfo oo_fuzzyBooleanForKey:@"auto_ai" defaultValue:YES])
    4019             :                         {
    4020             :                                 // Set AI based on role
    4021             :                                 autoAI = [self defaultAIForRole:role];
    4022             :                                 if (autoAI != nil)
    4023             :                                 {
    4024             :                                         [ship setAITo:autoAI];
    4025             :                                         // Nikos 20090604
    4026             :                                         // Pirate, trader or police with auto_ai? Follow populator rules for them.
    4027             :                                         if ([role isEqualToString:@"pirate"]) [ship setBounty:20 + randf() * 50 withReason:kOOLegalStatusReasonSetup];
    4028             :                                         if ([role isEqualToString:@"trader"]) [ship setBounty:0 withReason:kOOLegalStatusReasonSetup];
    4029             :                                         if ([role isEqualToString:@"police"]) [ship setScanClass:CLASS_POLICE];
    4030             :                                         if ([role isEqualToString:@"interceptor"])
    4031             :                                         {
    4032             :                                                 [ship setScanClass: CLASS_POLICE];
    4033             :                                                 [ship setPrimaryRole:@"police"]; // to make sure interceptors get the correct pilot later on.
    4034             :                                         }
    4035             :                                 }
    4036             :                                 if ([role isEqualToString:@"thargoid"]) [ship setScanClass: CLASS_THARGOID]; // thargoids are not on the autoAIMap
    4037             :                         }
    4038             :                 }
    4039             :         }
    4040             :         
    4041             :         return ship;
    4042             :         
    4043             :         OOJS_PROFILE_EXIT
    4044             : }
    4045             : 
    4046             : 
    4047             : - (OOVisualEffectEntity *) newVisualEffectWithName:(NSString *)effectKey
    4048             : {
    4049             :         OOJS_PROFILE_ENTER
    4050             :         
    4051             :         NSDictionary                    *effectDict = nil;
    4052             :         OOVisualEffectEntity    *effect = nil;
    4053             :         
    4054             :         effectDict = [[OOShipRegistry sharedRegistry] effectInfoForKey:effectKey];
    4055             :         if (effectDict == nil)  return nil;
    4056             :         
    4057             :         @try
    4058             :         {
    4059             :                 effect = [[OOVisualEffectEntity alloc] initWithKey:effectKey definition:effectDict];
    4060             :         }
    4061             :         @catch (NSException *exception)
    4062             :         {
    4063             :                 if ([[exception name] isEqual:OOLITE_EXCEPTION_DATA_NOT_FOUND])
    4064             :                 {
    4065             :                         OOLog(kOOLogException, @"***** Oolite Exception : '%@' in [Universe newVisualEffectWithName: %@ ] *****", [exception reason], effectKey);
    4066             :                 }
    4067             :                 else  @throw exception;
    4068             :         }
    4069             :         
    4070             :         return effect;
    4071             :         
    4072             :         OOJS_PROFILE_EXIT
    4073             : }
    4074             : 
    4075             : 
    4076             : - (ShipEntity *) newSubentityWithName:(NSString *)shipKey andScaleFactor:(float)scale
    4077             : {
    4078             :         return [self newShipWithName:shipKey usePlayerProxy:NO isSubentity:YES andScaleFactor:scale];
    4079             : }
    4080             : 
    4081             : 
    4082             : - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy
    4083             : {
    4084             :         return [self newShipWithName:shipKey usePlayerProxy:usePlayerProxy isSubentity:NO];
    4085             : }
    4086             : 
    4087             : - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy isSubentity:(BOOL)isSubentity
    4088             : {
    4089             :         return [self newShipWithName:shipKey usePlayerProxy:usePlayerProxy isSubentity:isSubentity andScaleFactor:1.0f];
    4090             : }
    4091             : 
    4092             : - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy isSubentity:(BOOL)isSubentity andScaleFactor:(float)scale
    4093             : {
    4094             :         OOJS_PROFILE_ENTER
    4095             :         
    4096             :         NSDictionary    *shipDict = nil;
    4097             :         ShipEntity              *ship = nil;
    4098             :         
    4099             :         shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey];
    4100             :         if (shipDict == nil)  return nil;
    4101             :         
    4102             :         volatile Class shipClass = nil;
    4103             :         if (isSubentity)
    4104             :         {
    4105             :                 shipClass = [ShipEntity class];
    4106             :         }
    4107             :         else
    4108             :         {
    4109             :                 shipClass = [self shipClassForShipDictionary:shipDict];
    4110             :                 if (usePlayerProxy && shipClass == [ShipEntity class])
    4111             :                 {
    4112             :                         shipClass = [ProxyPlayerEntity class];
    4113             :                 }
    4114             :         }
    4115             :         
    4116             :         @try
    4117             :         {
    4118             :                 if (scale != 1.0f)
    4119             :                 {
    4120             :                         NSMutableDictionary *mShipDict = [shipDict mutableCopy];
    4121             :                         [mShipDict setObject:[NSNumber numberWithFloat:scale] forKey:@"model_scale_factor"];
    4122             :                         shipDict = [NSDictionary dictionaryWithDictionary:mShipDict];
    4123             :                         [mShipDict release];
    4124             :                 }
    4125             :                 ship = [[shipClass alloc] initWithKey:shipKey definition:shipDict];
    4126             :         }
    4127             :         @catch (NSException *exception)
    4128             :         {
    4129             :                 if ([[exception name] isEqual:OOLITE_EXCEPTION_DATA_NOT_FOUND])
    4130             :                 {
    4131             :                         OOLog(kOOLogException, @"***** Oolite Exception : '%@' in [Universe newShipWithName: %@ ] *****", [exception reason], shipKey);
    4132             :                 }
    4133             :                 else  @throw exception;
    4134             :         }
    4135             :         
    4136             :         // Set primary role to same as ship name, if ship name is also a role.
    4137             :         // Otherwise, if caller doesn't set a role, one will be selected randomly.
    4138             :         if ([ship hasRole:shipKey])  [ship setPrimaryRole:shipKey];
    4139             :         
    4140             :         return ship;
    4141             :         
    4142             :         OOJS_PROFILE_EXIT
    4143             : }
    4144             : 
    4145             : 
    4146             : - (DockEntity *) newDockWithName:(NSString *)shipDataKey andScaleFactor:(float)scale
    4147             : {
    4148             :         OOJS_PROFILE_ENTER
    4149             :         
    4150             :         NSDictionary    *shipDict = nil;
    4151             :         DockEntity              *dock = nil;
    4152             :         
    4153             :         shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipDataKey];
    4154             :         if (shipDict == nil)  return nil;
    4155             :         
    4156             :         @try
    4157             :         {
    4158             :                 if (scale != 1.0f)
    4159             :                 {
    4160             :                         NSMutableDictionary *mShipDict = [shipDict mutableCopy];
    4161             :                         [mShipDict setObject:[NSNumber numberWithFloat:scale] forKey:@"model_scale_factor"];
    4162             :                         shipDict = [NSDictionary dictionaryWithDictionary:mShipDict];
    4163             :                         [mShipDict release];
    4164             :                 }
    4165             :                 dock = [[DockEntity alloc] initWithKey:shipDataKey definition:shipDict];
    4166             :         }
    4167             :         @catch (NSException *exception)
    4168             :         {
    4169             :                 if ([[exception name] isEqual:OOLITE_EXCEPTION_DATA_NOT_FOUND])
    4170             :                 {
    4171             :                         OOLog(kOOLogException, @"***** Oolite Exception : '%@' in [Universe newDockWithName: %@ ] *****", [exception reason], shipDataKey);
    4172             :                 }
    4173             :                 else  @throw exception;
    4174             :         }
    4175             :         
    4176             :         // Set primary role to same as name, if ship name is also a role.
    4177             :         // Otherwise, if caller doesn't set a role, one will be selected randomly.
    4178             :         if ([dock hasRole:shipDataKey])  [dock setPrimaryRole:shipDataKey];
    4179             :         
    4180             :         return dock;
    4181             :         
    4182             :         OOJS_PROFILE_EXIT
    4183             : }
    4184             : 
    4185             : 
    4186             : - (ShipEntity *) newShipWithName:(NSString *)shipKey
    4187             : {
    4188             :         return [self newShipWithName:shipKey usePlayerProxy:NO];
    4189             : }
    4190             : 
    4191             : 
    4192             : - (Class) shipClassForShipDictionary:(NSDictionary *)dict
    4193             : {
    4194             :         OOJS_PROFILE_ENTER
    4195             :         
    4196             :         if (dict == nil)  return Nil;
    4197             :         
    4198             :         BOOL            isStation = NO;
    4199             :         NSString        *shipRoles = [dict oo_stringForKey:@"roles"];
    4200             :         
    4201             :         if (shipRoles != nil)
    4202             :         {
    4203             :                 isStation = [shipRoles rangeOfString:@"station"].location != NSNotFound ||
    4204             :                 [shipRoles rangeOfString:@"carrier"].location != NSNotFound;
    4205             :         }
    4206             :         
    4207             :         // Note priority here: is_carrier overrides isCarrier which overrides roles.
    4208             :         isStation = [dict oo_boolForKey:@"isCarrier" defaultValue:isStation];
    4209             :         isStation = [dict oo_boolForKey:@"is_carrier" defaultValue:isStation];
    4210             :         
    4211             : 
    4212             :         return isStation ? [StationEntity class] : [ShipEntity class];
    4213             :         
    4214             :         OOJS_PROFILE_EXIT
    4215             : }
    4216             : 
    4217             : 
    4218             : - (NSString *)defaultAIForRole:(NSString *)role
    4219             : {
    4220             :         return [autoAIMap oo_stringForKey:role];
    4221             : }
    4222             : 
    4223             : 
    4224             : - (OOCargoQuantity) maxCargoForShip:(NSString *) desc
    4225             : {
    4226             :         return [[[OOShipRegistry sharedRegistry] shipInfoForKey:desc] oo_unsignedIntForKey:@"max_cargo" defaultValue:0];
    4227             : }
    4228             : 
    4229             : /*
    4230             :  * Price for an item expressed in 10ths of credits (divide by 10 to get credits)
    4231             :  */
    4232             : - (OOCreditsQuantity) getEquipmentPriceForKey:(NSString *)eq_key
    4233             : {
    4234             :         NSArray *itemData;
    4235             :         foreach (itemData, equipmentData)
    4236             :         {
    4237             :                 NSString *itemType = [itemData oo_stringAtIndex:EQUIPMENT_KEY_INDEX];
    4238             :                 
    4239             :                 if ([itemType isEqual:eq_key])
    4240             :                 {
    4241             :                         return [itemData oo_unsignedLongLongAtIndex:EQUIPMENT_PRICE_INDEX];
    4242             :                 }
    4243             :         }
    4244             :         return 0;
    4245             : }
    4246             : 
    4247             : 
    4248             : - (OOCommodities *) commodities
    4249             : {
    4250             :         return commodities;
    4251             : }
    4252             : 
    4253             : 
    4254             : /* Converts template cargo pods to real ones */
    4255             : - (ShipEntity *) reifyCargoPod:(ShipEntity *)cargoObj
    4256             : {
    4257             :         if ([cargoObj isTemplateCargoPod])
    4258             :         {
    4259             :                 return [UNIVERSE cargoPodFromTemplate:cargoObj];
    4260             :         }
    4261             :         else
    4262             :         {
    4263             :                 return cargoObj;
    4264             :         }
    4265             : }
    4266             : 
    4267             : 
    4268             : - (ShipEntity *) cargoPodFromTemplate:(ShipEntity *)cargoObj
    4269             : {
    4270             :         ShipEntity *container = nil;
    4271             :         // this is a template container, so we need to make a real one
    4272             :         OOCommodityType co_type = [cargoObj commodityType];
    4273             :         OOCargoQuantity co_amount = [UNIVERSE getRandomAmountOfCommodity:co_type];
    4274             :         if (randf() < 0.5) // stops OXP monopolising pods for commodities
    4275             :         {
    4276             :                 container = [UNIVERSE newShipWithRole:co_type]; // newShipWithRole returns retained object
    4277             :         }
    4278             :         if (container == nil)
    4279             :         {
    4280             :                 container = [UNIVERSE newShipWithRole:@"cargopod"]; 
    4281             :         }
    4282             :         [container setCommodity:co_type andAmount:co_amount];
    4283             :         return [container autorelease];
    4284             : }
    4285             : 
    4286             : 
    4287             : - (NSArray *) getContainersOfGoods:(OOCargoQuantity)how_many scarce:(BOOL)scarce legal:(BOOL)legal
    4288             : {
    4289             :         /*      build list of goods allocating 0..100 for each based on how much of
    4290             :                 each quantity there is. Use a ratio of n x 100/64 for plentiful goods;
    4291             :                 reverse the probabilities for scarce goods.
    4292             :         */
    4293             :         NSMutableArray  *accumulator = [NSMutableArray arrayWithCapacity:how_many];
    4294             :         NSUInteger              i=0, commodityCount = [commodityMarket count];
    4295             :         OOCargoQuantity quantities[commodityCount];
    4296             :         OOCargoQuantity total_quantity = 0;
    4297             : 
    4298             :         NSArray                 *goodsKeys = [commodityMarket goods];
    4299             :         NSString                *goodsKey = nil;
    4300             : 
    4301             :         foreach (goodsKey, goodsKeys)
    4302             :         {
    4303             :                 OOCargoQuantity q = [commodityMarket quantityForGood:goodsKey];
    4304             :                 if (scarce)
    4305             :                 {
    4306             :                         if (q < 64)  q = 64 - q;
    4307             :                         else  q = 0;
    4308             :                 }
    4309             :                 // legal YES restricts (almost) only to legal goods
    4310             :                 // legal NO allows illegal goods, but not necessarily a full hold
    4311             :                 if (legal && [commodityMarket exportLegalityForGood:goodsKey] > 0)
    4312             :                 {
    4313             :                         q &= 1; // keep a very small chance, sometimes
    4314             :                 }
    4315             :                 if (q > 64) q = 64;
    4316             :                 q *= 100;   q/= 64;
    4317             :                 quantities[i++] = q;
    4318             :                 total_quantity += q;
    4319             :         }
    4320             :         // quantities is now used to determine which good get into the containers
    4321             :         for (i = 0; i < how_many; i++)
    4322             :         {
    4323             :                 NSUInteger co_type = 0;
    4324             :                 
    4325             :                 int qr=0;
    4326             :                 if(total_quantity)
    4327             :                 {
    4328             :                         qr = 1+(Ranrot() % total_quantity);
    4329             :                         co_type = 0;
    4330             :                         while (qr > 0)
    4331             :                         {
    4332             :                                 NSAssert((NSUInteger)co_type < commodityCount, @"Commodity type index out of range.");
    4333             :                                 qr -= quantities[co_type++];
    4334             :                         }
    4335             :                         co_type--;
    4336             :                 }
    4337             : 
    4338             :                 ShipEntity *container = [cargoPods objectForKey:[goodsKeys oo_stringAtIndex:co_type]];
    4339             :                 
    4340             :                 if (container != nil)
    4341             :                 {
    4342             :                         [accumulator addObject:container];
    4343             :                 }
    4344             :                 else
    4345             :                 {
    4346             :                         OOLog(@"universe.createContainer.failed", @"***** ERROR: failed to find a container to fill with %@ (%ld).", [goodsKeys oo_stringAtIndex:co_type], co_type);
    4347             : 
    4348             :                 }
    4349             :         }
    4350             :         return [NSArray arrayWithArray:accumulator];    
    4351             : }
    4352             : 
    4353             : 
    4354             : - (NSArray *) getContainersOfCommodity:(OOCommodityType)commodity_name :(OOCargoQuantity)how_much
    4355             : {
    4356             :         NSMutableArray  *accumulator = [NSMutableArray arrayWithCapacity:how_much];
    4357             :         if (![commodities goodDefined:commodity_name])  
    4358             :         {
    4359             :                 return [NSArray array]; // empty array
    4360             :         }
    4361             :         
    4362             :         ShipEntity *container = [cargoPods objectForKey:commodity_name];
    4363             :         while (how_much > 0)
    4364             :         {
    4365             :                 if (container)
    4366             :                 {
    4367             :                         [accumulator addObject:container];
    4368             :                 }
    4369             :                 else
    4370             :                 {
    4371             :                         OOLog(@"universe.createContainer.failed", @"***** ERROR: failed to find a container to fill with %@", commodity_name);
    4372             :                 }
    4373             : 
    4374             :                 how_much--;
    4375             :         }
    4376             :         return [NSArray arrayWithArray:accumulator];    
    4377             : }
    4378             : 
    4379             : 
    4380             : - (void) fillCargopodWithRandomCargo:(ShipEntity *)cargopod
    4381             : {
    4382             :         if (cargopod == nil || ![cargopod hasRole:@"cargopod"] || [cargopod cargoType] == CARGO_SCRIPTED_ITEM)  return;
    4383             :         
    4384             :         if ([cargopod commodityType] == nil || ![cargopod commodityAmount])
    4385             :         {
    4386             :                 NSString *aCommodity = [self getRandomCommodity];
    4387             :                 OOCargoQuantity aQuantity = [self getRandomAmountOfCommodity:aCommodity];
    4388             :                 [cargopod setCommodity:aCommodity andAmount:aQuantity];         
    4389             :         }
    4390             : }
    4391             : 
    4392             : 
    4393             : - (NSString *) getRandomCommodity
    4394             : {
    4395             :         return [commodities getRandomCommodity];
    4396             : }
    4397             : 
    4398             : 
    4399             : - (OOCargoQuantity) getRandomAmountOfCommodity:(OOCommodityType)co_type
    4400             : {
    4401             :         OOMassUnit              units;
    4402             :         
    4403             :         if (co_type == nil)  {
    4404             :                 return 0;
    4405             :         }
    4406             :         
    4407             :         units = [commodities massUnitForGood:co_type];
    4408             :         switch (units)
    4409             :         {
    4410             :                 case 0 :        // TONNES
    4411             :                         return 1;
    4412             :                 case 1 :        // KILOGRAMS
    4413             :                         return 1 + (Ranrot() % 6) + (Ranrot() % 6) + (Ranrot() % 6);
    4414             :                 case 2 :        // GRAMS
    4415             :                         return 4 + (Ranrot() % 16) + (Ranrot() % 11) + (Ranrot() % 6);
    4416             :         }
    4417             :         OOLog(@"universe.commodityAmount.warning",@"Commodity %@ has an unrecognised mass unit, assuming tonnes",co_type);
    4418             :         return 1;
    4419             : }
    4420             : 
    4421             : 
    4422             : - (NSDictionary *)commodityDataForType:(OOCommodityType)type
    4423             : {
    4424             :         return [commodityMarket definitionForGood:type];
    4425             : }
    4426             : 
    4427             : 
    4428             : - (NSString *) displayNameForCommodity:(OOCommodityType)co_type
    4429             : {
    4430             :         return [commodityMarket nameForGood:co_type];
    4431             : }
    4432             : 
    4433             : 
    4434             : - (NSString *) describeCommodity:(OOCommodityType)co_type amount:(OOCargoQuantity)co_amount
    4435             : {
    4436             :         int                             units;
    4437             :         NSString                *unitDesc = nil, *typeDesc = nil;
    4438             :         NSDictionary    *commodity = [self commodityDataForType:co_type];
    4439             :         
    4440             :         if (commodity == nil) return @"";
    4441             :         
    4442             :         units = [commodityMarket massUnitForGood:co_type];
    4443             :         if (co_amount == 1)
    4444             :         {
    4445             :                 switch (units)
    4446             :                 {
    4447             :                         case UNITS_KILOGRAMS :  // KILOGRAM
    4448             :                                 unitDesc = DESC(@"cargo-kilogram");
    4449             :                                 break;
    4450             :                         case UNITS_GRAMS :      // GRAM
    4451             :                                 unitDesc = DESC(@"cargo-gram");
    4452             :                                 break;
    4453             :                         case UNITS_TONS :       // TONNE
    4454             :                         default :
    4455             :                                 unitDesc = DESC(@"cargo-ton");
    4456             :                                 break;
    4457             :                 }
    4458             :         }
    4459             :         else
    4460             :         {
    4461             :                 switch (units)
    4462             :                 {
    4463             :                         case UNITS_KILOGRAMS :  // KILOGRAMS
    4464             :                                 unitDesc = DESC(@"cargo-kilograms");
    4465             :                                 break;
    4466             :                         case UNITS_GRAMS :      // GRAMS
    4467             :                                 unitDesc = DESC(@"cargo-grams");
    4468             :                                 break;
    4469             :                         case UNITS_TONS :       // TONNES
    4470             :                         default :
    4471             :                                 unitDesc = DESC(@"cargo-tons");
    4472             :                                 break;
    4473             :                 }
    4474             :         }
    4475             :         
    4476             :         typeDesc = [commodityMarket nameForGood:co_type];
    4477             :         
    4478             :         return [NSString stringWithFormat:@"%d %@ %@",co_amount, unitDesc, typeDesc];
    4479             : }
    4480             : 
    4481             : ////////////////////////////////////////////////////
    4482             : 
    4483             : - (void) setGameView:(MyOpenGLView *)view
    4484             : {
    4485             :         [gameView release];
    4486             :         gameView = [view retain];
    4487             : }
    4488             : 
    4489             : 
    4490             : - (MyOpenGLView *) gameView
    4491             : {
    4492             :         return gameView;
    4493             : }
    4494             : 
    4495             : 
    4496             : - (GameController *) gameController
    4497             : {
    4498             :         return [[self gameView] gameController];
    4499             : }
    4500             : 
    4501             : 
    4502             : - (NSDictionary *) gameSettings
    4503             : {
    4504             : #if OOLITE_SDL
    4505             :         NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:12];
    4506             : #else
    4507             :         NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:10];
    4508             : #endif
    4509             :         
    4510             :         [result oo_setInteger:[PLAYER isSpeechOn] forKey:@"speechOn"];
    4511             :         [result oo_setBool:autoSave forKey:@"autosave"];
    4512             :         [result oo_setBool:wireframeGraphics forKey:@"wireframeGraphics"];
    4513             :         [result oo_setBool:doProcedurallyTexturedPlanets forKey:@"procedurallyTexturedPlanets"];
    4514             : #if OOLITE_SDL
    4515             :         [result oo_setFloat:[gameView gammaValue] forKey:@"gammaValue"];
    4516             : #endif
    4517             : 
    4518             :         [result oo_setFloat:[gameView fov:NO] forKey:@"fovValue"];
    4519             : 
    4520             : #if OOLITE_WINDOWS
    4521             :         if ([gameView hdrOutput])
    4522             :         {
    4523             :                 [result oo_setFloat:[gameView hdrMaxBrightness] forKey:@"hdr-max-brightness"];
    4524             :                 [result oo_setFloat:[gameView hdrPaperWhiteBrightness] forKey:@"hdr-paperwhite-brightness"];
    4525             :                 [result setObject:OOStringFromHDRToneMapper([gameView hdrToneMapper]) forKey:@"hdr-tone-mapper"];
    4526             :         }
    4527             : #endif
    4528             : 
    4529             :         [result setObject:OOStringFromSDRToneMapper([gameView sdrToneMapper]) forKey:@"sdr-tone-mapper"];
    4530             :         
    4531             :         [result setObject:OOStringFromGraphicsDetail([self detailLevel]) forKey:@"detailLevel"];
    4532             :         
    4533             :         NSString *desc = @"UNDEFINED";
    4534             :         switch ([[OOMusicController sharedController] mode])
    4535             :         {
    4536             :                 case kOOMusicOff:               desc = @"MUSIC_OFF"; break;
    4537             :                 case kOOMusicOn:                desc = @"MUSIC_ON"; break;
    4538             :                 case kOOMusicITunes:    desc = @"MUSIC_ITUNES"; break;
    4539             :         }
    4540             :         [result setObject:desc forKey:@"musicMode"];
    4541             :         
    4542             :         NSDictionary *gameWindow = [NSDictionary dictionaryWithObjectsAndKeys:
    4543             :                                                 [NSNumber numberWithFloat:[gameView viewSize].width], @"width",
    4544             :                                                 [NSNumber numberWithFloat:[gameView viewSize].height], @"height",
    4545             :                                                 [NSNumber numberWithBool:[[self gameController] inFullScreenMode]], @"fullScreen",
    4546             :                                                 nil];
    4547             :         [result setObject:gameWindow forKey:@"gameWindow"];
    4548             :         
    4549             :         [result setObject:[PLAYER keyConfig] forKey:@"keyConfig"];
    4550             : 
    4551             :         return [[result copy] autorelease];
    4552             : }
    4553             : 
    4554             : 
    4555             : - (void) useGUILightSource:(BOOL)GUILight
    4556             : {
    4557             :         if (GUILight != demo_light_on)
    4558             :         {
    4559             :                 if (![self useShaders])
    4560             :                 {
    4561             :                         if (GUILight) 
    4562             :                         {
    4563             :                                 OOGL(glEnable(GL_LIGHT0));
    4564             :                                 OOGL(glDisable(GL_LIGHT1));
    4565             :                         }
    4566             :                         else
    4567             :                         {
    4568             :                                 OOGL(glEnable(GL_LIGHT1));
    4569             :                                 OOGL(glDisable(GL_LIGHT0));
    4570             :                         }
    4571             :                 }
    4572             :                 // There should be nothing to do for shaders, they use the same (always on) light source
    4573             :                 // both in flight & in gui mode. According to the standard, shaders should treat lights as
    4574             :                 // always enabled. At least one non-standard shader implementation (windows' X3100 Intel
    4575             :                 // core with GM965 chipset and version 6.14.10.4990 driver) does _not_ use glDisabled lights,
    4576             :                 // making the following line necessary.
    4577             :                 
    4578             :                 else OOGL(glEnable(GL_LIGHT1)); // make sure we have a light, even with shaders (!)
    4579             :                 
    4580             :                 demo_light_on = GUILight;
    4581             :         }
    4582             : }
    4583             : 
    4584             : 
    4585           0 : - (void) lightForEntity:(BOOL)isLit
    4586             : {
    4587             :         if (isLit != object_light_on)
    4588             :         {
    4589             :                 if ([self useShaders])
    4590             :                 {
    4591             :                         if (isLit)
    4592             :                         {
    4593             :                                 OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_diffuse));
    4594             :                                 OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_specular));
    4595             :                         }
    4596             :                         else
    4597             :                         {
    4598             :                                 OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_off));
    4599             :                                 OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_off));
    4600             :                         }
    4601             :                 }
    4602             :                 else
    4603             :                 {
    4604             :                         if (!demo_light_on)
    4605             :                         {
    4606             :                                 if (isLit) OOGL(glEnable(GL_LIGHT1));
    4607             :                                 else OOGL(glDisable(GL_LIGHT1));
    4608             :                         }
    4609             :                         else
    4610             :                         {
    4611             :                                 // If we're in demo/GUI mode we should always have a lit object.
    4612             :                                 OOGL(glEnable(GL_LIGHT0));
    4613             :                                 
    4614             :                                 // Redundant, see above.
    4615             :                                 //if (isLit)  OOGL(glEnable(GL_LIGHT0));
    4616             :                                 //else  OOGL(glDisable(GL_LIGHT0));
    4617             :                         }
    4618             :                 }
    4619             :                 
    4620             :                 object_light_on = isLit;
    4621             :         }
    4622             : }
    4623             : 
    4624             : 
    4625             : // global rotation matrix definitions
    4626           0 : static const OOMatrix   fwd_matrix =
    4627             :                                                 {{
    4628             :                                                         { 1.0f,  0.0f,  0.0f,  0.0f },
    4629             :                                                         { 0.0f,  1.0f,  0.0f,  0.0f },
    4630             :                                                         { 0.0f,  0.0f,  1.0f,  0.0f },
    4631             :                                                         { 0.0f,  0.0f,  0.0f,  1.0f }
    4632             :                                                 }};
    4633           0 : static const OOMatrix   aft_matrix =
    4634             :                                                 {{
    4635             :                                                         {-1.0f,  0.0f,  0.0f,  0.0f },
    4636             :                                                         { 0.0f,  1.0f,  0.0f,  0.0f },
    4637             :                                                         { 0.0f,  0.0f, -1.0f,  0.0f },
    4638             :                                                         { 0.0f,  0.0f,  0.0f,  1.0f }
    4639             :                                                 }};
    4640           0 : static const OOMatrix   port_matrix =
    4641             :                                                 {{
    4642             :                                                         { 0.0f,  0.0f, -1.0f,  0.0f },
    4643             :                                                         { 0.0f,  1.0f,  0.0f,  0.0f },
    4644             :                                                         { 1.0f,  0.0f,  0.0f,  0.0f },
    4645             :                                                         { 0.0f,  0.0f,  0.0f,  1.0f }
    4646             :                                                 }};
    4647           0 : static const OOMatrix   starboard_matrix =
    4648             :                                                 {{
    4649             :                                                         { 0.0f,  0.0f,  1.0f,  0.0f },
    4650             :                                                         { 0.0f,  1.0f,  0.0f,  0.0f },
    4651             :                                                         {-1.0f,  0.0f,  0.0f,  0.0f },
    4652             :                                                         { 0.0f,  0.0f,  0.0f,  1.0f }
    4653             :                                                 }};
    4654             : 
    4655             : 
    4656           0 : - (void) getActiveViewMatrix:(OOMatrix *)outMatrix forwardVector:(Vector *)outForward upVector:(Vector *)outUp
    4657             : {
    4658             :         assert(outMatrix != NULL && outForward != NULL && outUp != NULL);
    4659             :         
    4660             :         PlayerEntity                    *player = nil;
    4661             :         
    4662             :         switch (viewDirection)
    4663             :         {
    4664             :                 case VIEW_AFT:
    4665             :                         *outMatrix = aft_matrix;
    4666             :                         *outForward = vector_flip(kBasisZVector);
    4667             :                         *outUp = kBasisYVector;
    4668             :                         return;
    4669             :                         
    4670             :                 case VIEW_PORT:
    4671             :                         *outMatrix = port_matrix;
    4672             :                         *outForward = vector_flip(kBasisXVector);
    4673             :                         *outUp = kBasisYVector;
    4674             :                         return;
    4675             :                         
    4676             :                 case VIEW_STARBOARD:
    4677             :                         *outMatrix = starboard_matrix;
    4678             :                         *outForward = kBasisXVector;
    4679             :                         *outUp = kBasisYVector;
    4680             :                         return;
    4681             :                         
    4682             :                 case VIEW_CUSTOM:
    4683             :                         player = PLAYER;
    4684             :                         *outMatrix = [player customViewMatrix];
    4685             :                         *outForward = [player customViewForwardVector];
    4686             :                         *outUp = [player customViewUpVector];
    4687             :                         return;
    4688             :                         
    4689             :                 case VIEW_FORWARD:
    4690             :                 case VIEW_NONE:
    4691             :                 case VIEW_GUI_DISPLAY:
    4692             :                 case VIEW_BREAK_PATTERN:
    4693             :                         ;
    4694             :         }
    4695             :         
    4696             :         *outMatrix = fwd_matrix;
    4697             :         *outForward = kBasisZVector;
    4698             :         *outUp = kBasisYVector;
    4699             : }
    4700             : 
    4701             : 
    4702           0 : - (OOMatrix) activeViewMatrix
    4703             : {
    4704             :         OOMatrix                        m;
    4705             :         Vector                          f, u;
    4706             :         
    4707             :         [self getActiveViewMatrix:&m forwardVector:&f upVector:&u];
    4708             :         return m;
    4709             : }
    4710             : 
    4711             : 
    4712             : /* Code adapted from http://www.crownandcutlass.com/features/technicaldetails/frustum.html
    4713             :  * Original license is: "This page and its contents are Copyright 2000 by Mark Morley
    4714             :  * Unless otherwise noted, you may use any and all code examples provided herein in any way you want."
    4715             : */
    4716             : 
    4717             : - (void) defineFrustum
    4718             : {
    4719             :         OOMatrix clip;
    4720             :         GLfloat   rt;
    4721             :         
    4722             :         clip = OOGLGetModelViewProjection();
    4723             :         
    4724             :         /* Extract the numbers for the RIGHT plane */
    4725             :         frustum[0][0] = clip.m[0][3] - clip.m[0][0];
    4726             :         frustum[0][1] = clip.m[1][3] - clip.m[1][0];
    4727             :         frustum[0][2] = clip.m[2][3] - clip.m[2][0];
    4728             :         frustum[0][3] = clip.m[3][3] - clip.m[3][0];
    4729             :         
    4730             :         /* Normalize the result */
    4731             :         rt = 1.0f / sqrt(frustum[0][0] * frustum[0][0] + frustum[0][1] * frustum[0][1] + frustum[0][2] * frustum[0][2]);
    4732             :         frustum[0][0] *= rt;
    4733             :         frustum[0][1] *= rt;
    4734             :         frustum[0][2] *= rt;
    4735             :         frustum[0][3] *= rt;
    4736             :         
    4737             :         /* Extract the numbers for the LEFT plane */
    4738             :         frustum[1][0] = clip.m[0][3] + clip.m[0][0];
    4739             :         frustum[1][1] = clip.m[1][3] + clip.m[1][0];
    4740             :         frustum[1][2] = clip.m[2][3] + clip.m[2][0];
    4741             :         frustum[1][3] = clip.m[3][3] + clip.m[3][0];
    4742             :         
    4743             :         /* Normalize the result */
    4744             :         rt = 1.0f / sqrt(frustum[1][0] * frustum[1][0] + frustum[1][1] * frustum[1][1] + frustum[1][2] * frustum[1][2]);
    4745             :         frustum[1][0] *= rt;
    4746             :         frustum[1][1] *= rt;
    4747             :         frustum[1][2] *= rt;
    4748             :         frustum[1][3] *= rt;
    4749             : 
    4750             :         /* Extract the BOTTOM plane */
    4751             :         frustum[2][0] = clip.m[0][3] + clip.m[0][1];
    4752             :         frustum[2][1] = clip.m[1][3] + clip.m[1][1];
    4753             :         frustum[2][2] = clip.m[2][3] + clip.m[2][1];
    4754             :         frustum[2][3] = clip.m[3][3] + clip.m[3][1];
    4755             : 
    4756             :         /* Normalize the result */
    4757             :         rt = 1.0 / sqrt(frustum[2][0] * frustum[2][0] + frustum[2][1] * frustum[2][1] + frustum[2][2] * frustum[2][2]);
    4758             :         frustum[2][0] *= rt;
    4759             :         frustum[2][1] *= rt;
    4760             :         frustum[2][2] *= rt;
    4761             :         frustum[2][3] *= rt;
    4762             : 
    4763             :         /* Extract the TOP plane */
    4764             :         frustum[3][0] = clip.m[0][3] - clip.m[0][1];
    4765             :         frustum[3][1] = clip.m[1][3] - clip.m[1][1];
    4766             :         frustum[3][2] = clip.m[2][3] - clip.m[2][1];
    4767             :         frustum[3][3] = clip.m[3][3] - clip.m[3][1];
    4768             : 
    4769             :         /* Normalize the result */
    4770             :         rt = 1.0 / sqrt(frustum[3][0] * frustum[3][0] + frustum[3][1] * frustum[3][1] + frustum[3][2] * frustum[3][2]);
    4771             :         frustum[3][0] *= rt;
    4772             :         frustum[3][1] *= rt;
    4773             :         frustum[3][2] *= rt;
    4774             :         frustum[3][3] *= rt;
    4775             : 
    4776             :         /* Extract the FAR plane */
    4777             :         frustum[4][0] = clip.m[0][3] - clip.m[0][2];
    4778             :         frustum[4][1] = clip.m[1][3] - clip.m[1][2];
    4779             :         frustum[4][2] = clip.m[2][3] - clip.m[2][2];
    4780             :         frustum[4][3] = clip.m[3][3] - clip.m[3][2];
    4781             : 
    4782             :         /* Normalize the result */
    4783             :         rt = sqrt(frustum[4][0] * frustum[4][0] + frustum[4][1] * frustum[4][1] + frustum[4][2] * frustum[4][2]);
    4784             :         frustum[4][0] *= rt;
    4785             :         frustum[4][1] *= rt;
    4786             :         frustum[4][2] *= rt;
    4787             :         frustum[4][3] *= rt;
    4788             : 
    4789             :         /* Extract the NEAR plane */
    4790             :         frustum[5][0] = clip.m[0][3] + clip.m[0][2];
    4791             :         frustum[5][1] = clip.m[1][3] + clip.m[1][2];
    4792             :         frustum[5][2] = clip.m[2][3] + clip.m[2][2];
    4793             :         frustum[5][3] = clip.m[3][3] + clip.m[3][2];
    4794             : 
    4795             :         /* Normalize the result */
    4796             :         rt = sqrt(frustum[5][0] * frustum[5][0] + frustum[5][1] * frustum[5][1] + frustum[5][2] * frustum[5][2]);
    4797             :         frustum[5][0] *= rt;
    4798             :         frustum[5][1] *= rt;
    4799             :         frustum[5][2] *= rt;
    4800             :         frustum[5][3] *= rt;
    4801             : }
    4802             : 
    4803             : 
    4804             : - (BOOL) viewFrustumIntersectsSphereAt:(Vector)position withRadius:(GLfloat)radius
    4805             : {
    4806             :         // position is the relative position between the camera and the object
    4807             :         int p;
    4808             :         for (p = 0; p < 6; p++)
    4809             :         {
    4810             :                 if (frustum[p][0] * position.x + frustum[p][1] * position.y + frustum[p][2] * position.z + frustum[p][3] <= -radius)
    4811             :                 {
    4812             :                         return NO;
    4813             :                 }
    4814             :         }
    4815             :         return YES;
    4816             : }
    4817             : 
    4818             : 
    4819             : - (void) drawUniverse
    4820             : {
    4821             :         int currentPostFX = [self currentPostFX];
    4822             :         BOOL hudSeparateRenderPass =  [self useShaders] && (currentPostFX == OO_POSTFX_NONE || ((currentPostFX == OO_POSTFX_CLOAK || currentPostFX == OO_POSTFX_CRTBADSIGNAL) && [self colorblindMode] == OO_POSTFX_NONE));
    4823             :         NSSize  viewSize = [gameView viewSize];
    4824             :         OOLog(@"universe.profile.draw", @"%@", @"Begin draw");
    4825             :         
    4826             :         if (!no_update)
    4827             :         {
    4828             :                 if ((int)targetFramebufferSize.width != (int)viewSize.width || (int)targetFramebufferSize.height != (int)viewSize.height)
    4829             :                 {
    4830             :                         [self resizeTargetFramebufferWithViewSize:viewSize];
    4831             :                 }
    4832             :         
    4833             :                 if([self useShaders])
    4834             :                 {
    4835             :                         if ([gameView msaa])
    4836             :                         {
    4837             :                                 OOGL(glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebufferID));
    4838             :                         }
    4839             :                         else
    4840             :                         {
    4841             :                                 OOGL(glBindFramebuffer(GL_FRAMEBUFFER, targetFramebufferID));
    4842             :                         }
    4843             :                 }
    4844             :                 @try
    4845             :                 {
    4846             :                         no_update = YES;        // block other attempts to draw
    4847             :                         
    4848             :                         int                             i, v_status, vdist;
    4849             :                         Vector                  view_dir, view_up;
    4850             :                         OOMatrix                view_matrix;
    4851             :                         int                             ent_count =     n_entities;
    4852             :                         Entity                  *my_entities[ent_count];
    4853             :                         int                             draw_count = 0;
    4854             :                         PlayerEntity    *player = PLAYER;
    4855             :                         Entity                  *drawthing = nil;
    4856             :                         BOOL                    demoShipMode = [player showDemoShips];
    4857             :                         
    4858             :                         float   aspect = viewSize.height/viewSize.width;
    4859             : 
    4860             :                         if (!displayGUI && wasDisplayGUI)
    4861             :                         {
    4862             :                                 // reset light1 position for the shaders
    4863             :                                 if (cachedSun) [UNIVERSE setMainLightPosition:HPVectorToVector([cachedSun position])]; // the main light is the sun.
    4864             :                                 else [UNIVERSE setMainLightPosition:kZeroVector];
    4865             :                         }
    4866             :                         wasDisplayGUI = displayGUI;
    4867             :                         // use a non-mutable copy so this can't be changed under us.
    4868             :                         for (i = 0; i < ent_count; i++)
    4869             :                         {
    4870             :                                 /* BUG: this list is ordered nearest to furthest from
    4871             :                                  * the player, and we just assume that the camera is
    4872             :                                  * on/near the player. So long as everything uses
    4873             :                                  * depth tests, we'll get away with it; it'll just
    4874             :                                  * occasionally be inefficient. - CIM */
    4875             :                                 Entity *e = sortedEntities[i]; // ordered NEAREST -> FURTHEST AWAY
    4876             :                                 if ([e isVisible])
    4877             :                                 {
    4878             :                                         my_entities[draw_count++] = [[e retain] autorelease];
    4879             :                                 }
    4880             :                         }
    4881             :                         
    4882             :                         v_status = [player status];
    4883             :                         
    4884             :                         OOCheckOpenGLErrors(@"Universe before doing anything");
    4885             :                         
    4886             :                         OOSetOpenGLState(OPENGL_STATE_OPAQUE);  // FIXME: should be redundant.
    4887             :                         
    4888             :                         OOGL(glClear(GL_COLOR_BUFFER_BIT));
    4889             : 
    4890             :                         if (!displayGUI)
    4891             :                         {
    4892             :                                 OOGL(glClearColor(skyClearColor[0], skyClearColor[1], skyClearColor[2], skyClearColor[3]));
    4893             :                         }
    4894             :                         else
    4895             :                         {
    4896             :                                 OOGL(glClearColor(0.0, 0.0, 0.0, 0.0));
    4897             :                                 // If set, display background GUI image. Must be done before enabling lights to avoid dim backgrounds
    4898             :                                 OOGLResetProjection();
    4899             :                                 OOGLFrustum(-0.5, 0.5, -aspect*0.5, aspect*0.5, 1.0, MAX_CLEAR_DEPTH);
    4900             :                                 [gui drawGUIBackground];
    4901             :                         
    4902             :                         }
    4903             : 
    4904             :                         BOOL            fogging, bpHide = [self breakPatternHide];
    4905             :                         
    4906             :                         for (vdist=0;vdist<=1;vdist++)
    4907             :                         {
    4908             :                                 float   nearPlane = vdist ? 1.0 : INTERMEDIATE_CLEAR_DEPTH;
    4909             :                                 float   farPlane = vdist ? INTERMEDIATE_CLEAR_DEPTH : MAX_CLEAR_DEPTH;
    4910             :                                 float   ratio = (displayGUI ? 0.5 : [gameView fov:YES]) * nearPlane; // 0.5 is field of view ratio for GUIs
    4911             :                                 
    4912             :                                 OOGLResetProjection();
    4913             :                                 if ((displayGUI && 4*aspect >= 3) || (!displayGUI && 4*aspect <= 3))
    4914             :                                 {
    4915             :                                         OOGLFrustum(-ratio, ratio, -aspect*ratio, aspect*ratio, nearPlane, farPlane);
    4916             :                                 }
    4917             :                                 else
    4918             :                                 {
    4919             :                                         OOGLFrustum(-3*ratio/aspect/4, 3*ratio/aspect/4, -3*ratio/4, 3*ratio/4, nearPlane, farPlane);
    4920             :                                 }
    4921             : 
    4922             :                                 [self getActiveViewMatrix:&view_matrix forwardVector:&view_dir upVector:&view_up];
    4923             : 
    4924             :                                 OOGLResetModelView();   // reset matrix
    4925             :                                 OOGLLookAt(kZeroVector, kBasisZVector, kBasisYVector);
    4926             :                         
    4927             :                                 // HACK BUSTED
    4928             :                                 OOGLMultModelView(OOMatrixForScale(-1.0,1.0,1.0)); // flip left and right
    4929             :                                 OOGLPushModelView(); // save this flat viewpoint
    4930             : 
    4931             :                                 /* OpenGL viewpoints: 
    4932             :                                  *
    4933             :                                  * Oolite used to transform the viewpoint by the inverse of the
    4934             :                                  * view position, and then transform the objects by the inverse
    4935             :                                  * of their position, to get the correct view. However, as
    4936             :                                  * OpenGL only uses single-precision floats, this causes
    4937             :                                  * noticeable display inaccuracies relatively close to the
    4938             :                                  * origin.
    4939             :                                  *
    4940             :                                  * Instead, we now calculate the difference between the view
    4941             :                                  * position and the object using high-precision vectors, convert
    4942             :                                  * the difference to a low-precision vector (since if you can
    4943             :                                  * see it, it's close enough for the loss of precision not to
    4944             :                                  * matter) and use that relative vector for the OpenGL transform
    4945             :                                  *
    4946             :                                  * Objects which reset the view matrix in their display need to be
    4947             :                                  * handled a little more carefully than before.
    4948             :                                  */
    4949             : 
    4950             :                                 OOSetOpenGLState(OPENGL_STATE_OPAQUE); 
    4951             :                                 // clearing the depth buffer waits until we've set
    4952             :                                 // STATE_OPAQUE so that depth writes are definitely
    4953             :                                 // available.
    4954             :                                 OOGL(glClear(GL_DEPTH_BUFFER_BIT));
    4955             :                 
    4956             : 
    4957             :                                 // Set up view transformation matrix
    4958             :                                 OOMatrix flipMatrix = kIdentityMatrix;
    4959             :                                 flipMatrix.m[2][2] = -1;
    4960             :                                 view_matrix = OOMatrixMultiply(view_matrix, flipMatrix);
    4961             :                                 Vector viewOffset = [player viewpointOffset];
    4962             :                         
    4963             :                                 OOGLLookAt(view_dir, kZeroVector, view_up); 
    4964             : 
    4965             :                                 if (EXPECT(!displayGUI || demoShipMode))
    4966             :                                 {
    4967             :                                         if (EXPECT(!demoShipMode))      // we're in flight
    4968             :                                         {
    4969             :                                                 // rotate the view
    4970             :                                                 OOGLMultModelView([player rotationMatrix]);
    4971             :                                                 // translate the view
    4972             :                                                 // HPVect: camera-relative position
    4973             :                                                 OOGL(glLightModelfv(GL_LIGHT_MODEL_AMBIENT, stars_ambient));
    4974             :                                                 // main light position, no shaders, in-flight / shaders, in-flight and docked.
    4975             :                                                 if (cachedSun)
    4976             :                                                 {
    4977             :                                                         [self setMainLightPosition:[cachedSun cameraRelativePosition]];
    4978             :                                                 }
    4979             :                                                 else
    4980             :                                                 {
    4981             :                                                         // in witchspace
    4982             :                                                         [self setMainLightPosition:HPVectorToVector(HPvector_flip([PLAYER viewpointPosition]))];
    4983             :                                                 }
    4984             :                                                 OOGL(glLightfv(GL_LIGHT1, GL_POSITION, main_light_position));                                   
    4985             :                                         }
    4986             :                                         else
    4987             :                                         {
    4988             :                                                 OOGL(glLightModelfv(GL_LIGHT_MODEL_AMBIENT, docked_light_ambient));
    4989             :                                                 // main_light_position no shaders, docked/GUI.
    4990             :                                                 OOGL(glLightfv(GL_LIGHT0, GL_POSITION, main_light_position));
    4991             :                                                 // main light position, no shaders, in-flight / shaders, in-flight and docked.          
    4992             :                                                 OOGL(glLightfv(GL_LIGHT1, GL_POSITION, main_light_position));
    4993             :                                         }
    4994             :                                 
    4995             :                                 
    4996             :                                         OOGL([self useGUILightSource:demoShipMode]);
    4997             :                                 
    4998             :                                         // HACK: store view matrix for absolute drawing of active subentities (i.e., turrets, flashers).
    4999             :                                         viewMatrix = OOGLGetModelView();
    5000             : 
    5001             :                                         int                     furthest = draw_count - 1;
    5002             :                                         int                     nearest = 0;
    5003             :                                         BOOL            inAtmosphere = airResistanceFactor > 0.01;
    5004             :                                         GLfloat         fogFactor = 0.5 / airResistanceFactor;
    5005             :                                         double          fog_scale, half_scale;
    5006             :                                         GLfloat         flat_ambdiff[4] = {1.0, 1.0, 1.0, 1.0};   // for alpha
    5007             :                                         GLfloat         mat_no[4]               = {0.0, 0.0, 0.0, 1.0};   // nothing
    5008             :                                         GLfloat         fog_blend;
    5009             :                                 
    5010             :                                         OOGL(glHint(GL_FOG_HINT, [self reducedDetail] ? GL_FASTEST : GL_NICEST));
    5011             :                                 
    5012             :                                         [self defineFrustum]; // camera is set up for this frame
    5013             :                                 
    5014             :                                         OOVerifyOpenGLState();
    5015             :                                         OOCheckOpenGLErrors(@"Universe after setting up for opaque pass");
    5016             :                                         OOLog(@"universe.profile.draw", @"%@", @"Begin opaque pass");
    5017             : 
    5018             :                                 
    5019             :                                         //              DRAW ALL THE OPAQUE ENTITIES
    5020             :                                         for (i = furthest; i >= nearest; i--)
    5021             :                                         {
    5022             :                                                 drawthing = my_entities[i];
    5023             :                                                 OOEntityStatus d_status = [drawthing status];
    5024             :                                         
    5025             :                                                 if (bpHide && !drawthing->isImmuneToBreakPatternHide)  continue;
    5026             :                                                 if (vdist == 1 && [drawthing cameraRangeFront] > farPlane*1.5) continue;
    5027             :                                                 if (vdist == 0 && [drawthing cameraRangeBack] < nearPlane) continue;
    5028             : //                                              if (vdist == 1 && [drawthing isPlanet]) continue;
    5029             : 
    5030             :                                                 if (!((d_status == STATUS_COCKPIT_DISPLAY) ^ demoShipMode)) // either demo ship mode or in flight
    5031             :                                                 {
    5032             :                                                         // reset material properties
    5033             :                                                         // FIXME: should be part of SetState
    5034             :                                                         OOGL(glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, flat_ambdiff));
    5035             :                                                         OOGL(glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, mat_no));
    5036             :                                                 
    5037             :                                                         OOGLPushModelView();
    5038             :                                                         if (EXPECT(drawthing != player))
    5039             :                                                         {
    5040             :                                                                 //translate the object
    5041             :                                                                 // HPVect: camera relative
    5042             :                                                                 [drawthing updateCameraRelativePosition];
    5043             :                                                                 OOGLTranslateModelView([drawthing cameraRelativePosition]);
    5044             :                                                                 //rotate the object
    5045             :                                                                 OOGLMultModelView([drawthing drawRotationMatrix]);
    5046             :                                                         }
    5047             :                                                         else
    5048             :                                                         {
    5049             :                                                                 // Load transformation matrix
    5050             :                                                                 OOGLLoadModelView(view_matrix);
    5051             :                                                                 //translate the object  from the viewpoint
    5052             :                                                                 OOGLTranslateModelView(vector_flip(viewOffset));
    5053             :                                                         }
    5054             :                                                 
    5055             :                                                         // atmospheric fog
    5056             :                                                         fogging = (inAtmosphere && ![drawthing isStellarObject]);
    5057             :                                                 
    5058             :                                                         if (fogging)
    5059             :                                                         {
    5060             :                                                                 fog_scale = BILLBOARD_DEPTH * fogFactor;
    5061             :                                                                 half_scale = fog_scale * 0.50;
    5062             :                                                                 OOGL(glEnable(GL_FOG));
    5063             :                                                                 OOGL(glFogi(GL_FOG_MODE, GL_LINEAR));
    5064             :                                                                 OOGL(glFogfv(GL_FOG_COLOR, skyClearColor));
    5065             :                                                                 OOGL(glFogf(GL_FOG_START, half_scale));
    5066             :                                                                 OOGL(glFogf(GL_FOG_END, fog_scale));
    5067             :                                                                 fog_blend = OOClamp_0_1_f((magnitude([drawthing cameraRelativePosition]) - half_scale)/half_scale);
    5068             :                                                                 [drawthing setAtmosphereFogging: [OOColor colorWithRed: skyClearColor[0] green: skyClearColor[1] blue: skyClearColor[2] alpha: fog_blend]];
    5069             :                                                         }
    5070             :                                                 
    5071             :                                                         [self lightForEntity:demoShipMode || drawthing->isSunlit];
    5072             :                                                 
    5073             :                                                         // draw the thing
    5074             :                                                         [drawthing drawImmediate:false translucent:false];
    5075             :                                                 
    5076             :                                                         OOGLPopModelView();
    5077             : 
    5078             :                                                         // atmospheric fog
    5079             :                                                         if (fogging)
    5080             :                                                         {
    5081             :                                                                 [drawthing setAtmosphereFogging: [OOColor colorWithRed: 0.0 green: 0.0 blue: 0.0 alpha: 0.0]];
    5082             :                                                                 OOGL(glDisable(GL_FOG));
    5083             :                                                         }
    5084             :                                                 
    5085             :                                                 }
    5086             :                                 
    5087             :                                                 if (!((d_status == STATUS_COCKPIT_DISPLAY) ^ demoShipMode)) // either in flight or in demo ship mode
    5088             :                                                 {
    5089             :                                                         OOGLPushModelView();
    5090             :                                                         if (EXPECT(drawthing != player))
    5091             :                                                         {
    5092             :                                                                 //translate the object
    5093             :                                                                 // HPVect: camera relative positions
    5094             :                                                                 [drawthing updateCameraRelativePosition];
    5095             :                                                                 OOGLTranslateModelView([drawthing cameraRelativePosition]);
    5096             :                                                                 //rotate the object
    5097             :                                                                 OOGLMultModelView([drawthing drawRotationMatrix]);
    5098             :                                                         }
    5099             :                                                         else
    5100             :                                                         {
    5101             :                                                                 // Load transformation matrix
    5102             :                                                                 OOGLLoadModelView(view_matrix);
    5103             :                                                                 //translate the object  from the viewpoint
    5104             :                                                                 OOGLTranslateModelView(vector_flip(viewOffset));
    5105             :                                                         }
    5106             :                                                 
    5107             :                                                         // experimental - atmospheric fog
    5108             :                                                         fogging = (inAtmosphere && ![drawthing isStellarObject]);
    5109             :                                                 
    5110             :                                                         if (fogging)
    5111             :                                                         {
    5112             :                                                                 fog_scale = BILLBOARD_DEPTH * fogFactor;
    5113             :                                                                 half_scale = fog_scale * 0.50;
    5114             :                                                                 OOGL(glEnable(GL_FOG));
    5115             :                                                                 OOGL(glFogi(GL_FOG_MODE, GL_LINEAR));
    5116             :                                                                 OOGL(glFogfv(GL_FOG_COLOR, skyClearColor));
    5117             :                                                                 OOGL(glFogf(GL_FOG_START, half_scale));
    5118             :                                                                 OOGL(glFogf(GL_FOG_END, fog_scale));
    5119             :                                                                 fog_blend = OOClamp_0_1_f((magnitude([drawthing cameraRelativePosition]) - half_scale)/half_scale);
    5120             :                                                                 [drawthing setAtmosphereFogging: [OOColor colorWithRed: skyClearColor[0] green: skyClearColor[1] blue: skyClearColor[2] alpha: fog_blend]];
    5121             :                                                         }
    5122             :                                                 
    5123             :                                                         // draw the thing
    5124             :                                                         [drawthing drawImmediate:false translucent:true];
    5125             :                                                 
    5126             :                                                         // atmospheric fog
    5127             :                                                         if (fogging)
    5128             :                                                         {
    5129             :                                                                 [drawthing setAtmosphereFogging: [OOColor colorWithRed: 0.0 green: 0.0 blue: 0.0 alpha: 0.0]];
    5130             :                                                                 OOGL(glDisable(GL_FOG));
    5131             :                                                         }
    5132             :                                                 
    5133             :                                                         OOGLPopModelView();
    5134             :                                                 }
    5135             :                                         }
    5136             :                                 }
    5137             : 
    5138             :                                 OOGLPopModelView();
    5139             :                         }
    5140             :                         
    5141             :                         // glare effects covering the entire game window
    5142             :                         OOGLResetProjection();
    5143             :                         OOGLFrustum(-0.5, 0.5, -aspect*0.5, aspect*0.5, 1.0, MAX_CLEAR_DEPTH);
    5144             :                         OOSetOpenGLState(OPENGL_STATE_OVERLAY);  // FIXME: should be redundant.
    5145             :                         if (EXPECT(!displayGUI))
    5146             :                         {
    5147             :                                 if (!bpHide && cachedSun)
    5148             :                                 {
    5149             :                                         [cachedSun drawDirectVisionSunGlare];
    5150             :                                         [cachedSun drawStarGlare];
    5151             :                                 }
    5152             :                         }
    5153             :                         
    5154             :                         // actions when the HUD should be rendered separately from the 3d universe
    5155             :                         if (hudSeparateRenderPass)
    5156             :                         {
    5157             :                                 OOCheckOpenGLErrors(@"Universe after drawing entities");
    5158             :                                 OOSetOpenGLState(OPENGL_STATE_OVERLAY);  // FIXME: should be redundant.
    5159             :                                 
    5160             :                                 [self prepareToRenderIntoDefaultFramebuffer];   
    5161             :                                 OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
    5162             :                                 
    5163             :                                 OOLog(@"universe.profile.secondPassDraw", @"%@", @"Begin second pass draw");
    5164             :                                 [self drawTargetTextureIntoDefaultFramebuffer];
    5165             :                                 OOCheckOpenGLErrors(@"Universe after drawing from custom framebuffer to screen framebuffer");
    5166             :                                 OOLog(@"universe.profile.secondPassDraw", @"%@", @"End second pass drawing");
    5167             :         
    5168             :                                 OOLog(@"universe.profile.drawHUD", @"%@", @"Begin HUD drawing");
    5169             :                         }
    5170             :                         
    5171             :                         /* Reset for HUD drawing */
    5172             :                         OOCheckOpenGLErrors(@"Universe after drawing entities");
    5173             :                         OOLog(@"universe.profile.draw", @"%@", @"Begin HUD");
    5174             :                         
    5175             :                         GLfloat lineWidth = [gameView viewSize].width / 1024.0; // restore line size
    5176             :                         if (lineWidth < 1.0)  lineWidth = 1.0;
    5177             :                         if (lineWidth > 1.5)  lineWidth = 1.5; // don't overscale; think of ultra-wide screen setups
    5178             :                         OOGL(GLScaledLineWidth(lineWidth));
    5179             : 
    5180             :                         HeadUpDisplay *theHUD = [player hud];
    5181             :                         
    5182             :                         // If the HUD has a non-nil deferred name string, it means that a HUD switch was requested while it was being rendered.
    5183             :                         // If so, execute the deferred HUD switch now - Nikos 20110628
    5184             :                         if ([theHUD deferredHudName] != nil)
    5185             :                         {
    5186             :                                 NSString *deferredName = [[theHUD deferredHudName] retain];
    5187             :                                 [player switchHudTo:deferredName];
    5188             :                                 [deferredName release];
    5189             :                                 theHUD = [player hud];  // HUD has been changed, so point to its new address
    5190             :                         }
    5191             :                         
    5192             :                         // Hiding HUD: has been a regular - non-debug - feature as of r2749, about 2 yrs ago! --Kaks 2011.10.14
    5193             :                         static float sPrevHudAlpha = -1.0f;
    5194             :                         if ([theHUD isHidden])
    5195             :                         {
    5196             :                                 if (sPrevHudAlpha < 0.0f)
    5197             :                                 {
    5198             :                                         sPrevHudAlpha = [theHUD overallAlpha];
    5199             :                                 }
    5200             :                                 [theHUD setOverallAlpha:0.0f];
    5201             :                         }
    5202             :                         else if (sPrevHudAlpha >= 0.0f)
    5203             :                         {
    5204             :                                 [theHUD setOverallAlpha:sPrevHudAlpha];
    5205             :                                 sPrevHudAlpha = -1.0f;
    5206             :                         }
    5207             :                         
    5208             :                         switch (v_status) {
    5209             :                         case STATUS_DEAD:
    5210             :                         case STATUS_ESCAPE_SEQUENCE:
    5211             :                         case STATUS_START_GAME:
    5212             :                                 // no HUD rendering in these modes
    5213             :                                 break;
    5214             :                         default:
    5215             :                                 switch ([player guiScreen])
    5216             :                                 {
    5217             :                                 //case GUI_SCREEN_KEYBOARD:
    5218             :                                         // no HUD rendering on this screen
    5219             :                                         //break;
    5220             :                                 default:
    5221             :                                         [theHUD setLineWidth:lineWidth];
    5222             :                                         [theHUD renderHUD];
    5223             :                                 }
    5224             :                         }
    5225             : 
    5226             :                         // should come after the HUD to avoid it being overlapped by it
    5227             :                         [self drawMessage];
    5228             :                         
    5229             : #if (defined (SNAPSHOT_BUILD) && defined (OOLITE_SNAPSHOT_VERSION))
    5230             :                         [self drawWatermarkString:@"Development version " @OOLITE_SNAPSHOT_VERSION];
    5231             : #endif
    5232             :                         
    5233             :                         OOLog(@"universe.profile.drawHUD", @"%@", @"End HUD drawing");
    5234             :                         OOCheckOpenGLErrors(@"Universe after drawing HUD");
    5235             :                         
    5236             :                         OOGL(glFlush());        // don't wait around for drawing to complete
    5237             :                         
    5238             :                         no_update = NO; // allow other attempts to draw
    5239             :                         
    5240             :                         // frame complete, when it is time to update the fps_counter, updateClocks:delta_t
    5241             :                         // in PlayerEntity.m will take care of resetting the processed frames number to 0.
    5242             :                         if (![[self gameController] isGamePaused])
    5243             :                         {
    5244             :                                 framesDoneThisUpdate++;
    5245             :                         }
    5246             :                 }
    5247             :                 @catch (NSException *exception)
    5248             :                 {
    5249             :                         no_update = NO; // make sure we don't get stuck in all subsequent frames.
    5250             :                         
    5251             :                         if ([[exception name] hasPrefix:@"Oolite"])
    5252             :                         {
    5253             :                                 [self handleOoliteException:exception];
    5254             :                         }
    5255             :                         else
    5256             :                         {
    5257             :                                 OOLog(kOOLogException, @"***** Exception: %@ : %@ *****",[exception name], [exception reason]);
    5258             :                                 @throw exception;
    5259             :                         }
    5260             :                 }
    5261             :         }
    5262             :         
    5263             :         OOLog(@"universe.profile.draw", @"%@", @"End drawing");
    5264             :         
    5265             :         // actions when the HUD should be rendered together with the 3d universe
    5266             :         if(!hudSeparateRenderPass)
    5267             :         {
    5268             :                 if([self useShaders])
    5269             :                 {
    5270             :                         [self prepareToRenderIntoDefaultFramebuffer];
    5271             :                         OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
    5272             :                         
    5273             :                         OOLog(@"universe.profile.secondPassDraw", @"%@", @"Begin second pass draw");
    5274             :                         [self drawTargetTextureIntoDefaultFramebuffer];
    5275             :                         OOLog(@"universe.profile.secondPassDraw", @"%@", @"End second pass drawing");
    5276             :                 }
    5277             :         }
    5278             : }
    5279             : 
    5280             : 
    5281           0 : - (void) prepareToRenderIntoDefaultFramebuffer
    5282             : {
    5283             :         NSSize viewSize = [gameView viewSize];
    5284             :         if([self useShaders])
    5285             :         {
    5286             :                 if ([gameView msaa])
    5287             :                 {
    5288             :                         // resolve MSAA framebuffer to target framebuffer
    5289             :                         OOGL(glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFramebufferID));
    5290             :                         OOGL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetFramebufferID));
    5291             :                         OOGL(glBlitFramebuffer(0, 0, (GLint)viewSize.width, (GLint)viewSize.height, 0, 0, (GLint)viewSize.width, (GLint)viewSize.height, GL_COLOR_BUFFER_BIT, GL_NEAREST));
    5292             :                 }
    5293             :         }
    5294             : }
    5295             : 
    5296             : 
    5297             : - (int) framesDoneThisUpdate
    5298             : {
    5299             :         return framesDoneThisUpdate;
    5300             : }
    5301             : 
    5302             : 
    5303             : - (void) resetFramesDoneThisUpdate
    5304             : {
    5305             :         framesDoneThisUpdate = 0;
    5306             : }
    5307             : 
    5308             : 
    5309             : - (OOMatrix) viewMatrix
    5310             : {
    5311             :         return viewMatrix;
    5312             : }
    5313             : 
    5314             : 
    5315             : - (void) drawMessage
    5316             : {
    5317             :         OOSetOpenGLState(OPENGL_STATE_OVERLAY);
    5318             :         
    5319             :         OOGL(glDisable(GL_TEXTURE_2D)); // for background sheets
    5320             :         
    5321             :         float overallAlpha = [[PLAYER hud] overallAlpha];
    5322             :         if (displayGUI)
    5323             :         {
    5324             :                 if ([[self gameController] mouseInteractionMode] == MOUSE_MODE_UI_SCREEN_WITH_INTERACTION)
    5325             :                 {
    5326             :                         cursor_row = [gui drawGUI:1.0 drawCursor:YES];
    5327             :                 }
    5328             :                 else
    5329             :                 {
    5330             :                         [gui drawGUI:1.0 drawCursor:NO];
    5331             :                 }
    5332             :         }
    5333             :         
    5334             :         [message_gui drawGUI:[message_gui alpha] * overallAlpha drawCursor:NO];
    5335             :         [comm_log_gui drawGUI:[comm_log_gui alpha] * overallAlpha drawCursor:NO];
    5336             :         
    5337             :         OOVerifyOpenGLState();
    5338             : }
    5339             : 
    5340             : 
    5341             : - (void) drawWatermarkString:(NSString *) watermarkString
    5342             : {
    5343             :         NSSize watermarkStringSize = OORectFromString(watermarkString, 0.0f, 0.0f, NSMakeSize(10, 10)).size;
    5344             :         
    5345             :         OOGL(glColor4f(0.0, 1.0, 0.0, 1.0));
    5346             :         // position the watermark string on the top right hand corner of the game window and right-align it
    5347             :         OODrawString(watermarkString, MAIN_GUI_PIXEL_WIDTH / 2 - watermarkStringSize.width + 80,
    5348             :                                                 MAIN_GUI_PIXEL_HEIGHT / 2 - watermarkStringSize.height, [gameView display_z], NSMakeSize(10,10));
    5349             : }
    5350             : 
    5351             : 
    5352             : - (id)entityForUniversalID:(OOUniversalID)u_id
    5353             : {
    5354             :         if (u_id == 100)
    5355             :                 return PLAYER;  // the player
    5356             :         
    5357             :         if (MAX_ENTITY_UID < u_id)
    5358             :         {
    5359             :                 OOLog(@"universe.badUID", @"Attempt to retrieve entity for out-of-range UID %u. (This is an internal programming error, please report it.)", u_id);
    5360             :                 return nil;
    5361             :         }
    5362             :         
    5363             :         if ((u_id == NO_TARGET)||(!entity_for_uid[u_id]))
    5364             :                 return nil;
    5365             :         
    5366             :         Entity *ent = entity_for_uid[u_id];
    5367             :         if ([ent isEffect])     // effects SHOULD NOT HAVE U_IDs!
    5368             :         {
    5369             :                 return nil;
    5370             :         }
    5371             :         
    5372             :         if ([ent status] == STATUS_DEAD || [ent status] == STATUS_DOCKED)
    5373             :         {
    5374             :                 return nil;
    5375             :         }
    5376             :         
    5377             :         return ent;
    5378             : }
    5379             : 
    5380             : 
    5381           0 : static BOOL MaintainLinkedLists(Universe *uni)
    5382             : {
    5383             :         NSCParameterAssert(uni != NULL);
    5384             :         BOOL result = YES;
    5385             :         
    5386             :         // DEBUG check for loops and short lists
    5387             :         if (uni->n_entities > 0)
    5388             :         {
    5389             :                 int n;
    5390             :                 Entity  *checkEnt, *last;
    5391             :                 
    5392             :                 last = nil;
    5393             :                 
    5394             :                 n = uni->n_entities;
    5395             :                 checkEnt = uni->x_list_start;
    5396             :                 while ((n--)&&(checkEnt))
    5397             :                 {
    5398             :                         last = checkEnt;
    5399             :                         checkEnt = checkEnt->x_next;
    5400             :                 }
    5401             :                 if ((checkEnt)||(n > 0))
    5402             :                 {
    5403             :                         OOExtraLog(kOOLogEntityVerificationError, @"Broken x_next %@ list (%d) ***", uni->x_list_start, n);
    5404             :                         result = NO;
    5405             :                 }
    5406             :                 
    5407             :                 n = uni->n_entities;
    5408             :                 checkEnt = last;
    5409             :                 while ((n--)&&(checkEnt))       checkEnt = checkEnt->x_previous;
    5410             :                 if ((checkEnt)||(n > 0))
    5411             :                 {
    5412             :                         OOExtraLog(kOOLogEntityVerificationError, @"Broken x_previous %@ list (%d) ***", uni->x_list_start, n);
    5413             :                         if (result)
    5414             :                         {
    5415             :                                 OOExtraLog(kOOLogEntityVerificationRebuild, @"%@", @"REBUILDING x_previous list from x_next list");
    5416             :                                 checkEnt = uni->x_list_start;
    5417             :                                 checkEnt->x_previous = nil;
    5418             :                                 while (checkEnt->x_next)
    5419             :                                 {
    5420             :                                         last = checkEnt;
    5421             :                                         checkEnt = checkEnt->x_next;
    5422             :                                         checkEnt->x_previous = last;
    5423             :                                 }
    5424             :                         }
    5425             :                 }
    5426             :                 
    5427             :                 n = uni->n_entities;
    5428             :                 checkEnt = uni->y_list_start;
    5429             :                 while ((n--)&&(checkEnt))
    5430             :                 {
    5431             :                         last = checkEnt;
    5432             :                         checkEnt = checkEnt->y_next;
    5433             :                 }
    5434             :                 if ((checkEnt)||(n > 0))
    5435             :                 {
    5436             :                         OOExtraLog(kOOLogEntityVerificationError, @"Broken *** broken y_next %@ list (%d) ***", uni->y_list_start, n);
    5437             :                         result = NO;
    5438             :                 }
    5439             :                 
    5440             :                 n = uni->n_entities;
    5441             :                 checkEnt = last;
    5442             :                 while ((n--)&&(checkEnt))       checkEnt = checkEnt->y_previous;
    5443             :                 if ((checkEnt)||(n > 0))
    5444             :                 {
    5445             :                         OOExtraLog(kOOLogEntityVerificationError, @"Broken y_previous %@ list (%d) ***", uni->y_list_start, n);
    5446             :                         if (result)
    5447             :                         {
    5448             :                                 OOExtraLog(kOOLogEntityVerificationRebuild, @"%@", @"REBUILDING y_previous list from y_next list");
    5449             :                                 checkEnt = uni->y_list_start;
    5450             :                                 checkEnt->y_previous = nil;
    5451             :                                 while (checkEnt->y_next)
    5452             :                                 {
    5453             :                                         last = checkEnt;
    5454             :                                         checkEnt = checkEnt->y_next;
    5455             :                                         checkEnt->y_previous = last;
    5456             :                                 }
    5457             :                         }
    5458             :                 }
    5459             :                 
    5460             :                 n = uni->n_entities;
    5461             :                 checkEnt = uni->z_list_start;
    5462             :                 while ((n--)&&(checkEnt))
    5463             :                 {
    5464             :                         last = checkEnt;
    5465             :                         checkEnt = checkEnt->z_next;
    5466             :                 }
    5467             :                 if ((checkEnt)||(n > 0))
    5468             :                 {
    5469             :                         OOExtraLog(kOOLogEntityVerificationError, @"Broken z_next %@ list (%d) ***", uni->z_list_start, n);
    5470             :                         result = NO;
    5471             :                 }
    5472             :                 
    5473             :                 n = uni->n_entities;
    5474             :                 checkEnt = last;
    5475             :                 while ((n--)&&(checkEnt))       checkEnt = checkEnt->z_previous;
    5476             :                 if ((checkEnt)||(n > 0))
    5477             :                 {
    5478             :                         OOExtraLog(kOOLogEntityVerificationError, @"Broken z_previous %@ list (%d) ***", uni->z_list_start, n);
    5479             :                         if (result)
    5480             :                         {
    5481             :                                 OOExtraLog(kOOLogEntityVerificationRebuild, @"%@", @"REBUILDING z_previous list from z_next list");
    5482             :                                 checkEnt = uni->z_list_start;
    5483             :                                 NSCAssert(checkEnt != nil, @"Expected z-list to be non-empty.");      // Previously an implicit assumption. -- Ahruman 2011-01-25
    5484             :                                 checkEnt->z_previous = nil;
    5485             :                                 while (checkEnt->z_next)
    5486             :                                 {
    5487             :                                         last = checkEnt;
    5488             :                                         checkEnt = checkEnt->z_next;
    5489             :                                         checkEnt->z_previous = last;
    5490             :                                 }
    5491             :                         }
    5492             :                 }
    5493             :         }
    5494             :         
    5495             :         if (!result)
    5496             :         {
    5497             :                 OOExtraLog(kOOLogEntityVerificationRebuild, @"%@", @"Rebuilding all linked lists from scratch");
    5498             :                 NSArray *allEntities = uni->entities;
    5499             :                 uni->x_list_start = nil;
    5500             :                 uni->y_list_start = nil;
    5501             :                 uni->z_list_start = nil;
    5502             :                 
    5503             :                 Entity *ent = nil;
    5504             :                 foreach (ent, allEntities)
    5505             :                 {
    5506             :                         ent->x_next = nil;
    5507             :                         ent->x_previous = nil;
    5508             :                         ent->y_next = nil;
    5509             :                         ent->y_previous = nil;
    5510             :                         ent->z_next = nil;
    5511             :                         ent->z_previous = nil;
    5512             :                         [ent addToLinkedLists];
    5513             :                 }
    5514             :         }
    5515             :         
    5516             :         return result;
    5517             : }
    5518             : 
    5519             : 
    5520             : - (BOOL) addEntity:(Entity *) entity
    5521             : {
    5522             :         if (entity)
    5523             :         {
    5524             :                 ShipEntity *se = nil;
    5525             :                 OOVisualEffectEntity *ve = nil;
    5526             :                 OOWaypointEntity *wp = nil;
    5527             :                 
    5528             :                 if (![entity validForAddToUniverse])  return NO;
    5529             :                 
    5530             :                 // don't add things twice!
    5531             :                 if ([entities containsObject:entity])
    5532             :                         return YES;
    5533             :                 
    5534             :                 if (n_entities >= UNIVERSE_MAX_ENTITIES - 1)
    5535             :                 {
    5536             :                         // throw an exception here...
    5537             :                         OOLog(@"universe.addEntity.failed", @"***** Universe cannot addEntity:%@ -- Universe is full (%d entities out of %d)", entity, n_entities, UNIVERSE_MAX_ENTITIES);
    5538             : #ifndef NDEBUG
    5539             :                         if (OOLogWillDisplayMessagesInClass(@"universe.maxEntitiesDump")) [self debugDumpEntities];
    5540             : #endif
    5541             :                         return NO;
    5542             :                 }
    5543             :                 
    5544             :                 if (![entity isEffect])
    5545             :                 {
    5546             :                         unsigned limiter = UNIVERSE_MAX_ENTITIES;
    5547             :                         while (entity_for_uid[next_universal_id] != nil)        // skip allocated numbers
    5548             :                         {
    5549             :                                 next_universal_id++;                                            // increment keeps idkeys unique
    5550             :                                 if (next_universal_id >= MAX_ENTITY_UID)
    5551             :                                 {
    5552             :                                         next_universal_id = MIN_ENTITY_UID;
    5553             :                                 }
    5554             :                                 if (limiter-- == 0)
    5555             :                                 {
    5556             :                                         // Every slot has been tried! This should not happen due to previous test, but there was a problem here in 1.70.
    5557             :                                         OOLog(@"universe.addEntity.failed", @"***** Universe cannot addEntity:%@ -- Could not find free slot for entity.", entity);
    5558             :                                         return NO;
    5559             :                                 }
    5560             :                         }
    5561             :                         [entity setUniversalID:next_universal_id];
    5562             :                         entity_for_uid[next_universal_id] = entity;
    5563             :                         if ([entity isShip])
    5564             :                         {
    5565             :                                 se = (ShipEntity *)entity;
    5566             :                                 if ([se isBeacon])
    5567             :                                 {
    5568             :                                         [self setNextBeacon:se];
    5569             :                                 }
    5570             :                                 if ([se isStation])
    5571             :                                 {
    5572             :                                         // check if it is a proper rotating station (ie. roles contains the word "station")
    5573             :                                         if ([(StationEntity*)se isRotatingStation])
    5574             :                                         {
    5575             :                                                 double stationRoll = 0.0;
    5576             :                                                 // check for station_roll override
    5577             :                                                 id definedRoll = [[se shipInfoDictionary] objectForKey:@"station_roll"];
    5578             :                                                 
    5579             :                                                 if (definedRoll != nil)
    5580             :                                                 {
    5581             :                                                         stationRoll = OODoubleFromObject(definedRoll, stationRoll);
    5582             :                                                 }
    5583             :                                                 else
    5584             :                                                 {
    5585             :                                                         stationRoll = [[self currentSystemData] oo_doubleForKey:@"station_roll" defaultValue:STANDARD_STATION_ROLL];
    5586             :                                                 }
    5587             :                                                 
    5588             :                                                 [se setRoll: stationRoll];
    5589             :                                         }
    5590             :                                         else
    5591             :                                         {
    5592             :                                                 [se setRoll: 0.0];
    5593             :                                         }
    5594             :                                         [(StationEntity *)se setPlanet:[self planet]];
    5595             :                                         if ([se maxFlightSpeed] > 0) se->isExplicitlyNotMainStation = YES; // we never want carriers to become main stations.
    5596             :                                 }
    5597             :                                 // stations used to have STATUS_ACTIVE, they're all STATUS_IN_FLIGHT now.
    5598             :                                 if ([se status] != STATUS_COCKPIT_DISPLAY)
    5599             :                                 {
    5600             :                                         [se setStatus:STATUS_IN_FLIGHT];
    5601             :                                 }
    5602             :                         }
    5603             :                 }
    5604             :                 else
    5605             :                 {
    5606             :                         [entity setUniversalID:NO_TARGET];
    5607             :                         if ([entity isVisualEffect])
    5608             :                         {
    5609             :                                 ve = (OOVisualEffectEntity *)entity;
    5610             :                                 if ([ve isBeacon])
    5611             :                                 {
    5612             :                                         [self setNextBeacon:ve];
    5613             :                                 }
    5614             :                         }
    5615             :                         else if ([entity isWaypoint])
    5616             :                         {
    5617             :                                 wp = (OOWaypointEntity *)entity;
    5618             :                                 if ([wp isBeacon])
    5619             :                                 {
    5620             :                                         [self setNextBeacon:wp];
    5621             :                                 }
    5622             :                         }
    5623             :                 }
    5624             :                 
    5625             :                 // lighting considerations
    5626             :                 entity->isSunlit = YES;
    5627             :                 entity->shadingEntityID = NO_TARGET;
    5628             :                 
    5629             :                 // add it to the universe
    5630             :                 [entities addObject:entity];
    5631             :                 [entity wasAddedToUniverse];
    5632             :                 
    5633             :                 // maintain sorted list (and for the scanner relative position)
    5634             :                 HPVector entity_pos = entity->position;
    5635             :                 HPVector delta = HPvector_between(entity_pos, PLAYER->position);
    5636             :                 double z_distance = HPmagnitude2(delta);
    5637             :                 entity->zero_distance = z_distance;
    5638             :                 unsigned index = n_entities;
    5639             :                 sortedEntities[index] = entity;
    5640             :                 entity->zero_index = index;
    5641             :                 while ((index > 0)&&(z_distance < sortedEntities[index - 1]->zero_distance))   // bubble into place
    5642             :                 {
    5643             :                         sortedEntities[index] = sortedEntities[index - 1];
    5644             :                         sortedEntities[index]->zero_index = index;
    5645             :                         index--;
    5646             :                         sortedEntities[index] = entity;
    5647             :                         entity->zero_index = index;
    5648             :                 }
    5649             :                 
    5650             :                 // increase n_entities...
    5651             :                 n_entities++;
    5652             :                 
    5653             :                 // add entity to linked lists
    5654             :                 [entity addToLinkedLists];      // position and universe have been set - so we can do this
    5655             :                 if ([entity canCollide])        // filter only collidables disappearing
    5656             :                 {
    5657             :                         doLinkedListMaintenanceThisUpdate = YES;
    5658             :                 }
    5659             :                 
    5660             :                 if ([entity isWormhole])
    5661             :                 {
    5662             :                         [activeWormholes addObject:entity];
    5663             :                 }
    5664             :                 else if ([entity isPlanet])
    5665             :                 {
    5666             :                         [allPlanets addObject:entity];
    5667             :                 }
    5668             :                 else if ([entity isShip])
    5669             :                 {
    5670             :                         [[se getAI] setOwner:se];
    5671             :                         [[se getAI] setState:@"GLOBAL"];
    5672             :                         if ([entity isStation])
    5673             :                         {
    5674             :                                 [allStations addObject:entity];
    5675             :                         }
    5676             :                 }
    5677             :                 
    5678             :                 return YES;
    5679             :         }
    5680             :         return NO;
    5681             : }
    5682             : 
    5683             : 
    5684             : - (BOOL) removeEntity:(Entity *) entity
    5685             : {
    5686             :         if (entity != nil && ![entity isPlayer])
    5687             :         {
    5688             :                 /*      Ensure entity won't actually be dealloced until the end of this
    5689             :                         update (or the next update if none is in progress), because
    5690             :                         there may be things pointing to it but not retaining it.
    5691             :                 */
    5692             :                 [entitiesDeadThisUpdate addObject:entity];
    5693             :                 if ([entity isStation])
    5694             :                 {
    5695             :                         [allStations removeObject:entity];
    5696             :                         if ([PLAYER getTargetDockStation] == entity)
    5697             :                         {
    5698             :                                 [PLAYER setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
    5699             :                         }
    5700             :                 }
    5701             :                 return [self doRemoveEntity:entity];
    5702             :         }
    5703             :         return NO;
    5704             : }
    5705             : 
    5706             : 
    5707             : - (void) ensureEntityReallyRemoved:(Entity *)entity
    5708             : {
    5709             :         if ([entity universalID] != NO_TARGET)
    5710             :         {
    5711             :                 OOLog(@"universe.unremovedEntity", @"Entity %@ dealloced without being removed from universe! (This is an internal programming error, please report it.)", entity);
    5712             :                 [self doRemoveEntity:entity];
    5713             :         }
    5714             : }
    5715             : 
    5716             : 
    5717             : - (void) removeAllEntitiesExceptPlayer
    5718             : {
    5719             :         BOOL updating = no_update;
    5720             :         no_update = YES;                        // no drawing while we do this!
    5721             :         
    5722             : #ifndef NDEBUG
    5723             :         Entity* p0 = [entities objectAtIndex:0];
    5724             :         if (!(p0->isPlayer))
    5725             :         {
    5726             :                 OOLog(kOOLogInconsistentState, @"%@", @"***** First entity is not the player in Universe.removeAllEntitiesExceptPlayer - exiting.");
    5727             :                 exit(EXIT_FAILURE);
    5728             :         }
    5729             : #endif
    5730             :         
    5731             :         // preserve wormholes
    5732             :         NSMutableArray *savedWormholes = [activeWormholes mutableCopy];
    5733             :         
    5734             :         while ([entities count] > 1)
    5735             :         {
    5736             :                 Entity* ent = [entities objectAtIndex:1];
    5737             :                 if (ent->isStation)  // clear out queues
    5738             :                         [(StationEntity *)ent clear];
    5739             :                 if (EXPECT(![ent isVisualEffect]))
    5740             :                 {
    5741             :                         [self removeEntity:ent];
    5742             :                 }
    5743             :                 else
    5744             :                 {
    5745             :                         // this will ensure the effectRemoved script event will run
    5746             :                         [(OOVisualEffectEntity *)ent remove];
    5747             :                 }
    5748             :         }
    5749             :         
    5750             :         [activeWormholes release];
    5751             :         activeWormholes = savedWormholes;       // will be cleared out by populateSpaceFromActiveWormholes
    5752             :         
    5753             :         // maintain sorted list
    5754             :         n_entities = 1;
    5755             :         
    5756             :         cachedSun = nil;
    5757             :         cachedPlanet = nil;
    5758             :         cachedStation = nil;
    5759             :         [closeSystems release];
    5760             :         closeSystems = nil;
    5761             :         
    5762             :         [self resetBeacons];
    5763             :         [waypoints removeAllObjects];
    5764             :         
    5765             :         no_update = updating;   // restore drawing
    5766             : }
    5767             : 
    5768             : 
    5769             : - (void) removeDemoShips
    5770             : {
    5771             :         int i;
    5772             :         int ent_count = n_entities;
    5773             :         if (ent_count > 0)
    5774             :         {
    5775             :                 Entity* ent;
    5776             :                 for (i = 0; i < ent_count; i++)
    5777             :                 {
    5778             :                         ent = sortedEntities[i];
    5779             :                         if ([ent status] == STATUS_COCKPIT_DISPLAY && ![ent isPlayer])
    5780             :                         {
    5781             :                                 [self removeEntity:ent];
    5782             :                         }
    5783             :                 }
    5784             :         }
    5785             :         demo_ship = nil;
    5786             : }
    5787             : 
    5788             : 
    5789             : - (ShipEntity *) makeDemoShipWithRole:(NSString *)role spinning:(BOOL)spinning
    5790             : {
    5791             :         if ([PLAYER dockedStation] == nil)  return nil;
    5792             :         
    5793             :         [self removeDemoShips]; // get rid of any pre-existing models on display
    5794             :         
    5795             :         [PLAYER setShowDemoShips: YES];
    5796             :         Quaternion q2 = { (GLfloat)M_SQRT1_2, (GLfloat)M_SQRT1_2, (GLfloat)0.0, (GLfloat)0.0 };
    5797             :         
    5798             :         ShipEntity *ship = [self newShipWithRole:role];   // retain count = 1
    5799             :         if (ship)
    5800             :         {
    5801             :                 double cr = [ship collisionRadius];
    5802             :                 [ship setOrientation:q2];
    5803             :                 [ship setPositionX:0.0f y:0.0f z:3.6f * cr];
    5804             :                 [ship setScanClass:CLASS_NO_DRAW];
    5805             :                 [ship switchAITo:@"nullAI.plist"];
    5806             :                 [ship setPendingEscortCount:0];
    5807             :                 
    5808             :                 [UNIVERSE addEntity:ship];              // STATUS_IN_FLIGHT, AI state GLOBAL
    5809             : 
    5810             :                 if (spinning)
    5811             :                 {
    5812             :                         [ship setDemoShip: 1.0f];
    5813             :                 }
    5814             :                 else
    5815             :                 {
    5816             :                         [ship setDemoShip: 0.0f];
    5817             :                 }
    5818             :                 [ship setStatus:STATUS_COCKPIT_DISPLAY];
    5819             :                 // stop problems on the ship library screen
    5820             :                 // demo ships shouldn't have this equipment
    5821             :                 [ship removeEquipmentItem:@"EQ_SHIELD_BOOSTER"];
    5822             :                 [ship removeEquipmentItem:@"EQ_SHIELD_ENHANCER"];
    5823             :         }
    5824             :         
    5825             :         return [ship autorelease];
    5826             : }
    5827             : 
    5828             : 
    5829             : - (BOOL) isVectorClearFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2
    5830             : {
    5831             :         if (!e1)
    5832             :                 return NO;
    5833             :         
    5834             :         HPVector  f1;
    5835             :         HPVector p1 = e1->position;
    5836             :         HPVector v1 = p2;
    5837             :         v1.x -= p1.x;   v1.y -= p1.y;   v1.z -= p1.z;   // vector from entity to p2
    5838             :         
    5839             :         double  nearest = sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z) - dist;  // length of vector
    5840             :         
    5841             :         if (nearest < 0.0)
    5842             :                 return YES;                     // within range already!
    5843             :         
    5844             :         int i;
    5845             :         int ent_count = n_entities;
    5846             :         Entity* my_entities[ent_count];
    5847             :         for (i = 0; i < ent_count; i++)
    5848             :                 my_entities[i] = [sortedEntities[i] retain]; // retained
    5849             :         
    5850             :         if (v1.x || v1.y || v1.z)
    5851             :                 f1 = HPvector_normal(v1);   // unit vector in direction of p2 from p1
    5852             :         else
    5853             :                 f1 = make_HPvector(0, 0, 1);
    5854             :         
    5855             :         for (i = 0; i < ent_count ; i++)
    5856             :         {
    5857             :                 Entity *e2 = my_entities[i];
    5858             :                 if ((e2 != e1)&&([e2 canCollide]))
    5859             :                 {
    5860             :                         HPVector epos = e2->position;
    5861             :                         epos.x -= p1.x; epos.y -= p1.y; epos.z -= p1.z; // epos now holds vector from p1 to this entities position
    5862             :                         
    5863             :                         double d_forward = HPdot_product(epos,f1);      // distance along f1 which is nearest to e2's position
    5864             :                         
    5865             :                         if ((d_forward > 0)&&(d_forward < nearest))
    5866             :                         {
    5867             :                                 double cr = 1.10 * (e2->collision_radius + e1->collision_radius); //  10% safety margin
    5868             :                                 HPVector p0 = e1->position;
    5869             :                                 p0.x += d_forward * f1.x;       p0.y += d_forward * f1.y;       p0.z += d_forward * f1.z;
    5870             :                                 // p0 holds nearest point on current course to center of incident object
    5871             :                                 HPVector epos = e2->position;
    5872             :                                 p0.x -= epos.x; p0.y -= epos.y; p0.z -= epos.z;
    5873             :                                 // compare with center of incident object
    5874             :                                 double  dist2 = p0.x * p0.x + p0.y * p0.y + p0.z * p0.z;
    5875             :                                 if (dist2 < cr*cr)
    5876             :                                 {
    5877             :                                         for (i = 0; i < ent_count; i++)
    5878             :                                                 [my_entities[i] release]; //    released
    5879             :                                         return NO;
    5880             :                                 }
    5881             :                         }
    5882             :                 }
    5883             :         }
    5884             :         for (i = 0; i < ent_count; i++)
    5885             :                 [my_entities[i] release]; //    released
    5886             :         return YES;
    5887             : }
    5888             : 
    5889             : 
    5890             : - (Entity*) hazardOnRouteFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2
    5891             : {
    5892             :         if (!e1)
    5893             :                 return nil;
    5894             :         
    5895             :         HPVector f1;
    5896             :         HPVector p1 = e1->position;
    5897             :         HPVector v1 = p2;
    5898             :         v1.x -= p1.x;   v1.y -= p1.y;   v1.z -= p1.z;   // vector from entity to p2
    5899             :         
    5900             :         double  nearest = HPmagnitude(v1) - dist;  // length of vector
    5901             :         
    5902             :         if (nearest < 0.0)
    5903             :                 return nil;                     // within range already!
    5904             :         
    5905             :         Entity* result = nil;
    5906             :         int i;
    5907             :         int ent_count = n_entities;
    5908             :         Entity* my_entities[ent_count];
    5909             :         for (i = 0; i < ent_count; i++)
    5910             :                 my_entities[i] = [sortedEntities[i] retain]; // retained
    5911             :         
    5912             :         if (v1.x || v1.y || v1.z)
    5913             :                 f1 = HPvector_normal(v1);   // unit vector in direction of p2 from p1
    5914             :         else
    5915             :                 f1 = make_HPvector(0, 0, 1);
    5916             :         
    5917             :         for (i = 0; (i < ent_count) && (!result) ; i++)
    5918             :         {
    5919             :                 Entity *e2 = my_entities[i];
    5920             :                 if ((e2 != e1)&&([e2 canCollide]))
    5921             :                 {
    5922             :                         HPVector epos = e2->position;
    5923             :                         epos.x -= p1.x; epos.y -= p1.y; epos.z -= p1.z; // epos now holds vector from p1 to this entities position
    5924             :                         
    5925             :                         double d_forward = HPdot_product(epos,f1);      // distance along f1 which is nearest to e2's position
    5926             :                         
    5927             :                         if ((d_forward > 0)&&(d_forward < nearest))
    5928             :                         {
    5929             :                                 double cr = 1.10 * (e2->collision_radius + e1->collision_radius); //  10% safety margin
    5930             :                                 HPVector p0 = e1->position;
    5931             :                                 p0.x += d_forward * f1.x;       p0.y += d_forward * f1.y;       p0.z += d_forward * f1.z;
    5932             :                                 // p0 holds nearest point on current course to center of incident object
    5933             :                                 HPVector epos = e2->position;
    5934             :                                 p0.x -= epos.x; p0.y -= epos.y; p0.z -= epos.z;
    5935             :                                 // compare with center of incident object
    5936             :                                 double  dist2 = HPmagnitude2(p0);
    5937             :                                 if (dist2 < cr*cr)
    5938             :                                         result = e2;
    5939             :                         }
    5940             :                 }
    5941             :         }
    5942             :         for (i = 0; i < ent_count; i++)
    5943             :                 [my_entities[i] release]; //    released
    5944             :         return result;
    5945             : }
    5946             : 
    5947             : 
    5948             : - (HPVector) getSafeVectorFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2
    5949             : {
    5950             :         // heuristic three
    5951             :         
    5952             :         if (!e1)
    5953             :         {
    5954             :                 OOLog(kOOLogParameterError, @"%@", @"***** No entity set in Universe getSafeVectorFromEntity:toDistance:fromPoint:");
    5955             :                 return kZeroHPVector;
    5956             :         }
    5957             :         
    5958             :         HPVector  f1;
    5959             :         HPVector  result = p2;
    5960             :         int i;
    5961             :         int ent_count = n_entities;
    5962             :         Entity* my_entities[ent_count];
    5963             :         for (i = 0; i < ent_count; i++)
    5964             :                 my_entities[i] = [sortedEntities[i] retain];    // retained
    5965             :         HPVector p1 = e1->position;
    5966             :         HPVector v1 = p2;
    5967             :         v1.x -= p1.x;   v1.y -= p1.y;   v1.z -= p1.z;   // vector from entity to p2
    5968             :         
    5969             :         double  nearest = sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z) - dist;  // length of vector
    5970             :         
    5971             :         if (v1.x || v1.y || v1.z)
    5972             :                 f1 = HPvector_normal(v1);   // unit vector in direction of p2 from p1
    5973             :         else
    5974             :                 f1 = make_HPvector(0, 0, 1);
    5975             :         
    5976             :         for (i = 0; i < ent_count; i++)
    5977             :         {
    5978             :                 Entity *e2 = my_entities[i];
    5979             :                 if ((e2 != e1)&&([e2 canCollide]))
    5980             :                 {
    5981             :                         HPVector epos = e2->position;
    5982             :                         epos.x -= p1.x; epos.y -= p1.y; epos.z -= p1.z;
    5983             :                         double d_forward = HPdot_product(epos,f1);
    5984             :                         if ((d_forward > 0)&&(d_forward < nearest))
    5985             :                         {
    5986             :                                 double cr = 1.20 * (e2->collision_radius + e1->collision_radius); //  20% safety margin
    5987             :                                 
    5988             :                                 HPVector p0 = e1->position;
    5989             :                                 p0.x += d_forward * f1.x;       p0.y += d_forward * f1.y;       p0.z += d_forward * f1.z;
    5990             :                                 // p0 holds nearest point on current course to center of incident object
    5991             :                                 
    5992             :                                 HPVector epos = e2->position;
    5993             :                                 p0.x -= epos.x; p0.y -= epos.y; p0.z -= epos.z;
    5994             :                                 // compare with center of incident object
    5995             :                                 
    5996             :                                 double  dist2 = p0.x * p0.x + p0.y * p0.y + p0.z * p0.z;
    5997             :                                 
    5998             :                                 if (dist2 < cr*cr)
    5999             :                                 {
    6000             :                                         result = e2->position;                       // center of incident object
    6001             :                                         nearest = d_forward;
    6002             :                                         
    6003             :                                         if (dist2 == 0.0)
    6004             :                                         {
    6005             :                                                 // ie. we're on a line through the object's center !
    6006             :                                                 // jitter the position somewhat!
    6007             :                                                 result.x += ((int)(Ranrot() % 1024) - 512)/512.0; //   -1.0 .. +1.0
    6008             :                                                 result.y += ((int)(Ranrot() % 1024) - 512)/512.0; //   -1.0 .. +1.0
    6009             :                                                 result.z += ((int)(Ranrot() % 1024) - 512)/512.0; //   -1.0 .. +1.0
    6010             :                                         }
    6011             :                                         
    6012             :                                         HPVector  nearest_point = p1;
    6013             :                                         nearest_point.x += d_forward * f1.x;    nearest_point.y += d_forward * f1.y;    nearest_point.z += d_forward * f1.z;
    6014             :                                         // nearest point now holds nearest point on line to center of incident object
    6015             :                                         
    6016             :                                         HPVector outward = nearest_point;
    6017             :                                         outward.x -= result.x;  outward.y -= result.y;  outward.z -= result.z;
    6018             :                                         if (outward.x||outward.y||outward.z)
    6019             :                                                 outward = HPvector_normal(outward);
    6020             :                                         else
    6021             :                                                 outward.y = 1.0;
    6022             :                                         // outward holds unit vector through the nearest point on the line from the center of incident object
    6023             :                                         
    6024             :                                         HPVector backward = p1;
    6025             :                                         backward.x -= result.x; backward.y -= result.y; backward.z -= result.z;
    6026             :                                         if (backward.x||backward.y||backward.z)
    6027             :                                                 backward = HPvector_normal(backward);
    6028             :                                         else
    6029             :                                                 backward.z = -1.0;
    6030             :                                         // backward holds unit vector from center of the incident object to the center of the ship
    6031             :                                         
    6032             :                                         HPVector dd = result;
    6033             :                                         dd.x -= p1.x; dd.y -= p1.y; dd.z -= p1.z;
    6034             :                                         double current_distance = HPmagnitude(dd);
    6035             :                                         
    6036             :                                         // sanity check current_distance
    6037             :                                         if (current_distance < cr * 1.25)    // 25% safety margin
    6038             :                                                 current_distance = cr * 1.25;
    6039             :                                         if (current_distance > cr * 5.0)     // up to 2 diameters away 
    6040             :                                                 current_distance = cr * 5.0;
    6041             :                                         
    6042             :                                         // choose a point that's three parts backward and one part outward
    6043             :                                         
    6044             :                                         result.x += 0.25 * (outward.x * current_distance) + 0.75 * (backward.x * current_distance);             // push 'out' by this amount
    6045             :                                         result.y += 0.25 * (outward.y * current_distance) + 0.75 * (backward.y * current_distance);
    6046             :                                         result.z += 0.25 * (outward.z * current_distance) + 0.75 * (backward.z * current_distance);
    6047             :                                         
    6048             :                                 }
    6049             :                         }
    6050             :                 }
    6051             :         }
    6052             :         for (i = 0; i < ent_count; i++)
    6053             :                 [my_entities[i] release]; //    released
    6054             :         return result;
    6055             : }
    6056             : 
    6057             : 
    6058             : - (ShipEntity*) addWreckageFrom:(ShipEntity *)ship withRole:(NSString *)wreckRole at:(HPVector)rpos scale:(GLfloat)scale lifetime:(GLfloat)lifetime
    6059             : {
    6060             :         ShipEntity* wreck = [UNIVERSE newShipWithRole:wreckRole];   // retain count = 1
    6061             :         Quaternion q;
    6062             :         if (wreck)
    6063             :         {
    6064             :                 GLfloat expected_mass = 0.1f * [ship mass] * (0.75 + 0.5 * randf());
    6065             :                 GLfloat wreck_mass = [wreck mass];
    6066             :                 GLfloat scale_factor = powf(expected_mass / wreck_mass, 0.33333333f) * scale;   // cube root of volume ratio
    6067             :                 [wreck rescaleBy:scale_factor writeToCache:NO];
    6068             : 
    6069             :                 [wreck setPosition:rpos];
    6070             : 
    6071             :                 [wreck setVelocity:[ship velocity]];
    6072             : 
    6073             :                 quaternion_set_random(&q);
    6074             :                 [wreck setOrientation:q];
    6075             :                                                         
    6076             :                 [wreck setTemperature: 1000.0];         // take 1000e heat damage per second
    6077             :                 [wreck setHeatInsulation: 1.0e7];       // very large! so it won't cool down
    6078             :                 [wreck setEnergy: lifetime];
    6079             :                                                         
    6080             :                 [wreck setIsWreckage:YES];
    6081             : 
    6082             :                 [UNIVERSE addEntity:wreck];     // STATUS_IN_FLIGHT, AI state GLOBAL
    6083             :                 [wreck performTumble];
    6084             :                 //      [wreck rescaleBy: 1.0/scale_factor];
    6085             :                 [wreck release];
    6086             :         }
    6087             :         return wreck;
    6088             : }
    6089             : 
    6090             : 
    6091             : 
    6092             : - (void) addLaserHitEffectsAt:(HPVector)pos against:(ShipEntity *)target damage:(float)damage color:(OOColor *)color
    6093             : {
    6094             :         // low energy, start getting small surface explosions
    6095             :         if ([target showDamage] && [target energy] < [target maxEnergy]/2)
    6096             :         {
    6097             :                 NSString *key = (randf() < 0.5) ? @"oolite-hull-spark" : @"oolite-hull-spark-b";
    6098             :                 NSDictionary *settings = [UNIVERSE explosionSetting:key];
    6099             :                 OOExplosionCloudEntity* burst = [OOExplosionCloudEntity explosionCloudFromEntity:target withSettings:settings];
    6100             :                 [burst setPosition:pos];
    6101             :                 [self addEntity: burst];
    6102             :                 if ([target energy] * randf() < damage)
    6103             :                 {
    6104             :                         ShipEntity *wreck = [self addWreckageFrom:target withRole:@"oolite-wreckage-chunk" at:pos scale:0.05 lifetime:(125.0+(randf()*200.0))];
    6105             :                         if (wreck)
    6106             :                         {
    6107             :                                 Vector direction = HPVectorToVector(HPvector_normal(HPvector_subtract(pos,[target position])));
    6108             :                                 [wreck setVelocity:vector_add([wreck velocity],vector_multiply_scalar(direction,10+20*randf()))];
    6109             :                         }
    6110             :                 }
    6111             :         }
    6112             :         else
    6113             :         {
    6114             :                 [self addEntity:[OOFlashEffectEntity laserFlashWithPosition:pos velocity:[target velocity] color:color]];
    6115             :         }
    6116             : }
    6117             : 
    6118             : 
    6119             : - (ShipEntity *) firstShipHitByLaserFromShip:(ShipEntity *)srcEntity inDirection:(OOWeaponFacing)direction offset:(Vector)offset gettingRangeFound:(GLfloat *)range_ptr
    6120             : {
    6121             :         if (srcEntity == nil) return nil;
    6122             :         
    6123             :         ShipEntity              *hit_entity = nil;
    6124             :         ShipEntity              *hit_subentity = nil;
    6125             :         HPVector                        p0 = [srcEntity position];
    6126             :         Quaternion              q1 = [srcEntity normalOrientation];
    6127             :         ShipEntity              *parent = [srcEntity parentEntity];
    6128             :         
    6129             :         if (parent)
    6130             :         {
    6131             :                 // we're a subentity!
    6132             :                 BoundingBox bbox = [srcEntity boundingBox];
    6133             :                 HPVector midfrontplane = make_HPvector(0.5 * (bbox.max.x + bbox.min.x), 0.5 * (bbox.max.y + bbox.min.y), bbox.max.z);
    6134             :                 p0 = [srcEntity absolutePositionForSubentityOffset:midfrontplane];
    6135             :                 q1 = [parent orientation];
    6136             :                 if ([parent isPlayer])  q1.w = -q1.w;
    6137             :         }
    6138             :         
    6139             :         double                  nearest = [srcEntity weaponRange];
    6140             :         int                             i;
    6141             :         int                             ent_count = n_entities;
    6142             :         int                             ship_count = 0;
    6143             :         ShipEntity              *my_entities[ent_count];
    6144             :         
    6145             :         for (i = 0; i < ent_count; i++)
    6146             :         {
    6147             :                 Entity* ent = sortedEntities[i];
    6148             :                 if (ent != srcEntity && ent != parent && [ent isShip] && [ent canCollide])
    6149             :                 {
    6150             :                         my_entities[ship_count++] = [(ShipEntity *)ent retain];
    6151             :                 }
    6152             :         }
    6153             :         
    6154             :         
    6155             :         Vector u1, f1, r1;
    6156             :         basis_vectors_from_quaternion(q1, &r1, &u1, &f1);
    6157             :         p0 = HPvector_add(p0, vectorToHPVector(OOVectorMultiplyMatrix(offset, OOMatrixFromBasisVectors(r1, u1, f1))));
    6158             :         
    6159             :         switch (direction)
    6160             :         {
    6161             :                 case WEAPON_FACING_FORWARD:
    6162             :                 case WEAPON_FACING_NONE:
    6163             :                         break;
    6164             :                         
    6165             :                 case WEAPON_FACING_AFT:
    6166             :                         quaternion_rotate_about_axis(&q1, u1, M_PI);
    6167             :                         break;
    6168             :                         
    6169             :                 case WEAPON_FACING_PORT:
    6170             :                         quaternion_rotate_about_axis(&q1, u1, M_PI/2.0);
    6171             :                         break;
    6172             :                         
    6173             :                 case WEAPON_FACING_STARBOARD:
    6174             :                         quaternion_rotate_about_axis(&q1, u1, -M_PI/2.0);
    6175             :                         break;
    6176             :         }
    6177             :         
    6178             :         basis_vectors_from_quaternion(q1, &r1, NULL, &f1);
    6179             :         HPVector p1 = HPvector_add(p0, vectorToHPVector(vector_multiply_scalar(f1, nearest)));  //endpoint
    6180             :         
    6181             :         for (i = 0; i < ship_count; i++)
    6182             :         {
    6183             :                 ShipEntity *e2 = my_entities[i];
    6184             :                 
    6185             :                 // check outermost bounding sphere
    6186             :                 GLfloat cr = e2->collision_radius;
    6187             :                 Vector rpos = HPVectorToVector(HPvector_subtract(e2->position, p0));
    6188             :                 Vector v_off = make_vector(dot_product(rpos, r1), dot_product(rpos, u1), dot_product(rpos, f1));
    6189             :                 if (v_off.z > 0.0 && v_off.z < nearest + cr &&                                                            // ahead AND within range
    6190             :                         v_off.x < cr && v_off.x > -cr && v_off.y < cr && v_off.y > -cr &&           // AND not off to one side or another
    6191             :                         v_off.x * v_off.x + v_off.y * v_off.y < cr * cr)                                             // AND not off to both sides
    6192             :                 {
    6193             :                         ShipEntity *entHit = nil;
    6194             :                         GLfloat hit = [(ShipEntity *)e2 doesHitLine:p0 :p1 :&entHit];       // octree detection
    6195             :                         
    6196             :                         if (hit > 0.0 && hit < nearest)
    6197             :                         {
    6198             :                                 if ([entHit isSubEntity])
    6199             :                                 {
    6200             :                                         hit_subentity = entHit;
    6201             :                                 }
    6202             :                                 hit_entity = e2;
    6203             :                                 nearest = hit;
    6204             :                                 p1 = HPvector_add(p0, vectorToHPVector(vector_multiply_scalar(f1, nearest)));
    6205             :                         }
    6206             :                 }
    6207             :         }
    6208             :         
    6209             :         if (hit_entity)
    6210             :         {
    6211             :                 // I think the above code does not guarantee that the closest hit_subentity belongs to the closest hit_entity.
    6212             :                 if (hit_subentity && [hit_subentity owner] == hit_entity)  [hit_entity setSubEntityTakingDamage:hit_subentity];
    6213             :                 
    6214             :                 if (range_ptr != NULL)
    6215             :                 {
    6216             :                         *range_ptr = nearest;
    6217             :                 }
    6218             :         }
    6219             :         
    6220             :         for (i = 0; i < ship_count; i++)  [my_entities[i] release]; //       released
    6221             :         
    6222             :         return hit_entity;
    6223             : }
    6224             : 
    6225             : 
    6226             : - (Entity *) firstEntityTargetedByPlayer
    6227             : {
    6228             :         PlayerEntity    *player = PLAYER;
    6229             :         Entity                  *hit_entity = nil;
    6230             :         OOScalar                nearest2 = SCANNER_MAX_RANGE - 100;     // 100m shorter than range at which target is lost
    6231             :         nearest2 *= nearest2;
    6232             :         int                             i;
    6233             :         int                             ent_count = n_entities;
    6234             :         int                             ship_count = 0;
    6235             :         Entity                  *my_entities[ent_count];
    6236             :         
    6237             :         for (i = 0; i < ent_count; i++)
    6238             :         {
    6239             :                 if (([sortedEntities[i] isShip] && ![sortedEntities[i] isPlayer]) || [sortedEntities[i] isWormhole])
    6240             :                 {
    6241             :                         my_entities[ship_count++] = [sortedEntities[i] retain];
    6242             :                 }
    6243             :         }
    6244             :         
    6245             :         Quaternion q1 = [player normalOrientation];
    6246             :         Vector u1, f1, r1;
    6247             :         basis_vectors_from_quaternion(q1, &r1, &u1, &f1);
    6248             :         Vector offset = [player weaponViewOffset];
    6249             :         
    6250             :         HPVector p1 = HPvector_add([player position], vectorToHPVector(OOVectorMultiplyMatrix(offset, OOMatrixFromBasisVectors(r1, u1, f1))));
    6251             :         
    6252             :         // Note: deliberately tied to view direction, not weapon facing. All custom views count as forward for targeting.
    6253             :         switch (viewDirection)
    6254             :         {
    6255             :                 case VIEW_AFT :
    6256             :                         quaternion_rotate_about_axis(&q1, u1, M_PI);
    6257             :                         break;
    6258             :                 case VIEW_PORT :
    6259             :                         quaternion_rotate_about_axis(&q1, u1, 0.5 * M_PI);
    6260             :                         break;
    6261             :                 case VIEW_STARBOARD :
    6262             :                         quaternion_rotate_about_axis(&q1, u1, -0.5 * M_PI);
    6263             :                         break;
    6264             :                 default:
    6265             :                         break;
    6266             :         }
    6267             :         basis_vectors_from_quaternion(q1, &r1, NULL, &f1);
    6268             :         
    6269             :         for (i = 0; i < ship_count; i++)
    6270             :         {
    6271             :                 Entity *e2 = my_entities[i];
    6272             :                 if ([e2 canCollide] && [e2 scanClass] != CLASS_NO_DRAW)
    6273             :                 {
    6274             :                         Vector rp = HPVectorToVector(HPvector_subtract([e2 position], p1));
    6275             :                         OOScalar dist2 = magnitude2(rp);
    6276             :                         if (dist2 < nearest2)
    6277             :                         {
    6278             :                                 OOScalar df = dot_product(f1, rp);
    6279             :                                 if (df > 0.0 && df * df < nearest2)
    6280             :                                 {
    6281             :                                         OOScalar du = dot_product(u1, rp);
    6282             :                                         OOScalar dr = dot_product(r1, rp);
    6283             :                                         OOScalar cr = [e2 collisionRadius];
    6284             :                                         if (du * du + dr * dr < cr * cr)
    6285             :                                         {
    6286             :                                                 hit_entity = e2;
    6287             :                                                 nearest2 = dist2;
    6288             :                                         }
    6289             :                                 }
    6290             :                         }
    6291             :                 }
    6292             :         }
    6293             :         // check for MASC'M
    6294             :         if (hit_entity != nil && [hit_entity isShip])
    6295             :         {
    6296             :                 ShipEntity* ship = (ShipEntity*)hit_entity;
    6297             :                 if ([ship isJammingScanning] && ![player hasMilitaryScannerFilter])
    6298             :                 {
    6299             :                         hit_entity = nil;
    6300             :                 }
    6301             :         }
    6302             :         
    6303             :         for (i = 0; i < ship_count; i++)
    6304             :         {
    6305             :                 [my_entities[i] release];
    6306             :         }
    6307             :         
    6308             :         return hit_entity;
    6309             : }
    6310             : 
    6311             : 
    6312             : - (Entity *) firstEntityTargetedByPlayerPrecisely
    6313             : {
    6314             :         OOWeaponFacing targetFacing;
    6315             :         Vector laserPortOffset = kZeroVector;
    6316             :         PlayerEntity *player = PLAYER;
    6317             : 
    6318             :         switch (viewDirection)
    6319             :         {
    6320             :                 case VIEW_FORWARD:
    6321             :                         targetFacing = WEAPON_FACING_FORWARD;
    6322             :                         laserPortOffset = [[player forwardWeaponOffset] oo_vectorAtIndex:0];
    6323             :                         break;
    6324             :                         
    6325             :                 case VIEW_AFT:
    6326             :                         targetFacing = WEAPON_FACING_AFT;
    6327             :                         laserPortOffset = [[player aftWeaponOffset] oo_vectorAtIndex:0];
    6328             :                         break;
    6329             :                         
    6330             :                 case VIEW_PORT:
    6331             :                         targetFacing = WEAPON_FACING_PORT;
    6332             :                         laserPortOffset = [[player portWeaponOffset] oo_vectorAtIndex:0];
    6333             :                         break;
    6334             :                         
    6335             :                 case VIEW_STARBOARD:
    6336             :                         targetFacing = WEAPON_FACING_STARBOARD;
    6337             :                         laserPortOffset = [[player starboardWeaponOffset] oo_vectorAtIndex:0];
    6338             :                         break;
    6339             :                         
    6340             :                 default:
    6341             :                         // Match behaviour of -firstEntityTargetedByPlayer.
    6342             :                         targetFacing = WEAPON_FACING_FORWARD;
    6343             :                         laserPortOffset = [[player forwardWeaponOffset] oo_vectorAtIndex:0];
    6344             :         }
    6345             :         
    6346             :         return [self firstShipHitByLaserFromShip:PLAYER inDirection:targetFacing offset:laserPortOffset gettingRangeFound:NULL];
    6347             : }
    6348             : 
    6349             : 
    6350             : - (NSArray *) entitiesWithinRange:(double)range ofEntity:(Entity *)entity
    6351             : {
    6352             :         if (entity == nil)  return nil;
    6353             :         
    6354             :         return [self findShipsMatchingPredicate:YESPredicate
    6355             :                                                                   parameter:NULL
    6356             :                                                                         inRange:range
    6357             :                                                                    ofEntity:entity];
    6358             : }
    6359             : 
    6360             : 
    6361             : - (unsigned) countShipsWithRole:(NSString *)role inRange:(double)range ofEntity:(Entity *)entity
    6362             : {
    6363             :         return [self countShipsMatchingPredicate:HasRolePredicate
    6364             :                                                            parameter:role
    6365             :                                                                  inRange:range
    6366             :                                                                 ofEntity:entity];
    6367             : }
    6368             : 
    6369             : 
    6370             : - (unsigned) countShipsWithRole:(NSString *)role
    6371             : {
    6372             :         return [self countShipsWithRole:role inRange:-1 ofEntity:nil];
    6373             : }
    6374             : 
    6375             : 
    6376             : - (unsigned) countShipsWithPrimaryRole:(NSString *)role inRange:(double)range ofEntity:(Entity *)entity
    6377             : {
    6378             :         return [self countShipsMatchingPredicate:HasPrimaryRolePredicate
    6379             :                                                            parameter:role
    6380             :                                                                  inRange:range
    6381             :                                                                 ofEntity:entity];
    6382             : }
    6383             : 
    6384             : 
    6385             : - (unsigned) countShipsWithScanClass:(OOScanClass)scanClass inRange:(double)range ofEntity:(Entity *)entity
    6386             : {
    6387             :         return [self countShipsMatchingPredicate:HasScanClassPredicate
    6388             :                                                            parameter:[NSNumber numberWithInt:scanClass]
    6389             :                                                                  inRange:range
    6390             :                                                                 ofEntity:entity];
    6391             : }
    6392             : 
    6393             : 
    6394             : - (unsigned) countShipsWithPrimaryRole:(NSString *)role
    6395             : {
    6396             :         return [self countShipsWithPrimaryRole:role inRange:-1 ofEntity:nil];
    6397             : }
    6398             : 
    6399             : 
    6400             : - (unsigned) countEntitiesMatchingPredicate:(EntityFilterPredicate)predicate
    6401             :                                                                   parameter:(void *)parameter
    6402             :                                                                         inRange:(double)range
    6403             :                                                                    ofEntity:(Entity *)e1
    6404             : {
    6405             :         unsigned                i, found = 0;
    6406             :         HPVector                        p1;
    6407             :         double                  distance, cr;
    6408             :         
    6409             :         if (predicate == NULL)  predicate = YESPredicate;
    6410             :         
    6411             :         if (e1 != nil)  p1 = e1->position;
    6412             :         else  p1 = kZeroHPVector;
    6413             :         
    6414             :         for (i = 0; i < n_entities; i++)
    6415             :         {
    6416             :                 Entity *e2 = sortedEntities[i];
    6417             :                 if (e2 != e1 && predicate(e2, parameter))
    6418             :                 {
    6419             :                         if (range < 0)  distance = -1;       // Negative range means infinity
    6420             :                         else
    6421             :                         {
    6422             :                                 cr = range + e2->collision_radius;
    6423             :                                 distance = HPdistance2(e2->position, p1) - cr * cr;
    6424             :                         }
    6425             :                         if (distance < 0)
    6426             :                         {
    6427             :                                 found++;
    6428             :                         }
    6429             :                 }
    6430             :         }
    6431             :         
    6432             :         return found;
    6433             : }
    6434             : 
    6435             : 
    6436             : - (unsigned) countShipsMatchingPredicate:(EntityFilterPredicate)predicate
    6437             :                                                            parameter:(void *)parameter
    6438             :                                                                  inRange:(double)range
    6439             :                                                                 ofEntity:(Entity *)entity
    6440             : {
    6441             :         if (predicate != NULL)
    6442             :         {
    6443             :                 BinaryOperationPredicateParameter param =
    6444             :                 {
    6445             :                         IsShipPredicate, NULL,
    6446             :                         predicate, parameter
    6447             :                 };
    6448             :                 
    6449             :                 return [self countEntitiesMatchingPredicate:ANDPredicate
    6450             :                                                                                   parameter:&param
    6451             :                                                                                         inRange:range
    6452             :                                                                                    ofEntity:entity];
    6453             :         }
    6454             :         else
    6455             :         {
    6456             :                 return [self countEntitiesMatchingPredicate:IsShipPredicate
    6457             :                                                                                   parameter:NULL
    6458             :                                                                                         inRange:range
    6459             :                                                                                    ofEntity:entity];
    6460             :         }
    6461             : }
    6462             : 
    6463             : 
    6464           0 : OOINLINE BOOL EntityInRange(HPVector p1, Entity *e2, float range)
    6465             : {
    6466             :         if (range < 0)  return YES;
    6467             :         float cr = range + e2->collision_radius;
    6468             :         return HPdistance2(e2->position,p1) < cr * cr;
    6469             : }
    6470             : 
    6471             : 
    6472             : // NOTE: OOJSSystem relies on this returning entities in distance-from-player order.
    6473             : // This can be easily changed by removing the [reference isPlayer] conditions in FindJSVisibleEntities().
    6474             : - (NSMutableArray *) findEntitiesMatchingPredicate:(EntityFilterPredicate)predicate
    6475             :                                                                   parameter:(void *)parameter
    6476             :                                                                         inRange:(double)range
    6477             :                                                                    ofEntity:(Entity *)e1
    6478             : {
    6479             :         OOJS_PROFILE_ENTER
    6480             :         
    6481             :         unsigned                i;
    6482             :         HPVector                        p1;
    6483             :         NSMutableArray  *result = nil;
    6484             :         
    6485             :         OOJSPauseTimeLimiter();
    6486             :         
    6487             :         if (predicate == NULL)  predicate = YESPredicate;
    6488             :         
    6489             :         result = [NSMutableArray arrayWithCapacity:n_entities];
    6490             :         
    6491             :         if (e1 != nil)  p1 = [e1 position];
    6492             :         else  p1 = kZeroHPVector;
    6493             :         
    6494             :         for (i = 0; i < n_entities; i++)
    6495             :         {
    6496             :                 Entity *e2 = sortedEntities[i];
    6497             :                 
    6498             :                 if (e1 != e2 &&
    6499             :                         EntityInRange(p1, e2, range) &&
    6500             :                         predicate(e2, parameter))
    6501             :                 {
    6502             :                         [result addObject:e2];
    6503             :                 }
    6504             :         }
    6505             :         
    6506             :         OOJSResumeTimeLimiter();
    6507             :         
    6508             :         return result;
    6509             :         
    6510             :         OOJS_PROFILE_EXIT
    6511             : }
    6512             : 
    6513             : 
    6514             : - (id) findOneEntityMatchingPredicate:(EntityFilterPredicate)predicate
    6515             :                                                         parameter:(void *)parameter
    6516             : {
    6517             :         unsigned                i;
    6518             :         Entity                  *candidate = nil;
    6519             :         
    6520             :         OOJSPauseTimeLimiter();
    6521             :         
    6522             :         if (predicate == NULL)  predicate = YESPredicate;
    6523             :         
    6524             :         for (i = 0; i < n_entities; i++)
    6525             :         {
    6526             :                 candidate = sortedEntities[i];
    6527             :                 if (predicate(candidate, parameter))  return candidate;
    6528             :         }
    6529             :         
    6530             :         OOJSResumeTimeLimiter();
    6531             :         
    6532             :         return nil;
    6533             : }
    6534             : 
    6535             : 
    6536             : - (NSMutableArray *) findShipsMatchingPredicate:(EntityFilterPredicate)predicate
    6537             :                                                                           parameter:(void *)parameter
    6538             :                                                                                 inRange:(double)range
    6539             :                                                                            ofEntity:(Entity *)entity
    6540             : {
    6541             :         if (predicate != NULL)
    6542             :         {
    6543             :                 BinaryOperationPredicateParameter param =
    6544             :                 {
    6545             :                         IsShipPredicate, NULL,
    6546             :                         predicate, parameter
    6547             :                 };
    6548             :                 
    6549             :                 return [self findEntitiesMatchingPredicate:ANDPredicate
    6550             :                                                                                  parameter:&param
    6551             :                                                                                    inRange:range
    6552             :                                                                                   ofEntity:entity];
    6553             :         }
    6554             :         else
    6555             :         {
    6556             :                 return [self findEntitiesMatchingPredicate:IsShipPredicate
    6557             :                                                                                  parameter:NULL
    6558             :                                                                                    inRange:range
    6559             :                                                                                   ofEntity:entity];
    6560             :         }
    6561             : }
    6562             : 
    6563             : 
    6564             : - (NSMutableArray *) findVisualEffectsMatchingPredicate:(EntityFilterPredicate)predicate
    6565             :                                                                           parameter:(void *)parameter
    6566             :                                                                                 inRange:(double)range
    6567             :                                                                            ofEntity:(Entity *)entity
    6568             : {
    6569             :         if (predicate != NULL)
    6570             :         {
    6571             :                 BinaryOperationPredicateParameter param =
    6572             :                 {
    6573             :                         IsVisualEffectPredicate, NULL,
    6574             :                         predicate, parameter
    6575             :                 };
    6576             :                 
    6577             :                 return [self findEntitiesMatchingPredicate:ANDPredicate
    6578             :                                                                                  parameter:&param
    6579             :                                                                                    inRange:range
    6580             :                                                                                   ofEntity:entity];
    6581             :         }
    6582             :         else
    6583             :         {
    6584             :                 return [self findEntitiesMatchingPredicate:IsVisualEffectPredicate
    6585             :                                                                                  parameter:NULL
    6586             :                                                                                    inRange:range
    6587             :                                                                                   ofEntity:entity];
    6588             :         }
    6589             : }
    6590             : 
    6591             : 
    6592             : - (id) nearestEntityMatchingPredicate:(EntityFilterPredicate)predicate
    6593             :                                                         parameter:(void *)parameter
    6594             :                                          relativeToEntity:(Entity *)entity
    6595             : {
    6596             :         unsigned                i;
    6597             :         HPVector                        p1;
    6598             :         float                   rangeSq = INFINITY;
    6599             :         id                              result = nil;
    6600             :         
    6601             :         if (predicate == NULL)  predicate = YESPredicate;
    6602             :         
    6603             :         if (entity != nil)  p1 = [entity position];
    6604             :         else  p1 = kZeroHPVector;
    6605             :         
    6606             :         for (i = 0; i < n_entities; i++)
    6607             :         {
    6608             :                 Entity *e2 = sortedEntities[i];
    6609             :                 float distanceToReferenceEntitySquared = (float)HPdistance2(p1, [e2 position]);
    6610             :                 
    6611             :                 if (entity != e2 &&
    6612             :                         distanceToReferenceEntitySquared < rangeSq &&
    6613             :                         predicate(e2, parameter))
    6614             :                 {
    6615             :                         result = e2;
    6616             :                         rangeSq = distanceToReferenceEntitySquared;
    6617             :                 }
    6618             :         }
    6619             :         
    6620             :         return [[result retain] autorelease];
    6621             : }
    6622             : 
    6623             : 
    6624             : - (id) nearestShipMatchingPredicate:(EntityFilterPredicate)predicate
    6625             :                                                   parameter:(void *)parameter
    6626             :                                    relativeToEntity:(Entity *)entity
    6627             : {
    6628             :         if (predicate != NULL)
    6629             :         {
    6630             :                 BinaryOperationPredicateParameter param =
    6631             :                 {
    6632             :                         IsShipPredicate, NULL,
    6633             :                         predicate, parameter
    6634             :                 };
    6635             :                 
    6636             :                 return [self nearestEntityMatchingPredicate:ANDPredicate
    6637             :                                                                                   parameter:&param
    6638             :                                                                    relativeToEntity:entity];
    6639             :         }
    6640             :         else
    6641             :         {
    6642             :                 return [self nearestEntityMatchingPredicate:IsShipPredicate
    6643             :                                                                                   parameter:NULL
    6644             :                                                                    relativeToEntity:entity];
    6645             :         }
    6646             : }
    6647             : 
    6648             : 
    6649             : - (OOTimeAbsolute) getTime
    6650             : {
    6651             :         return universal_time;
    6652             : }
    6653             : 
    6654             : 
    6655             : - (OOTimeDelta) getTimeDelta
    6656             : {
    6657             :         return time_delta;
    6658             : }
    6659             : 
    6660             : 
    6661             : - (void) findCollisionsAndShadows
    6662             : {
    6663             :         unsigned i;
    6664             :         
    6665             :         [universeRegion clearEntityList];
    6666             :         
    6667             :         for (i = 0; i < n_entities; i++)
    6668             :         {
    6669             :                 [universeRegion checkEntity:sortedEntities[i]]; // sorts out which region it's in
    6670             :         }
    6671             :         
    6672             :         if (![[self gameController] isGamePaused])
    6673             :         {
    6674             :                 [universeRegion findCollisions];
    6675             :         }
    6676             :         
    6677             :         // do check for entities that can't see the sun!
    6678             :         [universeRegion findShadowedEntities];
    6679             : }
    6680             : 
    6681             : 
    6682             : - (NSString*) collisionDescription
    6683             : {
    6684             :         if (universeRegion != nil)  return [universeRegion collisionDescription];
    6685             :         else  return @"-";
    6686             : }
    6687             : 
    6688             : 
    6689             : - (void) dumpCollisions
    6690             : {
    6691             :         dumpCollisionInfo = YES;
    6692             : }
    6693             : 
    6694             : 
    6695             : - (OOViewID) viewDirection
    6696             : {
    6697             :         return viewDirection;
    6698             : }
    6699             : 
    6700             : 
    6701             : - (void) setViewDirection:(OOViewID) vd
    6702             : {
    6703             :         NSString                *ms = nil;
    6704             :         BOOL                    guiSelected = NO;
    6705             :         
    6706             :         if ((viewDirection == vd) && (vd != VIEW_CUSTOM) && (!displayGUI))
    6707             :                 return;
    6708             :         
    6709             :         switch (vd)
    6710             :         {
    6711             :                 case VIEW_FORWARD:
    6712             :                         ms = DESC(@"forward-view-string");
    6713             :                         break;
    6714             :                         
    6715             :                 case VIEW_AFT:
    6716             :                         ms = DESC(@"aft-view-string");
    6717             :                         break;
    6718             :                         
    6719             :                 case VIEW_PORT:
    6720             :                         ms = DESC(@"port-view-string");
    6721             :                         break;
    6722             :                         
    6723             :                 case VIEW_STARBOARD:
    6724             :                         ms = DESC(@"starboard-view-string");
    6725             :                         break;
    6726             :                         
    6727             :                 case VIEW_CUSTOM:
    6728             :                         ms = [PLAYER customViewDescription];
    6729             :                         break;
    6730             :                         
    6731             :                 case VIEW_GUI_DISPLAY:
    6732             :                         [self setDisplayText:YES];
    6733             :                         [self setMainLightPosition:(Vector){ DEMO_LIGHT_POSITION }];
    6734             :                         guiSelected = YES;
    6735             :                         break;
    6736             :                         
    6737             :                 default:
    6738             :                         guiSelected = YES;
    6739             :                         break;
    6740             :         }
    6741             :         
    6742             :         if (guiSelected)
    6743             :         {
    6744             :                 [[self gameController] setMouseInteractionModeForUIWithMouseInteraction:NO];
    6745             :         }
    6746             :         else
    6747             :         {
    6748             :                 displayGUI = NO;   // switch off any text displays
    6749             :                 [[self gameController] setMouseInteractionModeForFlight];
    6750             :         }
    6751             :         
    6752             :         if (viewDirection != vd || viewDirection == VIEW_CUSTOM)
    6753             :         {
    6754             :                 #if (ALLOW_CUSTOM_VIEWS_WHILE_PAUSED)
    6755             :                 BOOL gamePaused = [[self gameController] isGamePaused];
    6756             :                 #else
    6757             :                 BOOL gamePaused = NO;
    6758             :                 #endif
    6759             :                 // view notifications for when the player switches to/from gui!
    6760             :                 //if (EXPECT(viewDirection == VIEW_GUI_DISPLAY || vd == VIEW_GUI_DISPLAY )) [PLAYER noteViewDidChangeFrom:viewDirection toView:vd];
    6761             :                 viewDirection = vd;
    6762             :                 if (ms && !gamePaused)
    6763             :                 {
    6764             :                         [self addMessage:ms forCount:3];
    6765             :                 }
    6766             :                 else if (gamePaused)
    6767             :                 {
    6768             :                         [message_gui clear];
    6769             :                 }
    6770             :         }
    6771             : }
    6772             : 
    6773             : 
    6774             : - (void) enterGUIViewModeWithMouseInteraction:(BOOL)mouseInteraction
    6775             : {
    6776             :         OOViewID vd = viewDirection;
    6777             :         [self setViewDirection:VIEW_GUI_DISPLAY];
    6778             :         if (viewDirection != vd) {
    6779             :                 PlayerEntity    *player = PLAYER;
    6780             :                 JSContext *context = OOJSAcquireContext();
    6781             :                 ShipScriptEvent(context, player, "viewDirectionChanged", OOJSValueFromViewID(context, viewDirection), OOJSValueFromViewID(context, vd));
    6782             :                 OOJSRelinquishContext(context);
    6783             :         }
    6784             :         [[self gameController] setMouseInteractionModeForUIWithMouseInteraction:mouseInteraction];
    6785             : }
    6786             : 
    6787             : 
    6788             : - (NSString *) soundNameForCustomSoundKey:(NSString *)key
    6789             : {
    6790             :         NSString                                *result = nil;
    6791             :         NSMutableSet                    *seen = nil;
    6792             :         id object = [customSounds objectForKey:key];
    6793             :         
    6794             :         if ([object isKindOfClass:[NSArray class]] && [object count] > 0)
    6795             :         {
    6796             :                 key = [object oo_stringAtIndex:Ranrot() % [object count]];
    6797             :         }
    6798             :         else
    6799             :         {
    6800             :                 object=nil;
    6801             :         }
    6802             :         
    6803             :         result = [[OOCacheManager sharedCache] objectForKey:key inCache:@"resolved custom sounds"];
    6804             :         if (result == nil)
    6805             :         {
    6806             :                 // Resolve sound, allowing indirection within customsounds.plist
    6807             :                 seen = [NSMutableSet set];
    6808             :                 result = key;
    6809             :                 if (object == nil || ([result hasPrefix:@"["] && [result hasSuffix:@"]"]))
    6810             :                 {
    6811             :                         for (;;)
    6812             :                         {
    6813             :                                 [seen addObject:result];
    6814             :                                 object = [customSounds objectForKey:result];
    6815             :                                 if( [object isKindOfClass:[NSArray class]] && [object count] > 0)
    6816             :                                 {
    6817             :                                         result = [object oo_stringAtIndex:Ranrot() % [object count]];
    6818             :                                         if ([key hasPrefix:@"["] && [key hasSuffix:@"]"]) key=result;
    6819             :                                 }
    6820             :                                 else
    6821             :                                 {
    6822             :                                         if ([object isKindOfClass:[NSString class]])
    6823             :                                                 result = object;
    6824             :                                         else
    6825             :                                                 result = nil;
    6826             :                                 }
    6827             :                                 if (result == nil || ![result hasPrefix:@"["] || ![result hasSuffix:@"]"])  break;
    6828             :                                 if ([seen containsObject:result])
    6829             :                                 {
    6830             :                                         OOLogERR(@"sound.customSounds.recursion", @"recursion in customsounds.plist for '%@' (at '%@'), no sound will be played.", key, result);
    6831             :                                         result = nil;
    6832             :                                         break;
    6833             :                                 }
    6834             :                         }
    6835             :                 }
    6836             :                 
    6837             :                 if (result == nil)  result = @"__oolite-no-sound";
    6838             :                 [[OOCacheManager sharedCache] setObject:result forKey:key inCache:@"resolved custom sounds"];
    6839             :         }
    6840             :         
    6841             :         if ([result isEqualToString:@"__oolite-no-sound"])
    6842             :         {
    6843             :                 OOLog(@"sound.customSounds", @"Could not resolve sound name in customsounds.plist for '%@', no sound will be played.", key);
    6844             :                 result = nil;
    6845             :         }
    6846             :         return result;
    6847             : }
    6848             : 
    6849             : 
    6850             : - (NSDictionary *) screenTextureDescriptorForKey:(NSString *)key
    6851             : {
    6852             :         id value = [screenBackgrounds objectForKey:key];
    6853             :         while ([value isKindOfClass:[NSArray class]])  value = [value objectAtIndex:Ranrot() % [value count]];
    6854             :         
    6855             :         if ([value isKindOfClass:[NSString class]])  value = [NSDictionary dictionaryWithObject:value forKey:@"name"];
    6856             :         else if (![value isKindOfClass:[NSDictionary class]])  value = nil;
    6857             :         
    6858             :         // Start loading the texture, and return nil if it doesn't exist.
    6859             :         if (![[self gui] preloadGUITexture:value])  value = nil;
    6860             :         
    6861             :         return value;
    6862             : }
    6863             : 
    6864             : 
    6865             : - (void) setScreenTextureDescriptorForKey:(NSString *)key descriptor:(NSDictionary *)desc
    6866             : {
    6867             :         NSMutableDictionary *sbCopy = [screenBackgrounds mutableCopy];
    6868             :         if (desc == nil)
    6869             :         {
    6870             :                 [sbCopy removeObjectForKey:key];
    6871             :         } 
    6872             :         else
    6873             :         {
    6874             :                 [sbCopy setObject:desc forKey:key];
    6875             :         }
    6876             :         [screenBackgrounds release];
    6877             :         screenBackgrounds = [sbCopy copy];
    6878             :         [sbCopy release];
    6879             : }
    6880             : 
    6881             : 
    6882             : - (void) clearPreviousMessage
    6883             : {
    6884             :         if (currentMessage)     [currentMessage release];
    6885             :         currentMessage = nil;
    6886             : }
    6887             : 
    6888             : 
    6889             : - (void) setMessageGuiBackgroundColor:(OOColor *)some_color
    6890             : {
    6891             :         [message_gui setBackgroundColor:some_color];
    6892             : }
    6893             : 
    6894             : 
    6895             : - (void) displayMessage:(NSString *) text forCount:(OOTimeDelta)count
    6896             : {
    6897             :         if (![currentMessage isEqual:text] || universal_time >= messageRepeatTime)
    6898             :         {
    6899             :                 if (currentMessage)     [currentMessage release];
    6900             :                 currentMessage = [text retain];
    6901             :                 messageRepeatTime=universal_time + 6.0;
    6902             :                 [self showGUIMessage:text withScroll:YES andColor:[message_gui textColor] overDuration:count];
    6903             :         }
    6904             : }
    6905             : 
    6906             : 
    6907             : - (void) displayCountdownMessage:(NSString *) text forCount:(OOTimeDelta)count
    6908             : {
    6909             :         if (![currentMessage isEqual:text] && universal_time >= countdown_messageRepeatTime)
    6910             :         {
    6911             :                 if (currentMessage)     [currentMessage release];
    6912             :                 currentMessage = [text retain];
    6913             :                 countdown_messageRepeatTime=universal_time + count;
    6914             :                 [self showGUIMessage:text withScroll:NO andColor:[message_gui textColor] overDuration:count];
    6915             :         }
    6916             : }
    6917             : 
    6918             : 
    6919             : - (void) addDelayedMessage:(NSString *)text forCount:(OOTimeDelta)count afterDelay:(double)delay
    6920             : {
    6921             :         NSMutableDictionary *msgDict = [NSMutableDictionary dictionaryWithCapacity:2];
    6922             :         [msgDict setObject:text forKey:@"message"];
    6923             :         [msgDict setObject:[NSNumber numberWithDouble:count] forKey:@"duration"];
    6924             :         [self performSelector:@selector(addDelayedMessage:) withObject:msgDict afterDelay:delay];
    6925             : }
    6926             : 
    6927             : 
    6928             : - (void) addDelayedMessage:(NSDictionary *) textdict
    6929             : {
    6930             :         NSString                *msg = nil;
    6931             :         OOTimeDelta             msg_duration;
    6932             :         
    6933             :         msg = [textdict oo_stringForKey:@"message"];
    6934             :         if (msg == nil)  return;
    6935             :         msg_duration = [textdict oo_nonNegativeDoubleForKey:@"duration" defaultValue:3.0];
    6936             :         
    6937             :         [self addMessage:msg forCount:msg_duration];
    6938             : }
    6939             : 
    6940             : 
    6941             : - (void) addMessage:(NSString *)text forCount:(OOTimeDelta)count
    6942             : {
    6943             :         [self addMessage:text forCount:count forceDisplay:NO];
    6944             : }
    6945             : 
    6946             : 
    6947           0 : - (void) speakWithSubstitutions:(NSString *)text
    6948             : {
    6949             : #if OOLITE_SPEECH_SYNTH
    6950             :         //speech synthesis
    6951             :         
    6952             :         PlayerEntity* player = PLAYER;
    6953             :         if ([player isSpeechOn] > OOSPEECHSETTINGS_OFF)
    6954             :         {
    6955             :                 NSString        *systemSaid = nil;
    6956             :                 NSString        *h_systemSaid = nil;
    6957             :                 
    6958             :                 NSString        *systemName = [self getSystemName:systemID];
    6959             :                 
    6960             :                 systemSaid = systemName;
    6961             :                 
    6962             :                 NSString        *h_systemName = [self getSystemName:[player targetSystemID]];
    6963             :                 h_systemSaid = h_systemName;
    6964             :                 
    6965             :                 NSString        *spokenText = text;
    6966             :                 if (speechArray != nil)
    6967             :                 {
    6968             :                         NSEnumerator    *speechEnumerator = nil;
    6969             :                         NSArray                 *thePair = nil;
    6970             :                         
    6971             :                         for (speechEnumerator = [speechArray objectEnumerator]; (thePair = [speechEnumerator nextObject]); )
    6972             :                         {
    6973             :                                 NSString *original_phrase = [thePair oo_stringAtIndex:0];
    6974             :                                 
    6975             :                                 NSUInteger replacementIndex;
    6976             : #if OOLITE_MAC_OS_X
    6977             :                                 replacementIndex = 1;
    6978             : #elif OOLITE_ESPEAK
    6979             :                                 replacementIndex = [thePair count] > 2 ? 2 : 1;
    6980             : #endif
    6981             :                                 
    6982             :                                 NSString *replacement_phrase = [thePair oo_stringAtIndex:replacementIndex];
    6983             :                                 if (![replacement_phrase isEqualToString:@"_"])
    6984             :                                 {
    6985             :                                         spokenText = [spokenText stringByReplacingOccurrencesOfString:original_phrase withString:replacement_phrase];
    6986             :                                 }
    6987             :                         }
    6988             :                         spokenText = [spokenText stringByReplacingOccurrencesOfString:systemName withString:systemSaid];
    6989             :                         spokenText = [spokenText stringByReplacingOccurrencesOfString:h_systemName withString:h_systemSaid];
    6990             :                 }
    6991             :                 [self stopSpeaking];
    6992             :                 [self startSpeakingString:spokenText];
    6993             :         }
    6994             : #endif  // OOLITE_SPEECH_SYNTH
    6995             : }
    6996             : 
    6997             : 
    6998             : - (void) addMessage:(NSString *) text forCount:(OOTimeDelta) count forceDisplay:(BOOL) forceDisplay
    6999             : {
    7000             :         if (![currentMessage isEqual:text] || forceDisplay || universal_time >= messageRepeatTime)
    7001             :         {
    7002             :                 if ([PLAYER isSpeechOn] == OOSPEECHSETTINGS_ALL)
    7003             :                 {
    7004             :                         [self speakWithSubstitutions:text];
    7005             :                 }
    7006             :                 
    7007             :                 [self showGUIMessage:text withScroll:YES andColor:[message_gui textColor] overDuration:count];
    7008             : 
    7009             :                 [PLAYER doScriptEvent:OOJSID("consoleMessageReceived") withArgument:text];
    7010             :                                 
    7011             :                 [currentMessage release];
    7012             :                 currentMessage = [text retain];
    7013             :                 messageRepeatTime=universal_time + 6.0;
    7014             :         }
    7015             : }
    7016             : 
    7017             : 
    7018             : - (void) addCommsMessage:(NSString *)text forCount:(OOTimeDelta)count
    7019             : {
    7020             :         [self addCommsMessage:text forCount:count andShowComms:_autoCommLog logOnly:NO];
    7021             : }
    7022             : 
    7023             : 
    7024             : - (void) addCommsMessage:(NSString *)text forCount:(OOTimeDelta)count andShowComms:(BOOL)showComms logOnly:(BOOL)logOnly
    7025             : {
    7026             :         if ([PLAYER showDemoShips]) return;
    7027             :         
    7028             :         NSString *expandedMessage = OOExpand(text);
    7029             :         
    7030             :         if (![currentMessage isEqualToString:expandedMessage] || universal_time >= messageRepeatTime)
    7031             :         {
    7032             :                 PlayerEntity* player = PLAYER;
    7033             :                 
    7034             :                 if (!logOnly)
    7035             :                 {
    7036             :                         if ([player isSpeechOn] >= OOSPEECHSETTINGS_COMMS)
    7037             :                         {
    7038             :                                 // EMMSTRAN: should say "Incoming message from ..." when prefixed with sender name.
    7039             :                                 NSString *format = OOExpandKey(@"speech-synthesis-incoming-message-@");
    7040             :                                 [self speakWithSubstitutions:[NSString stringWithFormat:format, expandedMessage]];
    7041             :                         }
    7042             :                         
    7043             :                         [self showGUIMessage:expandedMessage withScroll:YES andColor:[message_gui textCommsColor] overDuration:count];
    7044             :                         
    7045             :                         [currentMessage release];
    7046             :                         currentMessage = [expandedMessage retain];
    7047             :                         messageRepeatTime=universal_time + 6.0;
    7048             :                 }
    7049             :                 
    7050             :                 [comm_log_gui printLongText:expandedMessage align:GUI_ALIGN_LEFT color:nil fadeTime:0.0 key:nil addToArray:[player commLog]];
    7051             :                 
    7052             :                 if (showComms)  [self showCommsLog:6.0];
    7053             :         }
    7054             : }
    7055             : 
    7056             : 
    7057             : - (void) showCommsLog:(OOTimeDelta)how_long
    7058             : {
    7059             :         [comm_log_gui setAlpha:1.0];
    7060             :         if (![self permanentCommLog]) [comm_log_gui fadeOutFromTime:[self getTime] overDuration:how_long];
    7061             : }
    7062             : 
    7063             : 
    7064             : - (void) showGUIMessage:(NSString *)text withScroll:(BOOL)scroll andColor:(OOColor *)selectedColor overDuration:(OOTimeDelta)how_long
    7065             : {
    7066             :         if (scroll)
    7067             :         {
    7068             :                 [message_gui printLongText:text align:GUI_ALIGN_CENTER color:selectedColor fadeTime:how_long key:nil addToArray:nil];
    7069             :         }
    7070             :         else
    7071             :         {
    7072             :                 [message_gui printLineNoScroll:text align:GUI_ALIGN_CENTER color:selectedColor fadeTime:how_long key:nil addToArray:nil];
    7073             :         }
    7074             :         [message_gui setAlpha:1.0f];
    7075             : }
    7076             : 
    7077             : 
    7078           0 : - (void) repopulateSystem
    7079             : {
    7080             :         if (EXPECT_NOT([PLAYER status] == STATUS_START_GAME))
    7081             :         {
    7082             :                 return; // no need to be adding ships as this is not a "real" game
    7083             :         }
    7084             :         JSContext                       *context = OOJSAcquireContext();
    7085             :         [PLAYER doWorldScriptEvent:OOJSIDFromString(system_repopulator) inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit];
    7086             :         OOJSRelinquishContext(context);
    7087             :         next_repopulation = SYSTEM_REPOPULATION_INTERVAL;
    7088             : }
    7089             : 
    7090             : 
    7091             : - (void) update:(OOTimeDelta)inDeltaT
    7092             : {
    7093             :         volatile OOTimeDelta delta_t = inDeltaT * [self timeAccelerationFactor];
    7094             :         NSUInteger sessionID = _sessionID;
    7095             :         OOLog(@"universe.profile.update", @"%@", @"Begin update");
    7096             :         if (EXPECT(!no_update))
    7097             :         {
    7098             :                 next_repopulation -= delta_t;
    7099             :                 if (next_repopulation < 0)
    7100             :                 {
    7101             :                         [self repopulateSystem];
    7102             :                 }
    7103             : 
    7104             :                 unsigned        i, ent_count = n_entities;
    7105             :                 Entity          *my_entities[ent_count];
    7106             :                 
    7107             :                 [self verifyEntitySessionIDs];
    7108             :                 
    7109             :                 // use a retained copy so this can't be changed under us.
    7110             :                 for (i = 0; i < ent_count; i++)
    7111             :                 {
    7112             :                         my_entities[i] = [sortedEntities[i] retain];    // explicitly retain each one
    7113             :                 }
    7114             :                 
    7115             :                 NSString * volatile update_stage = @"initialisation";
    7116             : #ifndef NDEBUG
    7117             :                 id volatile update_stage_param = nil;
    7118             : #endif
    7119             :                 
    7120             :                 @try
    7121             :                 {
    7122             :                         PlayerEntity *player = PLAYER;
    7123             :                         
    7124             :                         skyClearColor[0] = 0.0;
    7125             :                         skyClearColor[1] = 0.0;
    7126             :                         skyClearColor[2] = 0.0;
    7127             :                         skyClearColor[3] = 0.0;
    7128             :                         
    7129             :                         time_delta = delta_t;
    7130             :                         universal_time += delta_t;
    7131             :                         
    7132             :                         if (EXPECT_NOT([player showDemoShips] && [player guiScreen] == GUI_SCREEN_SHIPLIBRARY))
    7133             :                         {
    7134             :                                 update_stage = @"demo management";
    7135             :                                 
    7136             :                                 if (universal_time >= demo_stage_time)
    7137             :                                 {
    7138             :                                         if (ent_count > 1)
    7139             :                                         {
    7140             :                                                 Vector          vel;
    7141             :                                                 Quaternion      q2 = kIdentityQuaternion;
    7142             :                                                 
    7143             :                                                 quaternion_rotate_about_y(&q2,M_PI);
    7144             :                                                 
    7145             :                                                 switch (demo_stage)
    7146             :                                                 {
    7147             :                                                         case DEMO_FLY_IN:
    7148             :                                                                 [demo_ship setPosition:[demo_ship destination]];        // ideal position
    7149             :                                                                 demo_stage = DEMO_SHOW_THING;
    7150             :                                                                 demo_stage_time = universal_time + 300.0;
    7151             :                                                                 break;
    7152             :                                                         case DEMO_SHOW_THING:
    7153             :                                                                 vel = make_vector(0, 0, DEMO2_VANISHING_DISTANCE * demo_ship->collision_radius * 6.0);
    7154             :                                                                 [demo_ship setVelocity:vel];
    7155             :                                                                 demo_stage = DEMO_FLY_OUT;
    7156             :                                                                 demo_stage_time = universal_time + 0.25;
    7157             :                                                                 break;
    7158             :                                                         case DEMO_FLY_OUT:
    7159             :                                                                 // change the demo_ship here
    7160             :                                                                 [self removeEntity:demo_ship];
    7161             :                                                                 demo_ship = nil;
    7162             :                                                                 
    7163             : /*                                                              NSString                *shipDesc = nil;
    7164             :                                                                 NSString                *shipName = nil;
    7165             :                                                                 NSDictionary    *shipDict = nil; */
    7166             :                                                                 
    7167             :                                                                 demo_ship_subindex = (demo_ship_subindex + 1) % [[demo_ships objectAtIndex:demo_ship_index] count];
    7168             :                                                                 demo_ship = [self newShipWithName:[[self demoShipData] oo_stringForKey:kOODemoShipKey] usePlayerProxy:NO];
    7169             :                                                                 
    7170             :                                                                 if (demo_ship != nil)
    7171             :                                                                 {
    7172             :                                                                         [demo_ship removeEquipmentItem:@"EQ_SHIELD_BOOSTER"];
    7173             :                                                                         [demo_ship removeEquipmentItem:@"EQ_SHIELD_ENHANCER"];
    7174             : 
    7175             :                                                                         [demo_ship switchAITo:@"nullAI.plist"];
    7176             :                                                                         [demo_ship setOrientation:q2];
    7177             :                                                                         [demo_ship setScanClass: CLASS_NO_DRAW];
    7178             :                                                                         [demo_ship setStatus: STATUS_COCKPIT_DISPLAY]; // prevents it getting escorts on addition
    7179             :                                                                         [demo_ship setDemoShip: 1.0f];
    7180             :                                                                         [demo_ship setDemoStartTime: universal_time];
    7181             :                                                                         if ([self addEntity:demo_ship])
    7182             :                                                                         {
    7183             :                                                                                 [demo_ship release];            // We now own a reference through the entity list.
    7184             :                                                                                 [demo_ship setStatus:STATUS_COCKPIT_DISPLAY];
    7185             :                                                                                 demo_start_z=DEMO2_VANISHING_DISTANCE * demo_ship->collision_radius;
    7186             :                                                                                 [demo_ship setPositionX:0.0f y:0.0f z:demo_start_z];
    7187             :                                                                                 [demo_ship setDestination: make_HPvector(0.0f, 0.0f, demo_start_z * 0.01f)];    // ideal position
    7188             :                                                                                 [demo_ship setVelocity:kZeroVector];
    7189             :                                                                                 [demo_ship setScanClass: CLASS_NO_DRAW];
    7190             : //                                                                              [gui setText:shipName != nil ? shipName : [demo_ship displayName] forRow:19 align:GUI_ALIGN_CENTER];
    7191             :                                                                                 
    7192             :                                                                                 [self setLibraryTextForDemoShip];
    7193             : 
    7194             :                                                                                 demo_stage = DEMO_FLY_IN;
    7195             :                                                                                 demo_start_time=universal_time;
    7196             :                                                                                 demo_stage_time = demo_start_time + DEMO2_FLY_IN_STAGE_TIME;
    7197             :                                                                         }
    7198             :                                                                         else
    7199             :                                                                         {
    7200             :                                                                                 demo_ship = nil;
    7201             :                                                                         }
    7202             :                                                                 }
    7203             :                                                                 break;
    7204             :                                                 }
    7205             :                                         }
    7206             :                                 }
    7207             :                                 else if (demo_stage == DEMO_FLY_IN)
    7208             :                                 {
    7209             :                                         GLfloat delta = (universal_time - demo_start_time) / DEMO2_FLY_IN_STAGE_TIME;
    7210             :                                         [demo_ship setPositionX:0.0f y:[demo_ship destination].y * delta z:demo_start_z + ([demo_ship destination].z - demo_start_z) * delta ];
    7211             :                                 }
    7212             :                         }
    7213             :                         
    7214             :                         update_stage = @"update:entity";
    7215             :                         NSMutableSet *zombies = nil;
    7216             :                         OOLog(@"universe.profile.update", @"%@", update_stage);
    7217             :                         for (i = 0; i < ent_count; i++)
    7218             :                         {
    7219             :                                 Entity *thing = my_entities[i];
    7220             : #ifndef NDEBUG
    7221             :                                 update_stage_param = thing;
    7222             :                                 update_stage = @"update:entity [%@]";
    7223             : #endif
    7224             :                                 // Game Over code depends on regular delta_t updates to the dead player entity. Ignore the player entity, even when dead.
    7225             :                                 if (EXPECT_NOT([thing status] == STATUS_DEAD && ![entitiesDeadThisUpdate containsObject:thing] && ![thing isPlayer]))
    7226             :                                 {
    7227             :                                         if (zombies == nil)  zombies = [NSMutableSet set];
    7228             :                                         [zombies addObject:thing];
    7229             :                                         continue;
    7230             :                                 }
    7231             :                                 
    7232             :                                 [thing update:delta_t];
    7233             :                                 if (EXPECT_NOT(sessionID != _sessionID))
    7234             :                                 {
    7235             :                                         // Game was reset (in player update); end this update: cycle.
    7236             :                                         break;
    7237             :                                 }
    7238             :                                 
    7239             : #ifndef NDEBUG
    7240             :                                 update_stage = @"update:list maintenance [%@]";
    7241             : #endif
    7242             :                                 
    7243             :                                 // maintain distance-from-player list
    7244             :                                 GLfloat z_distance = thing->zero_distance;
    7245             :                                 
    7246             :                                 int index = thing->zero_index;
    7247             :                                 while (index > 0 && z_distance < sortedEntities[index - 1]->zero_distance)
    7248             :                                 {
    7249             :                                         sortedEntities[index] = sortedEntities[index - 1];      // bubble up the list, usually by just one position
    7250             :                                         sortedEntities[index - 1] = thing;
    7251             :                                         thing->zero_index = index - 1;
    7252             :                                         sortedEntities[index]->zero_index = index;
    7253             :                                         index--;
    7254             :                                 }
    7255             :                                 
    7256             :                                 // update deterministic AI
    7257             :                                 if ([thing isShip])
    7258             :                                 {
    7259             : #ifndef NDEBUG
    7260             :                                         update_stage = @"update:think [%@]";
    7261             : #endif
    7262             :                                         AI* theShipsAI = [(ShipEntity *)thing getAI];
    7263             :                                         if (theShipsAI)
    7264             :                                         {
    7265             :                                                 double thinkTime = [theShipsAI nextThinkTime];
    7266             :                                                 if ((universal_time > thinkTime)||(thinkTime == 0.0))
    7267             :                                                 {
    7268             :                                                         [theShipsAI setNextThinkTime:universal_time + [theShipsAI thinkTimeInterval]];
    7269             :                                                         [theShipsAI think];
    7270             :                                                 }
    7271             :                                         }
    7272             :                                 }
    7273             :                         }
    7274             : #ifndef NDEBUG
    7275             :                 update_stage_param = nil;
    7276             : #endif
    7277             :                         
    7278             :                         if (zombies != nil)
    7279             :                         {
    7280             :                                 update_stage = @"shootin' zombies";
    7281             :                                 NSEnumerator *zombieEnum = nil;
    7282             :                                 Entity *zombie = nil;
    7283             :                                 for (zombieEnum = [zombies objectEnumerator]; (zombie = [zombieEnum nextObject]); )
    7284             :                                 {
    7285             :                                         OOLogERR(@"universe.zombie", @"Found dead entity %@ in active entity list, removing. This is an internal error, please report it.", zombie);
    7286             :                                         [self removeEntity:zombie];
    7287             :                                 }
    7288             :                         }
    7289             :                         
    7290             :                         // Maintain x/y/z order lists
    7291             :                         update_stage = @"updating linked lists";
    7292             :                         OOLog(@"universe.profile.update", @"%@", update_stage);
    7293             :                         for (i = 0; i < ent_count; i++)
    7294             :                         {
    7295             :                                 [my_entities[i] updateLinkedLists];
    7296             :                         }
    7297             :                         
    7298             :                         // detect collisions and light ships that can see the sun
    7299             :                         
    7300             :                         update_stage = @"collision and shadow detection";
    7301             :                         OOLog(@"universe.profile.update", @"%@", update_stage);
    7302             :                         [self filterSortedLists];
    7303             :                         [self findCollisionsAndShadows];
    7304             :                         
    7305             :                         // do any required check and maintenance of linked lists
    7306             :                         
    7307             :                         if (doLinkedListMaintenanceThisUpdate)
    7308             :                         {
    7309             :                                 MaintainLinkedLists(self);
    7310             :                                 doLinkedListMaintenanceThisUpdate = NO;
    7311             :                         }
    7312             :                 }
    7313             :                 @catch (NSException *exception)
    7314             :                 {
    7315             :                         if ([[exception name] hasPrefix:@"Oolite"])
    7316             :                         {
    7317             :                                 [self handleOoliteException:exception];
    7318             :                         }
    7319             :                         else
    7320             :                         {
    7321             : #ifndef NDEBUG
    7322             :                                 if (update_stage_param != nil)  update_stage = [NSString stringWithFormat:update_stage, update_stage_param];
    7323             : #endif
    7324             :                                 OOLog(kOOLogException, @"***** Exception during [%@] in [Universe update:] : %@ : %@ *****", update_stage, [exception name], [exception reason]);
    7325             :                                 @throw exception;
    7326             :                         }
    7327             :                 }
    7328             :                 
    7329             :                 // dispose of the non-mutable copy and everything it references neatly
    7330             :                 update_stage = @"clean up";
    7331             :                 OOLog(@"universe.profile.update", @"%@", update_stage);
    7332             :                 for (i = 0; i < ent_count; i++)
    7333             :                 {
    7334             :                         [my_entities[i] release];       // explicitly release each one
    7335             :                 }
    7336             :                 /* Garbage collection is going to result in a significant
    7337             :                  * pause when it happens. Doing it here is better than doing
    7338             :                  * it in the middle of the update when it might slow a
    7339             :                  * function into the timelimiter through no fault of its
    7340             :                  * own. JS_MaybeGC will only run a GC when it's
    7341             :                  * necessary. Merely checking is not significant in terms of
    7342             :                  * time. - CIM: 4/8/2013
    7343             :                  */
    7344             :                 update_stage = @"JS Garbage Collection";
    7345             :                 OOLog(@"universe.profile.update", @"%@", update_stage); 
    7346             : #ifndef NDEBUG
    7347             :                 JSContext *context = OOJSAcquireContext(); 
    7348             :                 uint32 gcbytes1 = JS_GetGCParameter(JS_GetRuntime(context),JSGC_BYTES);
    7349             :                 OOJSRelinquishContext(context);
    7350             : #endif
    7351             :                 [[OOJavaScriptEngine sharedEngine] garbageCollectionOpportunity:NO];
    7352             : #ifndef NDEBUG
    7353             :                 context = OOJSAcquireContext(); 
    7354             :                 uint32 gcbytes2 = JS_GetGCParameter(JS_GetRuntime(context),JSGC_BYTES);
    7355             :                 OOJSRelinquishContext(context);
    7356             :                 if (gcbytes2 < gcbytes1)
    7357             :                 {
    7358             :                         OOLog(@"universe.profile.jsgc",@"Unplanned JS Garbage Collection from %d to %d",gcbytes1,gcbytes2);
    7359             :                 }
    7360             : #endif
    7361             : 
    7362             : 
    7363             :         }
    7364             :         else
    7365             :         {
    7366             :                 // always perform player's dead updates: allows deferred JS resets.
    7367             :                 if ([PLAYER status] == STATUS_DEAD)  [PLAYER update:delta_t];
    7368             :         }
    7369             :         
    7370             :         [entitiesDeadThisUpdate autorelease];
    7371             :         entitiesDeadThisUpdate = nil;
    7372             :         entitiesDeadThisUpdate = [[NSMutableSet alloc] initWithCapacity:n_entities];
    7373             :         
    7374             : #if NEW_PLANETS
    7375             :         [self prunePreloadingPlanetMaterials];
    7376             : #endif
    7377             : 
    7378             :         OOLog(@"universe.profile.update", @"%@", @"Update complete");
    7379             : }
    7380             : 
    7381             : 
    7382             : #ifndef NDEBUG
    7383             : - (double) timeAccelerationFactor
    7384             : {
    7385             :         return timeAccelerationFactor;
    7386             : }
    7387             : 
    7388             : 
    7389             : - (void) setTimeAccelerationFactor:(double)newTimeAccelerationFactor
    7390             : {
    7391             :         if (newTimeAccelerationFactor < TIME_ACCELERATION_FACTOR_MIN || newTimeAccelerationFactor > TIME_ACCELERATION_FACTOR_MAX)
    7392             :         {
    7393             :                 newTimeAccelerationFactor = TIME_ACCELERATION_FACTOR_DEFAULT;
    7394             :         }
    7395             :         timeAccelerationFactor = newTimeAccelerationFactor;
    7396             : }
    7397             : #else
    7398             : - (double) timeAccelerationFactor
    7399             : {
    7400             :         return 1.0;
    7401             : }
    7402             : 
    7403             : 
    7404             : - (void) setTimeAccelerationFactor:(double)newTimeAccelerationFactor
    7405             : {
    7406             : }
    7407             : #endif
    7408             : 
    7409             : 
    7410             : - (BOOL) ECMVisualFXEnabled
    7411             : {
    7412             :         return ECMVisualFXEnabled;
    7413             : }
    7414             : 
    7415             : 
    7416             : - (void) setECMVisualFXEnabled:(BOOL)isEnabled
    7417             : {
    7418             :         ECMVisualFXEnabled = isEnabled;
    7419             : }
    7420             : 
    7421             : 
    7422             : - (void) filterSortedLists
    7423             : {
    7424             :         /*
    7425             :         Eric, 17-10-2010: raised the area to be not filtered out, from the combined collision size to 2x this size.
    7426             :         This allows this filtered list to be used also for proximity_alert and not only for collisions. Before the
    7427             :         proximity_alert could only trigger when already very near a collision. To late for ships to react.
    7428             :         This does raise the number of entities in the collision chain with as result that the number of pairs to compair
    7429             :         becomes significant larger. However, almost all of these extra pairs are dealt with by a simple distance check.
    7430             :         I currently see no noticeable negative effect while playing, but this change might still give some trouble I missed.
    7431             :         */
    7432             :         Entity  *e0, *next, *prev;
    7433             :         OOHPScalar start, finish, next_start, next_finish, prev_start, prev_finish;
    7434             :         
    7435             :         // using the z_list - set or clear collisionTestFilter and clear collision_chain
    7436             :         e0 = z_list_start;
    7437             :         while (e0)
    7438             :         {
    7439             :                 e0->collisionTestFilter = [e0 canCollide]?0:3;
    7440             :                 e0->collision_chain = nil;
    7441             :                 e0 = e0->z_next;
    7442             :         }
    7443             :         // done.
    7444             :         
    7445             :         /* We need to check the lists in both ascending and descending order
    7446             :          * to catch some cases with interposition of entities. We set cTF =
    7447             :          * 1 on the way up, and |= 2 on the way down. Therefore it's only 3
    7448             :          * at the end of the list if it was caught both ways on the same
    7449             :          * list. - CIM: 7/11/2012 */
    7450             : 
    7451             :         // start with the z_list
    7452             :         e0 = z_list_start;
    7453             :         while (e0)
    7454             :         {
    7455             :                 // here we are either at the start of the list or just past a gap
    7456             :                 start = e0->position.z - 2.0f * e0->collision_radius;
    7457             :                 finish = start + 4.0f * e0->collision_radius;
    7458             :                 next = e0->z_next;
    7459             :                 while ((next)&&(next->collisionTestFilter == 3))     // next has been eliminated from the list of possible colliders - so skip it
    7460             :                         next = next->z_next;
    7461             :                 if (next)
    7462             :                 {
    7463             :                         next_start = next->position.z - 2.0f * next->collision_radius;
    7464             :                         if (next_start < finish)
    7465             :                         {
    7466             :                                 // e0 and next overlap
    7467             :                                 while ((next)&&(next_start < finish))
    7468             :                                 {
    7469             :                                         // skip forward to the next gap or the end of the list
    7470             :                                         next_finish = next_start + 4.0f * next->collision_radius;
    7471             :                                         if (next_finish > finish)
    7472             :                                                 finish = next_finish;
    7473             :                                         e0 = next;
    7474             :                                         next = e0->z_next;
    7475             :                                         while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated - so skip it
    7476             :                                                 next = next->z_next;
    7477             :                                         if (next)
    7478             :                                                 next_start = next->position.z - 2.0f * next->collision_radius;
    7479             :                                 }
    7480             :                                 // now either (next == nil) or (next_start >= finish)-which would imply a gap!
    7481             :                         }
    7482             :                         else
    7483             :                         {
    7484             :                                 // e0 is a singleton
    7485             :                                 e0->collisionTestFilter = 1;
    7486             :                         }
    7487             :                 }
    7488             :                 else // (next == nil)
    7489             :                 {
    7490             :                         // at the end of the list so e0 is a singleton
    7491             :                         e0->collisionTestFilter = 1;
    7492             :                 }
    7493             :                 e0 = next;
    7494             :         }
    7495             :         // list filtered upwards, now filter downwards
    7496             :         // e0 currently = end of z list
    7497             :         while (e0)
    7498             :         {
    7499             :                 // here we are either at the start of the list or just past a gap
    7500             :                 start = e0->position.z + 2.0f * e0->collision_radius;
    7501             :                 finish = start - 4.0f * e0->collision_radius;
    7502             :                 prev = e0->z_previous;
    7503             :                 while ((prev)&&(prev->collisionTestFilter == 3))     // next has been eliminated from the list of possible colliders - so skip it
    7504             :                         prev = prev->z_previous;
    7505             :                 if (prev)
    7506             :                 {
    7507             :                         prev_start = prev->position.z + 2.0f * prev->collision_radius;
    7508             :                         if (prev_start > finish)
    7509             :                         {
    7510             :                                 // e0 and next overlap
    7511             :                                 while ((prev)&&(prev_start > finish))
    7512             :                                 {
    7513             :                                         // skip forward to the next gap or the end of the list
    7514             :                                         prev_finish = prev_start - 4.0f * prev->collision_radius;
    7515             :                                         if (prev_finish < finish)
    7516             :                                                 finish = prev_finish;
    7517             :                                         e0 = prev;
    7518             :                                         prev = e0->z_previous;
    7519             :                                         while ((prev)&&(prev->collisionTestFilter==3))       // next has been eliminated - so skip it
    7520             :                                                 prev = prev->z_previous;
    7521             :                                         if (prev)
    7522             :                                                 prev_start = prev->position.z + 2.0f * prev->collision_radius;
    7523             :                                 }
    7524             :                                 // now either (prev == nil) or (prev_start <= finish)-which would imply a gap!
    7525             :                         }
    7526             :                         else
    7527             :                         {
    7528             :                                 // e0 is a singleton
    7529             :                                 e0->collisionTestFilter |= 2;
    7530             :                         }
    7531             :                 }
    7532             :                 else // (prev == nil)
    7533             :                 {
    7534             :                         // at the end of the list so e0 is a singleton
    7535             :                         e0->collisionTestFilter |= 2;
    7536             :                 }
    7537             :                 e0 = prev;
    7538             :         }
    7539             :         // done! list filtered
    7540             :         
    7541             :         // then with the y_list, z_list singletons now create more gaps..
    7542             :         e0 = y_list_start;
    7543             :         while (e0)
    7544             :         {
    7545             :                 // here we are either at the start of the list or just past a gap
    7546             :                 start = e0->position.y - 2.0f * e0->collision_radius;
    7547             :                 finish = start + 4.0f * e0->collision_radius;
    7548             :                 next = e0->y_next;
    7549             :                 while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated from the list of possible colliders - so skip it
    7550             :                         next = next->y_next;
    7551             :                 if (next)
    7552             :                 {
    7553             :                         
    7554             :                         next_start = next->position.y - 2.0f * next->collision_radius;
    7555             :                         if (next_start < finish)
    7556             :                         {
    7557             :                                 // e0 and next overlap
    7558             :                                 while ((next)&&(next_start < finish))
    7559             :                                 {
    7560             :                                         // skip forward to the next gap or the end of the list
    7561             :                                         next_finish = next_start + 4.0f * next->collision_radius;
    7562             :                                         if (next_finish > finish)
    7563             :                                                 finish = next_finish;
    7564             :                                         e0 = next;
    7565             :                                         next = e0->y_next;
    7566             :                                         while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated - so skip it
    7567             :                                                 next = next->y_next;
    7568             :                                         if (next)
    7569             :                                                 next_start = next->position.y - 2.0f * next->collision_radius;
    7570             :                                 }
    7571             :                                 // now either (next == nil) or (next_start >= finish)-which would imply a gap!
    7572             :                         }
    7573             :                         else
    7574             :                         {
    7575             :                                 // e0 is a singleton
    7576             :                                 e0->collisionTestFilter = 1;
    7577             :                         }
    7578             :                 }
    7579             :                 else // (next == nil)
    7580             :                 {
    7581             :                         // at the end of the list so e0 is a singleton
    7582             :                         e0->collisionTestFilter = 1;
    7583             :                 }
    7584             :                 e0 = next;
    7585             :         }
    7586             :         // list filtered upwards, now filter downwards
    7587             :         // e0 currently = end of y list
    7588             :         while (e0)
    7589             :         {
    7590             :                 // here we are either at the start of the list or just past a gap
    7591             :                 start = e0->position.y + 2.0f * e0->collision_radius;
    7592             :                 finish = start - 4.0f * e0->collision_radius;
    7593             :                 prev = e0->y_previous;
    7594             :                 while ((prev)&&(prev->collisionTestFilter == 3))     // next has been eliminated from the list of possible colliders - so skip it
    7595             :                         prev = prev->y_previous;
    7596             :                 if (prev)
    7597             :                 {
    7598             :                         prev_start = prev->position.y + 2.0f * prev->collision_radius;
    7599             :                         if (prev_start > finish)
    7600             :                         {
    7601             :                                 // e0 and next overlap
    7602             :                                 while ((prev)&&(prev_start > finish))
    7603             :                                 {
    7604             :                                         // skip forward to the next gap or the end of the list
    7605             :                                         prev_finish = prev_start - 4.0f * prev->collision_radius;
    7606             :                                         if (prev_finish < finish)
    7607             :                                                 finish = prev_finish;
    7608             :                                         e0 = prev;
    7609             :                                         prev = e0->y_previous;
    7610             :                                         while ((prev)&&(prev->collisionTestFilter==3))       // next has been eliminated - so skip it
    7611             :                                                 prev = prev->y_previous;
    7612             :                                         if (prev)
    7613             :                                                 prev_start = prev->position.y + 2.0f * prev->collision_radius;
    7614             :                                 }
    7615             :                                 // now either (prev == nil) or (prev_start <= finish)-which would imply a gap!
    7616             :                         }
    7617             :                         else
    7618             :                         {
    7619             :                                 // e0 is a singleton
    7620             :                                 e0->collisionTestFilter |= 2;
    7621             :                         }
    7622             :                 }
    7623             :                 else // (prev == nil)
    7624             :                 {
    7625             :                         // at the end of the list so e0 is a singleton
    7626             :                         e0->collisionTestFilter |= 2;
    7627             :                 }
    7628             :                 e0 = prev;
    7629             :         }
    7630             :         // done! list filtered
    7631             :         
    7632             :         // finish with the x_list
    7633             :         e0 = x_list_start;
    7634             :         while (e0)
    7635             :         {
    7636             :                 // here we are either at the start of the list or just past a gap
    7637             :                 start = e0->position.x - 2.0f * e0->collision_radius;
    7638             :                 finish = start + 4.0f * e0->collision_radius;
    7639             :                 next = e0->x_next;
    7640             :                 while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated from the list of possible colliders - so skip it
    7641             :                         next = next->x_next;
    7642             :                 if (next)
    7643             :                 {
    7644             :                         next_start = next->position.x - 2.0f * next->collision_radius;
    7645             :                         if (next_start < finish)
    7646             :                         {
    7647             :                                 // e0 and next overlap
    7648             :                                 while ((next)&&(next_start < finish))
    7649             :                                 {
    7650             :                                         // skip forward to the next gap or the end of the list
    7651             :                                         next_finish = next_start + 4.0f * next->collision_radius;
    7652             :                                         if (next_finish > finish)
    7653             :                                                 finish = next_finish;
    7654             :                                         e0 = next;
    7655             :                                         next = e0->x_next;
    7656             :                                         while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated - so skip it
    7657             :                                                 next = next->x_next;
    7658             :                                         if (next)
    7659             :                                                 next_start = next->position.x - 2.0f * next->collision_radius;
    7660             :                                 }
    7661             :                                 // now either (next == nil) or (next_start >= finish)-which would imply a gap!
    7662             :                         }
    7663             :                         else
    7664             :                         {
    7665             :                                 // e0 is a singleton
    7666             :                                 e0->collisionTestFilter = 1;
    7667             :                         }
    7668             :                 }
    7669             :                 else // (next == nil)
    7670             :                 {
    7671             :                         // at the end of the list so e0 is a singleton
    7672             :                         e0->collisionTestFilter = 1;
    7673             :                 }
    7674             :                 e0 = next;
    7675             :         }
    7676             :         // list filtered upwards, now filter downwards
    7677             :         // e0 currently = end of x list
    7678             :         while (e0)
    7679             :         {
    7680             :                 // here we are either at the start of the list or just past a gap
    7681             :                 start = e0->position.x + 2.0f * e0->collision_radius;
    7682             :                 finish = start - 4.0f * e0->collision_radius;
    7683             :                 prev = e0->x_previous;
    7684             :                 while ((prev)&&(prev->collisionTestFilter == 3))     // next has been eliminated from the list of possible colliders - so skip it
    7685             :                         prev = prev->x_previous;
    7686             :                 if (prev)
    7687             :                 {
    7688             :                         prev_start = prev->position.x + 2.0f * prev->collision_radius;
    7689             :                         if (prev_start > finish)
    7690             :                         {
    7691             :                                 // e0 and next overlap
    7692             :                                 while ((prev)&&(prev_start > finish))
    7693             :                                 {
    7694             :                                         // skip forward to the next gap or the end of the list
    7695             :                                         prev_finish = prev_start - 4.0f * prev->collision_radius;
    7696             :                                         if (prev_finish < finish)
    7697             :                                                 finish = prev_finish;
    7698             :                                         e0 = prev;
    7699             :                                         prev = e0->x_previous;
    7700             :                                         while ((prev)&&(prev->collisionTestFilter==3))       // next has been eliminated - so skip it
    7701             :                                                 prev = prev->x_previous;
    7702             :                                         if (prev)
    7703             :                                                 prev_start = prev->position.x + 2.0f * prev->collision_radius;
    7704             :                                 }
    7705             :                                 // now either (prev == nil) or (prev_start <= finish)-which would imply a gap!
    7706             :                         }
    7707             :                         else
    7708             :                         {
    7709             :                                 // e0 is a singleton
    7710             :                                 e0->collisionTestFilter |= 2;
    7711             :                         }
    7712             :                 }
    7713             :                 else // (prev == nil)
    7714             :                 {
    7715             :                         // at the end of the list so e0 is a singleton
    7716             :                         e0->collisionTestFilter |= 2;
    7717             :                 }
    7718             :                 e0 = prev;
    7719             :         }
    7720             :         // done! list filtered
    7721             :         
    7722             :         // repeat the y_list - so gaps from the x_list influence singletons
    7723             :         e0 = y_list_start;
    7724             :         while (e0)
    7725             :         {
    7726             :                 // here we are either at the start of the list or just past a gap
    7727             :                 start = e0->position.y - 2.0f * e0->collision_radius;
    7728             :                 finish = start + 4.0f * e0->collision_radius;
    7729             :                 next = e0->y_next;
    7730             :                 while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated from the list of possible colliders - so skip it
    7731             :                         next = next->y_next;
    7732             :                 if (next)
    7733             :                 {
    7734             :                         next_start = next->position.y - 2.0f * next->collision_radius;
    7735             :                         if (next_start < finish)
    7736             :                         {
    7737             :                                 // e0 and next overlap
    7738             :                                 while ((next)&&(next_start < finish))
    7739             :                                 {
    7740             :                                         // skip forward to the next gap or the end of the list
    7741             :                                         next_finish = next_start + 4.0f * next->collision_radius;
    7742             :                                         if (next_finish > finish)
    7743             :                                                 finish = next_finish;
    7744             :                                         e0 = next;
    7745             :                                         next = e0->y_next;
    7746             :                                         while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated - so skip it
    7747             :                                                 next = next->y_next;
    7748             :                                         if (next)
    7749             :                                                 next_start = next->position.y - 2.0f * next->collision_radius;
    7750             :                                 }
    7751             :                                 // now either (next == nil) or (next_start >= finish)-which would imply a gap!
    7752             :                         }
    7753             :                         else
    7754             :                         {
    7755             :                                 // e0 is a singleton
    7756             :                                 e0->collisionTestFilter = 1;
    7757             :                         }
    7758             :                 }
    7759             :                 else // (next == nil)
    7760             :                 {
    7761             :                         // at the end of the list so e0 is a singleton
    7762             :                         e0->collisionTestFilter = 1;
    7763             :                 }
    7764             :                 e0 = next;
    7765             :         }
    7766             :         // e0 currently = end of y list
    7767             :         while (e0)
    7768             :         {
    7769             :                 // here we are either at the start of the list or just past a gap
    7770             :                 start = e0->position.y + 2.0f * e0->collision_radius;
    7771             :                 finish = start - 4.0f * e0->collision_radius;
    7772             :                 prev = e0->y_previous;
    7773             :                 while ((prev)&&(prev->collisionTestFilter == 3))     // next has been eliminated from the list of possible colliders - so skip it
    7774             :                         prev = prev->y_previous;
    7775             :                 if (prev)
    7776             :                 {
    7777             :                         prev_start = prev->position.y + 2.0f * prev->collision_radius;
    7778             :                         if (prev_start > finish)
    7779             :                         {
    7780             :                                 // e0 and next overlap
    7781             :                                 while ((prev)&&(prev_start > finish))
    7782             :                                 {
    7783             :                                         // skip forward to the next gap or the end of the list
    7784             :                                         prev_finish = prev_start - 4.0f * prev->collision_radius;
    7785             :                                         if (prev_finish < finish)
    7786             :                                                 finish = prev_finish;
    7787             :                                         e0 = prev;
    7788             :                                         prev = e0->y_previous;
    7789             :                                         while ((prev)&&(prev->collisionTestFilter==3))       // next has been eliminated - so skip it
    7790             :                                                 prev = prev->y_previous;
    7791             :                                         if (prev)
    7792             :                                                 prev_start = prev->position.y + 2.0f * prev->collision_radius;
    7793             :                                 }
    7794             :                                 // now either (prev == nil) or (prev_start <= finish)-which would imply a gap!
    7795             :                         }
    7796             :                         else
    7797             :                         {
    7798             :                                 // e0 is a singleton
    7799             :                                 e0->collisionTestFilter |= 2;
    7800             :                         }
    7801             :                 }
    7802             :                 else // (prev == nil)
    7803             :                 {
    7804             :                         // at the end of the list so e0 is a singleton
    7805             :                         e0->collisionTestFilter |= 2;
    7806             :                 }
    7807             :                 e0 = prev;
    7808             :         }
    7809             :         // done! list filtered
    7810             :         
    7811             :         // finally, repeat the z_list - this time building collision chains...
    7812             :         e0 = z_list_start;
    7813             :         while (e0)
    7814             :         {
    7815             :                 // here we are either at the start of the list or just past a gap
    7816             :                 start = e0->position.z - 2.0f * e0->collision_radius;
    7817             :                 finish = start + 4.0f * e0->collision_radius;
    7818             :                 next = e0->z_next;
    7819             :                 while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated from the list of possible colliders - so skip it
    7820             :                         next = next->z_next;
    7821             :                 if (next)
    7822             :                 {
    7823             :                         next_start = next->position.z - 2.0f * next->collision_radius;
    7824             :                         if (next_start < finish)
    7825             :                         {
    7826             :                                 // e0 and next overlap
    7827             :                                 while ((next)&&(next_start < finish))
    7828             :                                 {
    7829             :                                         // chain e0 to next in collision
    7830             :                                         e0->collision_chain = next;
    7831             :                                         // skip forward to the next gap or the end of the list
    7832             :                                         next_finish = next_start + 4.0f * next->collision_radius;
    7833             :                                         if (next_finish > finish)
    7834             :                                                 finish = next_finish;
    7835             :                                         e0 = next;
    7836             :                                         next = e0->z_next;
    7837             :                                         while ((next)&&(next->collisionTestFilter==3))       // next has been eliminated - so skip it
    7838             :                                                 next = next->z_next;
    7839             :                                         if (next)
    7840             :                                                 next_start = next->position.z - 2.0f * next->collision_radius;
    7841             :                                 }
    7842             :                                 // now either (next == nil) or (next_start >= finish)-which would imply a gap!
    7843             :                                 e0->collision_chain = nil;   // end the collision chain
    7844             :                         }
    7845             :                         else
    7846             :                         {
    7847             :                                 // e0 is a singleton
    7848             :                                 e0->collisionTestFilter = 1;
    7849             :                         }
    7850             :                 }
    7851             :                 else // (next == nil)
    7852             :                 {
    7853             :                         // at the end of the list so e0 is a singleton
    7854             :                         e0->collisionTestFilter = 1;
    7855             :                 }
    7856             :                 e0 = next;
    7857             :         }
    7858             :         // e0 currently = end of z list
    7859             :         while (e0)
    7860             :         {
    7861             :                 // here we are either at the start of the list or just past a gap
    7862             :                 start = e0->position.z + 2.0f * e0->collision_radius;
    7863             :                 finish = start - 4.0f * e0->collision_radius;
    7864             :                 prev = e0->z_previous;
    7865             :                 while ((prev)&&(prev->collisionTestFilter == 3))     // next has been eliminated from the list of possible colliders - so skip it
    7866             :                         prev = prev->z_previous;
    7867             :                 if (prev)
    7868             :                 {
    7869             :                         prev_start = prev->position.z + 2.0f * prev->collision_radius;
    7870             :                         if (prev_start > finish)
    7871             :                         {
    7872             :                                 // e0 and next overlap
    7873             :                                 while ((prev)&&(prev_start > finish))
    7874             :                                 {
    7875             :                                         // e0 probably already in collision chain at this point, but if it
    7876             :                                         // isn't we have to insert it
    7877             :                                         if (prev->collision_chain != e0)
    7878             :                                         {
    7879             :                                                 if (prev->collision_chain == nil)
    7880             :                                                 {
    7881             :                                                         // easy, just add it onto the start of the chain
    7882             :                                                         prev->collision_chain = e0;
    7883             :                                                 }
    7884             :                                                 else
    7885             :                                                 {
    7886             :                                                         /* not nil and not e0 shouldn't be possible, I think.
    7887             :                                                          * if it is, that implies that e0->collision_chain is nil, though
    7888             :                                                          * so: */
    7889             :                                                         if (e0->collision_chain == nil)
    7890             :                                                         {
    7891             :                                                                 e0->collision_chain = prev->collision_chain;
    7892             :                                                                 prev->collision_chain = e0;
    7893             :                                                         }
    7894             :                                                         else
    7895             :                                                         {
    7896             :                                                                 /* This shouldn't happen... If it does, we accept
    7897             :                                                                  * missing collision checks and move on */
    7898             :                                                                 OOLog(@"general.error.inconsistentState",@"Unexpected state in collision chain builder prev=%@, prev->c=%@, e0=%@, e0->c=%@",prev,prev->collision_chain,e0,e0->collision_chain);
    7899             :                                                         }
    7900             :                                                 }
    7901             :                                         }
    7902             :                                         // skip forward to the next gap or the end of the list
    7903             :                                         prev_finish = prev_start - 4.0f * prev->collision_radius;
    7904             :                                         if (prev_finish < finish)
    7905             :                                                 finish = prev_finish;
    7906             :                                         e0 = prev;
    7907             :                                         prev = e0->z_previous;
    7908             :                                         while ((prev)&&(prev->collisionTestFilter==3))       // next has been eliminated - so skip it
    7909             :                                                 prev = prev->z_previous;
    7910             :                                         if (prev)
    7911             :                                                 prev_start = prev->position.z + 2.0f * prev->collision_radius;
    7912             :                                 }
    7913             :                                 // now either (prev == nil) or (prev_start <= finish)-which would imply a gap!
    7914             : 
    7915             :                                 // all the collision chains are already terminated somewhere
    7916             :                                 // at this point so no need to set e0->collision_chain = nil
    7917             :                         }
    7918             :                         else
    7919             :                         {
    7920             :                                 // e0 is a singleton
    7921             :                                 e0->collisionTestFilter |= 2;
    7922             :                         }
    7923             :                 }
    7924             :                 else // (prev == nil)
    7925             :                 {
    7926             :                         // at the end of the list so e0 is a singleton
    7927             :                         e0->collisionTestFilter |= 2;
    7928             :                 }
    7929             :                 e0 = prev;
    7930             :         }
    7931             :         // done! list filtered
    7932             : }
    7933             : 
    7934             : 
    7935             : - (void) setGalaxyTo:(OOGalaxyID) g
    7936             : {
    7937             :         [self setGalaxyTo:g andReinit:NO];
    7938             : }
    7939             : 
    7940             : 
    7941             : - (void) setGalaxyTo:(OOGalaxyID) g andReinit:(BOOL) forced
    7942             : {
    7943             :         int                                             i;
    7944             :         NSAutoreleasePool               *pool = nil;
    7945             :         
    7946             :         if (galaxyID != g || forced) {
    7947             :                 galaxyID = g;
    7948             :                 
    7949             :                 // systems
    7950             :                 pool = [[NSAutoreleasePool alloc] init];
    7951             :                         
    7952             :                 for (i = 0; i < 256; i++)
    7953             :                 {
    7954             :                         if (system_names[i])
    7955             :                         {
    7956             :                                 [system_names[i] release];
    7957             :                         }
    7958             :                         system_names[i] = [[systemManager getProperty:@"name" forSystem:i inGalaxy:g] retain];
    7959             : 
    7960             :                 }
    7961             :                 [pool release];
    7962             :         }
    7963             : }
    7964             : 
    7965             : 
    7966             : - (void) setSystemTo:(OOSystemID) s
    7967             : {
    7968             :         NSDictionary    *systemData;
    7969             :         PlayerEntity    *player = PLAYER;
    7970             :         OOEconomyID             economy;
    7971             :         NSString                *scriptName;
    7972             :         
    7973             :         [self setGalaxyTo: [player galaxyNumber]];
    7974             :         
    7975             :         systemID = s;
    7976             :         targetSystemID = s;
    7977             :         
    7978             :         systemData = [self generateSystemData:targetSystemID];
    7979             :         economy = [systemData  oo_unsignedCharForKey:KEY_ECONOMY];
    7980             :         scriptName = [systemData  oo_stringForKey:@"market_script" defaultValue:nil];
    7981             :         
    7982             :         DESTROY(commodityMarket);
    7983             :         commodityMarket = [[commodities generateMarketForSystemWithEconomy:economy andScript:scriptName] retain];
    7984             : }
    7985             : 
    7986             : 
    7987             : - (OOSystemID) currentSystemID
    7988             : {
    7989             :         return systemID;
    7990             : }
    7991             : 
    7992             : 
    7993             : - (NSDictionary *) descriptions
    7994             : {
    7995             :         if (_descriptions == nil)
    7996             :         {
    7997             :                 // Load internal descriptions.plist for use in early init, OXP verifier etc.
    7998             :                 // It will be replaced by merged version later if running the game normally.
    7999             :                 _descriptions = [NSDictionary dictionaryWithContentsOfFile:[[[ResourceManager builtInPath]
    8000             :                                                                                                                                          stringByAppendingPathComponent:@"Config"]
    8001             :                                                                                                                                         stringByAppendingPathComponent:@"descriptions.plist"]];
    8002             :                 
    8003             :                 [self verifyDescriptions];
    8004             :         }
    8005             :         return _descriptions;
    8006             : }
    8007             : 
    8008             : 
    8009           0 : static void VerifyDesc(NSString *key, id desc);
    8010             : 
    8011             : 
    8012           0 : static void VerifyDescString(NSString *key, NSString *desc)
    8013             : {
    8014             :         if ([desc rangeOfString:@"%n"].location != NSNotFound)
    8015             :         {
    8016             :                 OOLog(@"descriptions.verify.percentN", @"***** FATAL: descriptions.plist entry \"%@\" contains the dangerous control sequence %%n.", key);
    8017             :                 exit(EXIT_FAILURE);
    8018             :         }
    8019             : }
    8020             : 
    8021             : 
    8022           0 : static void VerifyDescArray(NSString *key, NSArray *desc)
    8023             : {
    8024             :         id subDesc = nil;
    8025             :         foreach (subDesc, desc)
    8026             :         {
    8027             :                 VerifyDesc(key, subDesc);
    8028             :         }
    8029             : }
    8030             : 
    8031             : 
    8032             : static void VerifyDesc(NSString *key, id desc)
    8033             : {
    8034             :         if ([desc isKindOfClass:[NSString class]])
    8035             :         {
    8036             :                 VerifyDescString(key, desc);
    8037             :         }
    8038             :         else if ([desc isKindOfClass:[NSArray class]])
    8039             :         {
    8040             :                 VerifyDescArray(key, desc);
    8041             :         }
    8042             :         else if ([desc isKindOfClass:[NSNumber class]])
    8043             :         {
    8044             :                 // No verification needed.
    8045             :         }
    8046             :         else
    8047             :         {
    8048             :                 OOLogERR(@"descriptions.verify.badType", @"***** FATAL: descriptions.plist entry for \"%@\" is neither a string nor an array.", key);
    8049             :                 exit(EXIT_FAILURE);
    8050             :         }
    8051             : }
    8052             : 
    8053             : 
    8054           0 : - (void) verifyDescriptions
    8055             : {
    8056             :         /*
    8057             :                 Ensure that no descriptions.plist entries contain the %n format code,
    8058             :                 which can be used to smash the stack and potentially call arbitrary
    8059             :                 functions.
    8060             :                 
    8061             :                 %n is deliberately not supported in Foundation/CoreFoundation under
    8062             :                 Mac OS X, but unfortunately GNUstep implements it.
    8063             :                 -- Ahruman 2011-05-05
    8064             :         */
    8065             :         
    8066             :         NSString *key = nil;
    8067             :         if (_descriptions == nil)
    8068             :         {
    8069             :                 OOLog(@"descriptions.verify", @"%@", @"***** FATAL: Tried to verify descriptions, but descriptions was nil - unable to load any descriptions.plist file.");
    8070             :                 exit(EXIT_FAILURE);
    8071             :         }
    8072             :         foreachkey (key, _descriptions)
    8073             :         {
    8074             :                 VerifyDesc(key, [_descriptions objectForKey:key]);
    8075             :         }
    8076             : }
    8077             : 
    8078             : 
    8079           0 : - (void) loadDescriptions
    8080             : {
    8081             :         [_descriptions autorelease];
    8082             :         _descriptions = [[ResourceManager dictionaryFromFilesNamed:@"descriptions.plist" inFolder:@"Config" andMerge:YES] retain];
    8083             :         [self verifyDescriptions];
    8084             : }
    8085             : 
    8086             : 
    8087             : - (NSDictionary *) explosionSetting:(NSString *)explosion
    8088             : {
    8089             :         return [explosionSettings oo_dictionaryForKey:explosion defaultValue:nil];
    8090             : }
    8091             : 
    8092             : 
    8093             : - (NSArray *) scenarios
    8094             : {
    8095             :         return _scenarios;
    8096             : }
    8097             : 
    8098             : 
    8099           0 : - (void) loadScenarios
    8100             : {
    8101             :         [_scenarios autorelease];
    8102             :         _scenarios = [[ResourceManager arrayFromFilesNamed:@"scenarios.plist" inFolder:@"Config" andMerge:YES] retain];
    8103             : }
    8104             : 
    8105             : 
    8106             : - (NSDictionary *) characters
    8107             : {
    8108             :         return characters;
    8109             : }
    8110             : 
    8111             : 
    8112             : - (NSDictionary *) missiontext
    8113             : {
    8114             :         return missiontext;
    8115             : }
    8116             : 
    8117             : 
    8118             : - (NSString *)descriptionForKey:(NSString *)key
    8119             : {
    8120             :         return [self chooseStringForKey:key inDictionary:[self descriptions]];
    8121             : }
    8122             : 
    8123             : 
    8124             : - (NSString *)descriptionForArrayKey:(NSString *)key index:(unsigned)index
    8125             : {
    8126             :         NSArray *array = [[self descriptions] oo_arrayForKey:key];
    8127             :         if ([array count] <= index)  return nil;     // Catches nil array
    8128             :         return [array objectAtIndex:index];
    8129             : }
    8130             : 
    8131             : 
    8132             : - (BOOL) descriptionBooleanForKey:(NSString *)key
    8133             : {
    8134             :         return [[self descriptions] oo_boolForKey:key];
    8135             : }
    8136             : 
    8137             : 
    8138             : - (OOSystemDescriptionManager *) systemManager
    8139             : {
    8140             :         return systemManager;
    8141             : }
    8142             : 
    8143             : 
    8144             : - (NSString *) keyForPlanetOverridesForSystem:(OOSystemID) s inGalaxy:(OOGalaxyID) g
    8145             : {
    8146             :         return [NSString stringWithFormat:@"%d %d", g, s];
    8147             : }
    8148             : 
    8149             : 
    8150             : - (NSString *) keyForInterstellarOverridesForSystems:(OOSystemID) s1 :(OOSystemID) s2 inGalaxy:(OOGalaxyID) g
    8151             : {
    8152             :         return [NSString stringWithFormat:@"interstellar: %d %d %d", g, s1, s2];
    8153             : }
    8154             : 
    8155             : 
    8156             : - (NSDictionary *) generateSystemData:(OOSystemID) s
    8157             : {
    8158             :         return [self generateSystemData:s useCache:YES];
    8159             : }
    8160             : 
    8161             : 
    8162             : // cache isn't handled this way any more
    8163             : - (NSDictionary *) generateSystemData:(OOSystemID) s useCache:(BOOL) useCache
    8164             : {
    8165             :         OOJS_PROFILE_ENTER
    8166             :         
    8167             : // TODO: At the moment this method is only called for systems in the
    8168             : // same galaxy. At some point probably needs generalising to have a
    8169             : // galaxynumber parameter.
    8170             :         NSString *systemKey = [NSString stringWithFormat:@"%u %u",[PLAYER galaxyNumber],s];
    8171             : 
    8172             :         return [systemManager getPropertiesForSystemKey:systemKey];
    8173             :         
    8174             :         OOJS_PROFILE_EXIT
    8175             : }
    8176             : 
    8177             : 
    8178             : - (NSDictionary *) currentSystemData
    8179             : {
    8180             :         OOJS_PROFILE_ENTER
    8181             :         
    8182             :         if (![self inInterstellarSpace])
    8183             :         {
    8184             :                 return [self generateSystemData:systemID];
    8185             :         }
    8186             :         else
    8187             :         {
    8188             :                 static NSDictionary *interstellarDict = nil;
    8189             :                 if (interstellarDict == nil)
    8190             :                 {
    8191             :                         NSString *interstellarName = DESC(@"interstellar-space");
    8192             :                         NSString *notApplicable = DESC(@"not-applicable");
    8193             :                         NSNumber *minusOne = [NSNumber numberWithInt:-1];
    8194             :                         NSNumber *zero = [NSNumber numberWithInt:0];
    8195             :                         interstellarDict = [[NSDictionary alloc] initWithObjectsAndKeys:
    8196             :                                                                 interstellarName, KEY_NAME,
    8197             :                                                                 minusOne, KEY_GOVERNMENT,
    8198             :                                                                 minusOne, KEY_ECONOMY,
    8199             :                                                                 minusOne, KEY_TECHLEVEL,
    8200             :                                                                 zero, KEY_POPULATION,
    8201             :                                                                 zero, KEY_PRODUCTIVITY,
    8202             :                                                                 zero, KEY_RADIUS,
    8203             :                                                                 notApplicable, KEY_INHABITANTS,
    8204             :                                                                 notApplicable, KEY_DESCRIPTION,
    8205             :                                                                 nil];
    8206             :                 }
    8207             :                 
    8208             :                 return interstellarDict;
    8209             :         }
    8210             :         
    8211             :         OOJS_PROFILE_EXIT
    8212             : }
    8213             : 
    8214             : 
    8215             : - (BOOL) inInterstellarSpace
    8216             : {
    8217             :         return [self sun] == nil;
    8218             : }
    8219             : 
    8220             : 
    8221             : 
    8222             : 
    8223             : // layer 2
    8224             : // used by legacy script engine and sun going nova
    8225             : - (void) setSystemDataKey:(NSString *)key value:(NSObject *)object fromManifest:(NSString *)manifest
    8226             : {
    8227             :         [self setSystemDataForGalaxy:galaxyID planet:systemID key:key value:object fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC];
    8228             : }
    8229             : 
    8230             : 
    8231             : - (void) setSystemDataForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum key:(NSString *)key value:(id)object fromManifest:(NSString *)manifest forLayer:(OOSystemLayer)layer
    8232             : {
    8233             :         static BOOL sysdataLocked = NO;
    8234             :         if (sysdataLocked)
    8235             :         {
    8236             :                 OOLogERR(@"script.error", @"%@", @"System properties cannot be set during 'systemInformationChanged' events to avoid infinite loops.");
    8237             :                 return;
    8238             :         }
    8239             : 
    8240             :         BOOL sameGalaxy = (gnum == [PLAYER currentGalaxyID]);
    8241             :         BOOL sameSystem = (sameGalaxy && pnum == [self currentSystemID]);
    8242             : 
    8243             :         // trying to set  unsettable properties?  
    8244             :         if ([key isEqualToString:KEY_RADIUS] && sameGalaxy && sameSystem) // buggy if we allow this key to be set while in the system
    8245             :         {
    8246             :                 OOLogERR(@"script.error", @"System property '%@' cannot be set while in the system.",key);
    8247             :                 return;
    8248             :         }
    8249             : 
    8250             :         if ([key isEqualToString:@"coordinates"]) // setting this in game would be very confusing
    8251             :         {
    8252             :                 OOLogERR(@"script.error", @"System property '%@' cannot be set.",key);
    8253             :                 return;
    8254             :         }
    8255             : 
    8256             :         
    8257             :         NSString        *overrideKey = [NSString stringWithFormat:@"%u %u", gnum, pnum];
    8258             :         NSDictionary *sysInfo = nil;
    8259             :         
    8260             :         // short range map fix
    8261             :         [gui refreshStarChart];
    8262             : 
    8263             :         if (object != nil) {
    8264             :                 // long range map fixes
    8265             :                 if ([key isEqualToString:KEY_NAME])
    8266             :                 {       
    8267             :                         object=(id)[[(NSString *)object lowercaseString] capitalizedString];
    8268             :                         if(sameGalaxy)
    8269             :                         {
    8270             :                                 if (system_names[pnum]) [system_names[pnum] release];
    8271             :                                 system_names[pnum] = [(NSString *)object retain];
    8272             :                         }
    8273             :                 }
    8274             :                 else if ([key isEqualToString:@"sun_radius"])
    8275             :                 {
    8276             :                         if ([object doubleValue] < 1000.0 || [object doubleValue] > 10000000.0 ) 
    8277             :                         {
    8278             :                                 object = ([object doubleValue] < 1000.0 ? (id)@"1000.0" : (id)@"10000000.0"); // works!
    8279             :                         }
    8280             :                 }
    8281             :                 else if ([key hasPrefix:@"corona_"])
    8282             :                 {
    8283             :                         object = (id)[NSString stringWithFormat:@"%f",OOClamp_0_1_f([object floatValue])];
    8284             :                 }
    8285             :         }
    8286             :         
    8287             :         [systemManager setProperty:key forSystemKey:overrideKey andLayer:layer toValue:object fromManifest:manifest];
    8288             : 
    8289             :         
    8290             :         // Apply changes that can be effective immediately, issue warning if they can't be changed just now
    8291             :         if (sameSystem)
    8292             :         {
    8293             :                 sysInfo = [systemManager getPropertiesForCurrentSystem];
    8294             : 
    8295             :                 OOSunEntity* the_sun = [self sun];
    8296             :                 /* KEY_ECONOMY used to be here, but resetting the main station
    8297             :                  * market while the player is in the system is likely to cause
    8298             :                  * more trouble than it's worth. Let them leave and come back
    8299             :                  * - CIM */
    8300             :                 if ([key isEqualToString:KEY_TECHLEVEL])
    8301             :                 {       
    8302             :                         if([self station]){
    8303             :                                 [[self station] setEquivalentTechLevel:[object intValue]];
    8304             :                                 [[self station] setLocalShipyard:[self shipsForSaleForSystem:systemID
    8305             :                                                                 withTL:[object intValue] atTime:[PLAYER clockTime]]];
    8306             :                         }
    8307             :                 }
    8308             :                 else if ([key isEqualToString:@"sun_color"] || [key isEqualToString:@"star_count_multiplier"] ||
    8309             :                                 [key isEqualToString:@"nebula_count_multiplier"] || [key hasPrefix:@"sky_"])
    8310             :                 {
    8311             :                         SkyEntity       *the_sky = nil;
    8312             :                         int i;
    8313             :                         
    8314             :                         for (i = n_entities - 1; i > 0; i--)
    8315             :                                 if ((sortedEntities[i]) && ([sortedEntities[i] isKindOfClass:[SkyEntity class]]))
    8316             :                                         the_sky = (SkyEntity*)sortedEntities[i];
    8317             :                         
    8318             :                         if (the_sky != nil)
    8319             :                         {
    8320             :                                 [the_sky changeProperty:key withDictionary:sysInfo];
    8321             :                                 
    8322             :                                 if ([key isEqualToString:@"sun_color"])
    8323             :                                 {
    8324             :                                         OOColor *color = [the_sky skyColor];
    8325             :                                         if (the_sun != nil)
    8326             :                                         {
    8327             :                                                 [the_sun setSunColor:color];
    8328             :                                                 [the_sun getDiffuseComponents:sun_diffuse];
    8329             :                                                 [the_sun getSpecularComponents:sun_specular];
    8330             :                                         }
    8331             :                                         for (i = n_entities - 1; i > 0; i--)
    8332             :                                                 if ((sortedEntities[i]) && ([sortedEntities[i] isKindOfClass:[DustEntity class]]))
    8333             :                                                         [(DustEntity*)sortedEntities[i] setDustColor:[color blendedColorWithFraction:0.5 ofColor:[OOColor whiteColor]]];
    8334             :                                 }
    8335             :                         }
    8336             :                 }
    8337             :                 else if (the_sun != nil && ([key hasPrefix:@"sun_"] || [key hasPrefix:@"corona_"]))
    8338             :                 {
    8339             :                         [the_sun changeSunProperty:key withDictionary:sysInfo];
    8340             :                 }
    8341             :                 else if ([key isEqualToString:@"texture"])
    8342             :                 {
    8343             :                         [[self planet] setUpPlanetFromTexture:(NSString *)object];
    8344             :                 }
    8345             :                 else if ([key isEqualToString:@"texture_hsb_color"])
    8346             :                 {
    8347             :                         [[self planet] setUpPlanetFromTexture: [[self planet] textureFileName]];
    8348             :                 }
    8349             :                 else if ([key isEqualToString:@"air_color"])
    8350             :                 {
    8351             :                         [[self planet] setAirColor:[OOColor brightColorWithDescription:object]];
    8352             :                 }
    8353             :                 else if ([key isEqualToString:@"illumination_color"])
    8354             :                 {
    8355             :                         [[self planet] setIlluminationColor:[OOColor colorWithDescription:object]];
    8356             :                 }
    8357             :                 else if ([key isEqualToString:@"air_color_mix_ratio"])
    8358             :                 {
    8359             :                         [[self planet] setAirColorMixRatio:[sysInfo oo_floatForKey:key]];
    8360             :                 }
    8361             :         }
    8362             :         
    8363             :         sysdataLocked = YES;
    8364             :         [PLAYER doScriptEvent:OOJSID("systemInformationChanged") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithInt:gnum],[NSNumber numberWithInt:pnum],key,object,nil]];
    8365             :         sysdataLocked = NO;
    8366             : 
    8367             : }
    8368             : 
    8369             : 
    8370           0 : - (NSDictionary *) generateSystemDataForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum
    8371             : {
    8372             :         NSString *systemKey = [self keyForPlanetOverridesForSystem:pnum inGalaxy:gnum];
    8373             :         return [systemManager getPropertiesForSystemKey:systemKey];
    8374             : }
    8375             : 
    8376             : 
    8377             : - (NSArray *) systemDataKeysForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum
    8378             : {
    8379             :         return [[self generateSystemDataForGalaxy:gnum planet:pnum] allKeys];
    8380             : }
    8381             : 
    8382             : 
    8383             : /* Only called from OOJSSystemInfo. */
    8384             : - (id) systemDataForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum key:(NSString *)key
    8385             : {
    8386             :         return [systemManager getProperty:key forSystem:pnum inGalaxy:gnum];
    8387             : }
    8388             : 
    8389             : 
    8390             : - (NSString *) getSystemName:(OOSystemID) sys
    8391             : {
    8392             :         return [self getSystemName:sys forGalaxy:galaxyID];
    8393             : }
    8394             : 
    8395             : 
    8396             : - (NSString *) getSystemName:(OOSystemID) sys forGalaxy:(OOGalaxyID) gnum
    8397             : {
    8398             :         return [systemManager getProperty:@"name" forSystem:sys inGalaxy:gnum];
    8399             : }
    8400             : 
    8401             : 
    8402             : - (OOGovernmentID) getSystemGovernment:(OOSystemID) sys
    8403             : {
    8404             :         return [[systemManager getProperty:@"government" forSystem:sys inGalaxy:galaxyID] unsignedCharValue];
    8405             : }
    8406             : 
    8407             : 
    8408             : - (NSString *) getSystemInhabitants:(OOSystemID) sys
    8409             : {
    8410             :         return [self getSystemInhabitants:sys plural:YES];
    8411             : }
    8412             : 
    8413             : 
    8414             : - (NSString *) getSystemInhabitants:(OOSystemID) sys plural:(BOOL)plural
    8415             : {       
    8416             :         NSString *ret = nil;
    8417             :         if (!plural)
    8418             :         {
    8419             :                 ret = [systemManager getProperty:KEY_INHABITANT forSystem:sys inGalaxy:galaxyID];
    8420             :         }
    8421             :         if (ret != nil) // the singular form might be absent.
    8422             :         {
    8423             :                 return ret;
    8424             :         }
    8425             :         else
    8426             :         {
    8427             :                 return [systemManager getProperty:KEY_INHABITANTS forSystem:sys inGalaxy:galaxyID];
    8428             :         }
    8429             : }
    8430             : 
    8431             : 
    8432             : - (NSPoint) coordinatesForSystem:(OOSystemID)s
    8433             : {
    8434             :         return [systemManager getCoordinatesForSystem:s inGalaxy:galaxyID];
    8435             : }
    8436             : 
    8437             : 
    8438             : - (OOSystemID) findSystemFromName:(NSString *) sysName
    8439             : {
    8440             :         if (sysName == nil) return -1;  // no match found!
    8441             :         
    8442             :         NSString        *system_name = nil;
    8443             :         NSString        *match = [sysName lowercaseString];
    8444             :         int i;
    8445             :         for (i = 0; i < 256; i++)
    8446             :         {
    8447             :                 system_name = [system_names[i] lowercaseString];
    8448             :                 if ([system_name isEqualToString:match])
    8449             :                 {
    8450             :                         return i;
    8451             :                 }
    8452             :         }
    8453             :         return -1;      // no match found!
    8454             : }
    8455             : 
    8456             : 
    8457             : - (OOSystemID) findSystemAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) g
    8458             : {
    8459             :         OOLog(@"deprecated.function", @"%@", @"findSystemAtCoords");
    8460             :         return [self findSystemNumberAtCoords:coords withGalaxy:g includingHidden:YES];
    8461             : }
    8462             : 
    8463             : 
    8464             : - (NSMutableArray *) nearbyDestinationsWithinRange:(double)range
    8465             : {
    8466             :         NSMutableArray *result = [NSMutableArray arrayWithCapacity:16];
    8467             :         
    8468             :         range = OOClamp_0_max_d(range, MAX_JUMP_RANGE); // limit to systems within 7LY
    8469             :         NSPoint here = [PLAYER galaxy_coordinates];
    8470             :         
    8471             :         for (unsigned short i = 0; i < 256; i++)
    8472             :         {
    8473             :                 NSPoint there = [self coordinatesForSystem:i];
    8474             :                 double dist = distanceBetweenPlanetPositions(here.x, here.y, there.x, there.y);
    8475             :                 if (dist <= range && (i != systemID || [self inInterstellarSpace])) // if we are in interstellar space, it's OK to include the system we (mis)jumped from
    8476             :                 {
    8477             :                         [result addObject: [NSDictionary dictionaryWithObjectsAndKeys:
    8478             :                                                                 [NSNumber numberWithDouble:dist], @"distance",
    8479             :                                                                 [NSNumber numberWithInt:i], @"sysID",
    8480             :                                                                 [[self generateSystemData:i] oo_stringForKey:@"sun_gone_nova" defaultValue:@"0"], @"nova",
    8481             :                                                                 nil]];
    8482             :                 }
    8483             :         }
    8484             :         
    8485             :         return result;
    8486             : }
    8487             : 
    8488             : 
    8489             : - (OOSystemID) findNeighbouringSystemToCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) g
    8490             : {
    8491             : 
    8492             :         double distance;
    8493             :         int n,i,j;
    8494             :         double min_dist = 10000.0;
    8495             :         
    8496             :         // make list of connected systems
    8497             :         BOOL connected[256];
    8498             :         for (i = 0; i < 256; i++)
    8499             :                 connected[i] = NO;
    8500             :         connected[0] = YES;                     // system zero is always connected (true for galaxies 0..7)
    8501             :         for (n = 0; n < 3; n++)              //repeat three times for surety
    8502             :         {
    8503             :                 for (i = 0; i < 256; i++)   // flood fill out from system zero
    8504             :                 {
    8505             :                         NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g];
    8506             :                         for (j = 0; j < 256; j++)
    8507             :                         {
    8508             :                                 NSPoint jpos = [systemManager getCoordinatesForSystem:j inGalaxy:g];
    8509             :                                 double dist = distanceBetweenPlanetPositions(ipos.x,ipos.y,jpos.x,jpos.y);
    8510             :                                 if (dist <= MAX_JUMP_RANGE)
    8511             :                                 {
    8512             :                                         connected[j] |= connected[i];
    8513             :                                         connected[i] |= connected[j];
    8514             :                                 }
    8515             :                         }
    8516             :                 }
    8517             :         }
    8518             :         OOSystemID system = 0;
    8519             :         for (i = 0; i < 256; i++)
    8520             :         {
    8521             :                 NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g];
    8522             :                 distance = distanceBetweenPlanetPositions((int)coords.x, (int)coords.y, ipos.x, ipos.y);
    8523             :                 if ((connected[i])&&(distance < min_dist)&&(distance != 0.0))
    8524             :                 {
    8525             :                         min_dist = distance;
    8526             :                         system = i;
    8527             :                 }
    8528             :         }
    8529             :         
    8530             :         return system;
    8531             : }
    8532             : 
    8533             : 
    8534             : /* This differs from the above function in that it can return a system
    8535             :  * exactly at the specified coordinates */
    8536             : - (OOSystemID) findConnectedSystemAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) g
    8537             : {
    8538             : 
    8539             :         double distance;
    8540             :         int n,i,j;
    8541             :         double min_dist = 10000.0;
    8542             :         
    8543             :         // make list of connected systems
    8544             :         BOOL connected[256];
    8545             :         for (i = 0; i < 256; i++)
    8546             :                 connected[i] = NO;
    8547             :         connected[0] = YES;                     // system zero is always connected (true for galaxies 0..7)
    8548             :         for (n = 0; n < 3; n++)              //repeat three times for surety
    8549             :         {
    8550             :                 for (i = 0; i < 256; i++)   // flood fill out from system zero
    8551             :                 {
    8552             :                         NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g];
    8553             :                         for (j = 0; j < 256; j++)
    8554             :                         {
    8555             :                                 NSPoint jpos = [systemManager getCoordinatesForSystem:j inGalaxy:g];
    8556             :                                 double dist = distanceBetweenPlanetPositions(ipos.x,ipos.y,jpos.x,jpos.y);
    8557             :                                 if (dist <= MAX_JUMP_RANGE)
    8558             :                                 {
    8559             :                                         connected[j] |= connected[i];
    8560             :                                         connected[i] |= connected[j];
    8561             :                                 }
    8562             :                         }
    8563             :                 }
    8564             :         }
    8565             :         OOSystemID system = 0;
    8566             :         for (i = 0; i < 256; i++)
    8567             :         {
    8568             :                 NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g];
    8569             :                 distance = distanceBetweenPlanetPositions((int)coords.x, (int)coords.y, ipos.x, ipos.y);
    8570             :                 if ((connected[i])&&(distance < min_dist))
    8571             :                 {
    8572             :                         min_dist = distance;
    8573             :                         system = i;
    8574             :                 }
    8575             :         }
    8576             :         
    8577             :         return system;  
    8578             : }
    8579             : 
    8580             : 
    8581             : - (OOSystemID) findSystemNumberAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID)g includingHidden:(BOOL)hidden
    8582             : {
    8583             :         /*
    8584             :                 NOTE: this previously used NSNotFound as the default value, but
    8585             :                 returned an int, which would truncate on 64-bit systems. I assume
    8586             :                 no-one was using it in a context where the default value was returned.
    8587             :                 -- Ahruman 2012-08-25
    8588             :         */
    8589             :         OOSystemID      system = kOOMinimumSystemID;
    8590             :         unsigned        distance, dx, dy;
    8591             :         OOSystemID      i;
    8592             :         unsigned        min_dist = 10000;
    8593             :         
    8594             :         for (i = 0; i < 256; i++)
    8595             :         {
    8596             :                 if (!hidden) {
    8597             :                         NSDictionary *systemInfo = [systemManager getPropertiesForSystem:i inGalaxy:g];
    8598             :                         NSInteger concealment = [systemInfo oo_intForKey:@"concealment" defaultValue:OO_SYSTEMCONCEALMENT_NONE];
    8599             :                         if (concealment >= OO_SYSTEMCONCEALMENT_NOTHING) {
    8600             :                                 // system is not known
    8601             :                                 continue;
    8602             :                         }
    8603             :                 }
    8604             :                 NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g];
    8605             :                 dx = ABS(coords.x - ipos.x);
    8606             :                 dy = ABS(coords.y - ipos.y);
    8607             :                 
    8608             :                 if (dx > dy) distance = (dx + dx + dy) / 2;
    8609             :                 else                    distance = (dx + dy + dy) / 2;
    8610             :                 
    8611             :                 if (distance < min_dist)
    8612             :                 {
    8613             :                         min_dist = distance;
    8614             :                         system = i;
    8615             :                 }
    8616             :                 // with coincident systems choose only if ABOVE
    8617             :                 if ((distance == min_dist)&&(coords.y > ipos.y))
    8618             :                 {
    8619             :                         system = i;
    8620             :                 }
    8621             :                 // or if EQUAL but already selected
    8622             :                 else if ((distance == min_dist)&&(coords.y == ipos.y)&&(i==[PLAYER targetSystemID]))
    8623             :                 {
    8624             :                         system = i;
    8625             :                 }
    8626             :         }
    8627             :         return system;
    8628             : }
    8629             : 
    8630             : 
    8631             : - (NSPoint) findSystemCoordinatesWithPrefix:(NSString *) p_fix
    8632             : {
    8633             :         return [self findSystemCoordinatesWithPrefix:p_fix exactMatch:NO];
    8634             : }
    8635             : 
    8636             : 
    8637             : - (NSPoint) findSystemCoordinatesWithPrefix:(NSString *) p_fix exactMatch:(BOOL) exactMatch
    8638             : {
    8639             :         NSString        *system_name = nil;
    8640             :         NSPoint         system_coords = NSMakePoint(-1.0,-1.0);
    8641             :         int i;
    8642             :         int result = -1;
    8643             :         for (i = 0; i < 256; i++)
    8644             :         {
    8645             :                 system_found[i] = NO;
    8646             :                 system_name = [system_names[i] lowercaseString];
    8647             :                 if ((exactMatch && [system_name isEqualToString:p_fix]) || (!exactMatch && [system_name hasPrefix:p_fix]))
    8648             :                 {
    8649             :                         /* Only used in player-based search routines */
    8650             :                         NSDictionary *systemInfo = [systemManager getPropertiesForSystem:i inGalaxy:galaxyID];
    8651             :                         NSInteger concealment = [systemInfo oo_intForKey:@"concealment" defaultValue:OO_SYSTEMCONCEALMENT_NONE];
    8652             :                         if (concealment >= OO_SYSTEMCONCEALMENT_NONAME) {
    8653             :                                 // system is not known
    8654             :                                 continue;
    8655             :                         }
    8656             :                         
    8657             :                         system_found[i] = YES;
    8658             :                         if (result < 0)
    8659             :                         {
    8660             :                                 system_coords = [systemManager getCoordinatesForSystem:i inGalaxy:galaxyID];
    8661             :                                 result = i;
    8662             :                         }
    8663             :                 }
    8664             :         }
    8665             :         return system_coords;
    8666             : }
    8667             : 
    8668             : 
    8669             : - (BOOL*) systemsFound
    8670             : {
    8671             :         return (BOOL*)system_found;
    8672             : }
    8673             : 
    8674             : 
    8675             : - (NSString*)systemNameIndex:(OOSystemID)index
    8676             : {
    8677             :         return system_names[index & 255];
    8678             : }
    8679             : 
    8680             : 
    8681             : - (NSDictionary *) routeFromSystem:(OOSystemID) start toSystem:(OOSystemID) goal optimizedBy:(OORouteType) optimizeBy
    8682             : {
    8683             :         /*
    8684             :          time_cost = distance * distance
    8685             :          jump_cost = jumps * max_total_distance + distance = max_total_tistance + distance
    8686             :          
    8687             :          max_total_distance is 7 * 256
    8688             :          
    8689             :          max_time_cost = max_planets * max_time_cost = 256 * (7 * 7)
    8690             :          max_jump_cost = max_planets * max_jump_cost = 256 * (7 * 256 + 7)
    8691             :          */
    8692             :         
    8693             :         // no interstellar space for start and/or goal please
    8694             :         if (start == -1 || goal == -1)  return nil;
    8695             :         
    8696             : #ifdef CACHE_ROUTE_FROM_SYSTEM_RESULTS
    8697             :         
    8698             :         static NSDictionary *c_route = nil;
    8699             :         static OOSystemID c_start, c_goal;
    8700             :         static OORouteType c_optimizeBy;
    8701             :         
    8702             :         if (c_route != nil && c_start == start && c_goal == goal && c_optimizeBy == optimizeBy)
    8703             :         {
    8704             :                 return c_route;
    8705             :         }
    8706             :         
    8707             : #endif
    8708             :         
    8709             :         unsigned i, j;
    8710             :         
    8711             :         if (start > 255 || goal > 255) return nil;
    8712             :         
    8713             :         NSArray *neighbours[256];
    8714             :         BOOL concealed[256];
    8715             :         for (i = 0; i < 256; i++)
    8716             :         {
    8717             :                 NSDictionary *systemInfo = [systemManager getPropertiesForSystem:i inGalaxy:galaxyID];
    8718             :                 NSInteger concealment = [systemInfo oo_intForKey:@"concealment" defaultValue:OO_SYSTEMCONCEALMENT_NONE];
    8719             :                 if (concealment >= OO_SYSTEMCONCEALMENT_NOTHING) {
    8720             :                         // system is not known
    8721             :                         neighbours[i] = [NSArray array];
    8722             :                         concealed[i] = YES;
    8723             :                 }
    8724             :                 else
    8725             :                 {
    8726             :                         neighbours[i] = [self neighboursToSystem:i];
    8727             :                         concealed[i] = NO;
    8728             :                 }
    8729             :         }
    8730             :         
    8731             :         RouteElement *cheapest[256] = {0};
    8732             :         
    8733             :         double maxCost = optimizeBy == OPTIMIZED_BY_TIME ? 256 * (7 * 7) : 256 * (7 * 256 + 7);
    8734             :         
    8735             :         NSMutableArray *curr = [NSMutableArray arrayWithCapacity:256];
    8736             :         [curr addObject:cheapest[start] = [RouteElement elementWithLocation:start parent:-1 cost:0 distance:0 time:0 jumps: 0]];
    8737             :         
    8738             :         NSMutableArray *next = [NSMutableArray arrayWithCapacity:256];
    8739             :         while ([curr count] != 0)
    8740             :         {
    8741             :                 for (i = 0; i < [curr count]; i++) {
    8742             :                         RouteElement *elemI = [curr objectAtIndex:i];
    8743             :                         NSArray *ns = neighbours[[elemI location]];
    8744             :                         for (j = 0; j < [ns count]; j++)
    8745             :                         {
    8746             :                                 RouteElement *ce = cheapest[[elemI location]];
    8747             :                                 OOSystemID n = [ns oo_intAtIndex:j];
    8748             :                                 if (concealed[n])
    8749             :                                 {
    8750             :                                         continue;
    8751             :                                 }
    8752             :                                 OOSystemID c = [ce location];
    8753             :                                 
    8754             :                                 NSPoint cpos = [systemManager getCoordinatesForSystem:c inGalaxy:galaxyID];
    8755             :                                 NSPoint npos = [systemManager getCoordinatesForSystem:n inGalaxy:galaxyID];
    8756             : 
    8757             :                                 double lastDistance = distanceBetweenPlanetPositions(npos.x,npos.y,cpos.x,cpos.y);
    8758             :                                 double lastTime = lastDistance * lastDistance;
    8759             :                                 
    8760             :                                 double distance = [ce distance] + lastDistance;
    8761             :                                 double time = [ce time] + lastTime;
    8762             :                                 double cost = [ce cost] + (optimizeBy == OPTIMIZED_BY_TIME ? lastTime : 7 * 256 + lastDistance);
    8763             :                                 int jumps = [ce jumps] + 1;
    8764             :                                 
    8765             :                                 if (cost < maxCost && (cheapest[n] == nil || [cheapest[n] cost] > cost)) {
    8766             :                                         RouteElement *e = [RouteElement elementWithLocation:n parent:c cost:cost distance:distance time:time jumps:jumps];
    8767             :                                         cheapest[n] = e;
    8768             :                                         [next addObject:e];
    8769             :                                         
    8770             :                                         if (n == goal && cost < maxCost)
    8771             :                                                 maxCost = cost;
    8772             :                                 }
    8773             :                         }
    8774             :                 }
    8775             :                 [curr setArray:next];
    8776             :                 [next removeAllObjects];
    8777             :         }
    8778             :         
    8779             :         
    8780             :         if (!cheapest[goal]) return nil;
    8781             :         
    8782             :         NSMutableArray *route = [NSMutableArray arrayWithCapacity:256];
    8783             :         RouteElement *e = cheapest[goal];
    8784             :         for (;;)
    8785             :         {
    8786             :                 [route insertObject:[NSNumber numberWithInt:[e location]] atIndex:0];
    8787             :                 if ([e parent] == -1) break;
    8788             :                 e = cheapest[[e parent]];
    8789             :         }
    8790             :         
    8791             : #ifdef CACHE_ROUTE_FROM_SYSTEM_RESULTS
    8792             :         c_start = start;
    8793             :         c_goal = goal;
    8794             :         c_optimizeBy = optimizeBy;
    8795             :         [c_route release];
    8796             :         c_route = [[NSDictionary alloc] initWithObjectsAndKeys: route, @"route", [NSNumber numberWithDouble:[cheapest[goal] distance]], @"distance", nil];
    8797             :         
    8798             :         return c_route;
    8799             : #else
    8800             :         return [NSDictionary dictionaryWithObjectsAndKeys:
    8801             :                         route, @"route",
    8802             :                         [NSNumber numberWithDouble:[cheapest[goal] distance]], @"distance",
    8803             :                         [NSNumber numberWithDouble:[cheapest[goal] time]], @"time",
    8804             :                         [NSNumber numberWithInt:[cheapest[goal] jumps]], @"jumps",
    8805             :                         nil];
    8806             : #endif
    8807             : }
    8808             : 
    8809             : 
    8810             : - (NSArray *) neighboursToSystem: (OOSystemID) s
    8811             : {
    8812             :         if (s == systemID && closeSystems != nil) 
    8813             :         {
    8814             :                 return closeSystems;
    8815             :         }
    8816             :         NSArray *neighbours = [systemManager getNeighbourIDsForSystem:s inGalaxy:galaxyID];
    8817             : 
    8818             :         if (s == systemID)
    8819             :         {
    8820             :                 [closeSystems release];
    8821             :                 closeSystems = [neighbours copy];
    8822             :                 return closeSystems;
    8823             :         }
    8824             :         return neighbours;
    8825             : }
    8826             : 
    8827             : 
    8828             : /*
    8829             :         Planet texture preloading.
    8830             :         
    8831             :         In order to hide the cost of synthesizing textures, we want to start
    8832             :         rendering them asynchronously as soon as there's a hint they may be needed
    8833             :         soon: when a system is selected on one of the charts, and when beginning a
    8834             :         jump. However, it would be a Bad Ideaâ„¢ to allow an arbitrary number of
    8835             :         planets to be queued, since you can click on lots of systems quite
    8836             :         quickly on the long-range chart.
    8837             :         
    8838             :         To rate-limit this, we track the materials that are being preloaded and
    8839             :         only queue the ones for a new system if there are no more than two in the
    8840             :         queue. (Currently, each system will have at most two materials, the main
    8841             :         planet and the main planet's atmosphere, but it may be worth adding the
    8842             :         ability to declare planets in planetinfo.plist instead of using scripts so
    8843             :         that they can also benefit from preloading.)
    8844             :         
    8845             :         The preloading materials list is pruned before preloading, and also once
    8846             :         per frame so textures can fall out of the regular cache.
    8847             :         -- Ahruman 2009-12-19
    8848             :         
    8849             :         DISABLED due to crashes on some Windows systems. Textures generated here
    8850             :         remain in the sRecentTextures cache when released, suggesting a retain
    8851             :         imbalance somewhere. Cannot reproduce under Mac OS X. Needs further
    8852             :         analysis before reenabling.
    8853             :         http://www.aegidian.org/bb/viewtopic.php?f=3&t=12109
    8854             :         -- Ahruman 2012-06-29
    8855             : */
    8856             : - (void) preloadPlanetTexturesForSystem:(OOSystemID)s
    8857             : {
    8858             : // #if NEW_PLANETS
    8859             : #if 0
    8860             :         [self prunePreloadingPlanetMaterials];
    8861             :         
    8862             :         if ([_preloadingPlanetMaterials count] < 3)
    8863             :         {
    8864             :                 if (_preloadingPlanetMaterials == nil)  _preloadingPlanetMaterials = [[NSMutableArray alloc] initWithCapacity:4];
    8865             :                 
    8866             :                 OOPlanetEntity *planet = [[OOPlanetEntity alloc] initAsMainPlanetForSystem:s];
    8867             :                 OOMaterial *surface = [planet material];
    8868             :                 // can be nil if texture mis-defined
    8869             :                 if (surface != nil)
    8870             :                 {
    8871             :                         // if it's already loaded, no need to continue
    8872             :                         if (![surface isFinishedLoading])
    8873             :                         {
    8874             :                                 [_preloadingPlanetMaterials addObject:surface];
    8875             :                 
    8876             :                                 // In some instances (retextured planets atm), the main planet might not have an atmosphere defined.
    8877             :                                 // Trying to add nil to _preloadingPlanetMaterials will prematurely terminate the calling function.(!) --Kaks 20100107
    8878             :                                 OOMaterial *atmo = [planet atmosphereMaterial];
    8879             :                                 if (atmo != nil)  [_preloadingPlanetMaterials addObject:atmo];
    8880             :                         }
    8881             :                 }
    8882             :                 
    8883             :                 [planet release];
    8884             :         }
    8885             : #endif
    8886             : }
    8887             : 
    8888             : 
    8889             : - (NSDictionary *) globalSettings
    8890             : {
    8891             :         return globalSettings;
    8892             : }
    8893             : 
    8894             : 
    8895             : - (NSArray *) equipmentData
    8896             : {
    8897             :         return equipmentData;
    8898             : }
    8899             : 
    8900             : 
    8901             : - (NSArray *) equipmentDataOutfitting
    8902             : {
    8903             :         return equipmentDataOutfitting;
    8904             : }
    8905             : 
    8906             : 
    8907             : - (OOCommodityMarket *) commodityMarket
    8908             : {
    8909             :         return commodityMarket;
    8910             : }
    8911             : 
    8912             : 
    8913             : - (NSString *) timeDescription:(double) interval
    8914             : {
    8915             :         double r_time = interval;
    8916             :         NSString* result = @"";
    8917             :         
    8918             :         if (r_time > 86400)
    8919             :         {
    8920             :                 int days = floor(r_time / 86400);
    8921             :                 r_time -= 86400 * days;
    8922             :                 result = [NSString stringWithFormat:@"%@ %d day%@", result, days, (days > 1) ? @"s" : @""];
    8923             :         }
    8924             :         if (r_time > 3600)
    8925             :         {
    8926             :                 int hours = floor(r_time / 3600);
    8927             :                 r_time -= 3600 * hours;
    8928             :                 result = [NSString stringWithFormat:@"%@ %d hour%@", result, hours, (hours > 1) ? @"s" : @""];
    8929             :         }
    8930             :         if (r_time > 60)
    8931             :         {
    8932             :                 int mins = floor(r_time / 60);
    8933             :                 r_time -= 60 * mins;
    8934             :                 result = [NSString stringWithFormat:@"%@ %d minute%@", result, mins, (mins > 1) ? @"s" : @""];
    8935             :         }
    8936             :         if (r_time > 0)
    8937             :         {
    8938             :                 int secs = floor(r_time);
    8939             :                 result = [NSString stringWithFormat:@"%@ %d second%@", result, secs, (secs > 1) ? @"s" : @""];
    8940             :         }
    8941             :         return [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    8942             : }
    8943             : 
    8944             : 
    8945             : - (NSString *) shortTimeDescription:(double) interval
    8946             : {
    8947             :         double r_time = interval;
    8948             :         NSString* result = @"";
    8949             :         int parts = 0;
    8950             :         
    8951             :         if (interval <= 0.0)
    8952             :                 return DESC(@"contracts-no-time");
    8953             :         
    8954             :         if (r_time > 86400)
    8955             :         {
    8956             :                 int days = floor(r_time / 86400);
    8957             :                 r_time -= 86400 * days;
    8958             :                 result = [NSString stringWithFormat:@"%@ %d %@", result, days, DESC_PLURAL(@"contracts-day-word", days)];
    8959             :                 parts++;
    8960             :         }
    8961             :         if (r_time > 3600)
    8962             :         {
    8963             :                 int hours = floor(r_time / 3600);
    8964             :                 r_time -= 3600 * hours;
    8965             :                 result = [NSString stringWithFormat:@"%@ %d %@", result, hours, DESC_PLURAL(@"contracts-hour-word", hours)];
    8966             :                 parts++;
    8967             :         }
    8968             :         if (parts < 2 && r_time > 60)
    8969             :         {
    8970             :                 int mins = floor(r_time / 60);
    8971             :                 r_time -= 60 * mins;
    8972             :                 result = [NSString stringWithFormat:@"%@ %d %@", result, mins, DESC_PLURAL(@"contracts-minute-word", mins)];
    8973             :                 parts++;
    8974             :         }
    8975             :         if (parts < 2 && r_time > 0)
    8976             :         {
    8977             :                 int secs = floor(r_time);
    8978             :                 result = [NSString stringWithFormat:@"%@ %d %@", result, secs, DESC_PLURAL(@"contracts-second-word", secs)];
    8979             :         }
    8980             :         return [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    8981             : }
    8982             : 
    8983             : 
    8984             : - (void) makeSunSkimmer:(ShipEntity *) ship andSetAI:(BOOL)setAI
    8985             : {
    8986             :         if (setAI) [ship switchAITo:@"oolite-traderAI.js"];   // perfectly acceptable for both route 2 & 3
    8987             :         [ship setFuel:(Ranrot()&31)];
    8988             :         // slow ships need extra insulation or they will burn up when sunskimming. (Tested at biggest sun in G3: Aenqute)
    8989             :         float minInsulation = 1000 / [ship maxFlightSpeed] + 1;
    8990             :         if ([ship heatInsulation] < minInsulation) [ship setHeatInsulation:minInsulation];
    8991             : }
    8992             : 
    8993             : 
    8994             : - (Random_Seed) marketSeed
    8995             : {
    8996             :         Random_Seed             ret = [systemManager getRandomSeedForCurrentSystem];
    8997             :         
    8998             :         // adjust basic seed by market random factor
    8999             :         // which for (very bad) historical reasons is 0x80
    9000             : 
    9001             :         ret.f ^= 0x80;  // XOR back to front
    9002             :         ret.e ^= ret.f; // XOR
    9003             :         ret.d ^= ret.e; // XOR
    9004             :         ret.c ^= ret.d; // XOR
    9005             :         ret.b ^= ret.c; // XOR
    9006             :         ret.a ^= ret.b; // XOR
    9007             :         
    9008             :         return ret;
    9009             : }
    9010             : 
    9011             : 
    9012             : - (void) loadStationMarkets:(NSArray *)marketData
    9013             : {
    9014             :         if (marketData == nil)
    9015             :         {
    9016             :                 return;
    9017             :         }
    9018             : 
    9019             :         NSArray *stations = [self stations];
    9020             :         StationEntity *station = nil;
    9021             :         NSDictionary *savedMarket = nil;
    9022             : 
    9023             :         foreach (savedMarket, marketData)
    9024             :         {
    9025             :                 HPVector pos = [savedMarket oo_hpvectorForKey:@"position"];
    9026             :                 foreach (station, stations)
    9027             :                 {
    9028             :                         // must be deterministic and secondary
    9029             :                         if ([station allowsSaving] && station != [UNIVERSE station])
    9030             :                         {
    9031             :                                 // allow a km of drift just in case
    9032             :                                 if (HPdistance2(pos,[station position]) < 1000000)
    9033             :                                 {
    9034             :                                         [station setLocalMarket:[savedMarket oo_arrayForKey:@"market"]];
    9035             :                                         break;
    9036             :                                 }
    9037             :                         }
    9038             :                 }
    9039             :         }
    9040             : 
    9041             : }
    9042             : 
    9043             : 
    9044             : - (NSArray *) getStationMarkets
    9045             : {
    9046             :         NSMutableArray *markets = [[NSMutableArray alloc] init];
    9047             :         NSArray *stations = [self stations];
    9048             : 
    9049             :         StationEntity *station = nil;
    9050             :         NSMutableDictionary *savedMarket = nil;
    9051             : 
    9052             :         OOCommodityMarket *stationMarket = nil;
    9053             : 
    9054             :         foreach (station, stations)
    9055             :         {
    9056             :                 // must be deterministic and secondary
    9057             :                 if ([station allowsSaving] && station != [UNIVERSE station])
    9058             :                 {
    9059             :                         stationMarket = [station localMarket];
    9060             :                         if (stationMarket != nil)
    9061             :                         {
    9062             :                                 savedMarket = [NSMutableDictionary dictionaryWithCapacity:2];
    9063             :                                 [savedMarket setObject:[stationMarket saveStationAmounts] forKey:@"market"];
    9064             :                                 [savedMarket setObject:ArrayFromHPVector([station position]) forKey:@"position"];
    9065             :                                 [markets addObject:savedMarket];
    9066             :                         }
    9067             :                 }
    9068             :         }
    9069             : 
    9070             :         return [markets autorelease];
    9071             : }
    9072             : 
    9073             : 
    9074             : - (NSArray *) shipsForSaleForSystem:(OOSystemID)s withTL:(OOTechLevelID)specialTL atTime:(OOTimeAbsolute)current_time
    9075             : {
    9076             :         RANROTSeed saved_seed = RANROTGetFullSeed();
    9077             :         Random_Seed ship_seed = [self marketSeed];
    9078             :         
    9079             :         NSMutableDictionary             *resultDictionary = [NSMutableDictionary dictionary];
    9080             :         
    9081             :         float                                   tech_price_boost = (ship_seed.a + ship_seed.b) / 256.0;
    9082             :         unsigned                                i;
    9083             :         PlayerEntity                    *player = PLAYER;
    9084             :         OOShipRegistry                  *registry = [OOShipRegistry sharedRegistry];
    9085             :         RANROTSeed                              personalitySeed = RanrotSeedFromRandomSeed(ship_seed);
    9086             :         
    9087             :         for (i = 0; i < 256; i++)
    9088             :         {
    9089             :                 long long reference_time = 0x1000000 * floor(current_time / 0x1000000);
    9090             :                 
    9091             :                 long long c_time = ship_seed.a * 0x10000 + ship_seed.b * 0x100 + ship_seed.c;
    9092             :                 double ship_sold_time = reference_time + c_time;
    9093             :                 
    9094             :                 if (ship_sold_time < 0)
    9095             :                         ship_sold_time += 0x1000000;    // wraparound
    9096             :                 
    9097             :                 double days_until_sale = (ship_sold_time - current_time) / 86400.0;
    9098             :                 
    9099             :                 NSMutableArray  *keysForShips = [NSMutableArray arrayWithArray:[registry playerShipKeys]];
    9100             :                 unsigned                si;
    9101             :                 for (si = 0; si < [keysForShips count]; si++)
    9102             :                 {
    9103             :                         //eliminate any ships that fail a 'conditions test'
    9104             :                         NSString                *key = [keysForShips oo_stringAtIndex:si];
    9105             :                         NSDictionary    *dict = [registry shipyardInfoForKey:key];
    9106             :                         NSArray                 *conditions = [dict oo_arrayForKey:@"conditions"];
    9107             :                         
    9108             :                         if (![player scriptTestConditions:conditions])
    9109             :                         {
    9110             :                                 [keysForShips removeObjectAtIndex:si--];
    9111             :                         }
    9112             :                         NSString *condition_script = [dict oo_stringForKey:@"condition_script"];
    9113             :                         if (condition_script != nil)
    9114             :                         {
    9115             :                                 OOJSScript *condScript = [self getConditionScript:condition_script];
    9116             :                                 if (condScript != nil) // should always be non-nil, but just in case
    9117             :                                 {
    9118             :                                         JSContext                       *context = OOJSAcquireContext();
    9119             :                                         BOOL OK;
    9120             :                                         JSBool allow_purchase;
    9121             :                                         jsval result;
    9122             :                                         jsval args[] = { OOJSValueFromNativeObject(context, key) };
    9123             :                         
    9124             :                                         OK = [condScript callMethod:OOJSID("allowOfferShip")
    9125             :                                                                                                 inContext:context
    9126             :                                                                                 withArguments:args count:sizeof args / sizeof *args
    9127             :                                                                                                          result:&result];
    9128             : 
    9129             :                                         if (OK) OK = JS_ValueToBoolean(context, result, &allow_purchase);
    9130             :                         
    9131             :                                         OOJSRelinquishContext(context);
    9132             : 
    9133             :                                         if (OK && !allow_purchase)
    9134             :                                         {
    9135             :                                                 /* if the script exists, the function exists, the function
    9136             :                                                  * returns a bool, and that bool is false, block
    9137             :                                                  * purchase. Otherwise allow it as default */
    9138             :                                                 [keysForShips removeObjectAtIndex:si--];
    9139             :                                         }
    9140             :                                 }
    9141             :                         }
    9142             : 
    9143             :                 }
    9144             :                 
    9145             :                 NSDictionary    *systemInfo = [self generateSystemData:s];
    9146             :                 OOTechLevelID   techlevel;
    9147             :                 if (specialTL != NSNotFound)  
    9148             :                 {
    9149             :                         //if we are passed a tech level use that
    9150             :                         techlevel = specialTL;
    9151             :                 }
    9152             :                 else
    9153             :                 {
    9154             :                         //otherwise use default for system
    9155             :                         techlevel = [systemInfo oo_unsignedIntForKey:KEY_TECHLEVEL];
    9156             :                 }
    9157             :                 unsigned                ship_index = (ship_seed.d * 0x100 + ship_seed.e) % [keysForShips count];
    9158             :                 NSString                *ship_key = [keysForShips oo_stringAtIndex:ship_index];
    9159             :                 NSDictionary    *ship_info = [registry shipyardInfoForKey:ship_key];
    9160             :                 OOTechLevelID   ship_techlevel = [ship_info oo_intForKey:KEY_TECHLEVEL];
    9161             :                 
    9162             :                 double chance = 1.0 - pow(1.0 - [ship_info oo_doubleForKey:KEY_CHANCE], MAX((OOTechLevelID)1, techlevel - ship_techlevel));
    9163             :                 
    9164             :                 // seed random number generator
    9165             :                 int superRand1 = ship_seed.a * 0x10000 + ship_seed.c * 0x100 + ship_seed.e;
    9166             :                 uint32_t superRand2 = ship_seed.b * 0x10000 + ship_seed.d * 0x100 + ship_seed.f;
    9167             :                 ranrot_srand(superRand2);
    9168             :                 
    9169             :                 NSDictionary* shipBaseDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:ship_key];
    9170             :                 
    9171             :                 if ((days_until_sale > 0.0) && (days_until_sale < 30.0) && (ship_techlevel <= techlevel) && (randf() < chance) && (shipBaseDict != nil))
    9172             :                 {                       
    9173             :                         NSMutableDictionary* shipDict = [NSMutableDictionary dictionaryWithDictionary:shipBaseDict];
    9174             :                         NSMutableString* shortShipDescription = [NSMutableString stringWithCapacity:256];
    9175             :                         NSString *shipName = [shipDict oo_stringForKey:@"display_name" defaultValue:[shipDict oo_stringForKey:KEY_NAME]];
    9176             :                         OOCreditsQuantity price = [ship_info oo_unsignedIntForKey:KEY_PRICE];
    9177             :                         OOCreditsQuantity base_price = price;
    9178             :                         NSMutableArray* extras = [NSMutableArray arrayWithArray:[[ship_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]];
    9179             :                         NSString* fwdWeaponString = [[ship_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_stringForKey:KEY_EQUIPMENT_FORWARD_WEAPON];
    9180             :                         NSString* aftWeaponString = [[ship_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_stringForKey:KEY_EQUIPMENT_AFT_WEAPON];
    9181             :                         
    9182             :                         NSMutableArray* options = [NSMutableArray arrayWithArray:[ship_info oo_arrayForKey:KEY_OPTIONAL_EQUIPMENT]];
    9183             :                         OOCargoQuantity maxCargo = [shipDict oo_unsignedIntForKey:@"max_cargo"];
    9184             :                         
    9185             :                         // more info for potential purchasers - how to reveal this I'm not yet sure...
    9186             :                         //NSString* brochure_desc = [self brochureDescriptionWithDictionary: ship_dict standardEquipment: extras optionalEquipment: options];
    9187             :                         //NSLog(@"%@ Brochure description : \"%@\"", [ship_dict objectForKey:KEY_NAME], brochure_desc);
    9188             :                         
    9189             :                         [shortShipDescription appendFormat:@"%@:", shipName];
    9190             :                         
    9191             :                         OOWeaponFacingSet availableFacings = [ship_info oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:VALID_WEAPON_FACINGS] & VALID_WEAPON_FACINGS;
    9192             : 
    9193             :                         OOWeaponType fwdWeapon = OOWeaponTypeFromEquipmentIdentifierSloppy(fwdWeaponString);
    9194             :                         OOWeaponType aftWeapon = OOWeaponTypeFromEquipmentIdentifierSloppy(aftWeaponString);
    9195             :                         //port and starboard weapons are not modified in the shipyard
    9196             :                         // apply fwd and aft weapons to the ship
    9197             :                         if (fwdWeapon && fwdWeaponString) [shipDict setObject:fwdWeaponString forKey:KEY_EQUIPMENT_FORWARD_WEAPON];
    9198             :                         if (aftWeapon && aftWeaponString) [shipDict setObject:aftWeaponString forKey:KEY_EQUIPMENT_AFT_WEAPON];
    9199             :                         
    9200             :                         int passengerBerthCount = 0;
    9201             :                         BOOL customised = NO;
    9202             :                         BOOL weaponCustomized = NO;
    9203             :                         
    9204             :                         NSString *fwdWeaponDesc = nil;
    9205             :                         
    9206             :                         NSString *shortExtrasKey = @"shipyard-first-extra";
    9207             :                         
    9208             :                         // for testing condition scripts
    9209             :                         ShipEntity *testship = [[ProxyPlayerEntity alloc] initWithKey:ship_key definition:shipDict];
    9210             :                         // customise the ship (if chance = 1, then ship will get all possible add ons)
    9211             :                         while ((randf() < chance) && ([options count]))
    9212             :                         {
    9213             :                                 chance *= chance;       //decrease the chance of a further customisation (unless it is 1, which might be a bug)
    9214             :                                 int                             optionIndex = Ranrot() % [options count];
    9215             :                                 NSString                *equipmentKey = [options oo_stringAtIndex:optionIndex];
    9216             :                                 OOEquipmentType *item = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey];
    9217             :                                 
    9218             :                                 if (item != nil)
    9219             :                                 {
    9220             :                                         OOTechLevelID           eqTechLevel = [item techLevel];
    9221             :                                         OOCreditsQuantity       eqPrice = [item price] / 10;    // all amounts are x/10 due to being represented in tenths of credits.
    9222             :                                         NSString                        *eqShortDesc = [item name];
    9223             :                                         
    9224             :                                         if ([item techLevel] > techlevel)
    9225             :                                         {
    9226             :                                                 // Cap maximum tech level.
    9227             :                                                 eqTechLevel = MIN(eqTechLevel, 15U);
    9228             :                                                 
    9229             :                                                 // Higher tech items are rarer!
    9230             :                                                 if (randf() * (eqTechLevel - techlevel) < 1.0)
    9231             :                                                 {
    9232             :                                                         // All included equip has a 10% discount.
    9233             :                                                         eqPrice *= (tech_price_boost + eqTechLevel - techlevel) * 90 / 100;
    9234             :                                                 }
    9235             :                                                 else
    9236             :                                                         break;  // Bar this upgrade.
    9237             :                                         }
    9238             :                                         
    9239             :                                         if ([item incompatibleEquipment] != nil && extras != nil)
    9240             :                                         {
    9241             :                                                 NSEnumerator                            *keyEnum = nil;
    9242             :                                                 id                                                      key = nil;
    9243             :                                                 BOOL                                            incompatible = NO;
    9244             :                                                 
    9245             :                                                 for (keyEnum = [[item incompatibleEquipment] objectEnumerator]; (key = [keyEnum nextObject]); )
    9246             :                                                 {
    9247             :                                                         if ([extras containsObject:key])
    9248             :                                                         {
    9249             :                                                                 [options removeObject:equipmentKey];
    9250             :                                                                 incompatible = YES;
    9251             :                                                                 break;
    9252             :                                                         }
    9253             :                                                 }
    9254             :                                                 if (incompatible) break;
    9255             :                                                 
    9256             :                                                 // make sure the incompatible equipment is not choosen later on.
    9257             :                                                 for (keyEnum = [[item incompatibleEquipment] objectEnumerator]; (key = [keyEnum nextObject]); )
    9258             :                                                 {
    9259             :                                                         if ([options containsObject:key])
    9260             :                                                         {
    9261             :                                                                 [options removeObject:key]; 
    9262             :                                                         }
    9263             :                                                 }
    9264             :                                         }
    9265             :                                         
    9266             :                                         /* Check condition scripts */
    9267             :                                         NSString *condition_script = [item conditionScript];
    9268             :                                         if (condition_script != nil)
    9269             :                                         {
    9270             :                                                 OOJSScript *condScript = [self getConditionScript:condition_script];
    9271             :                                                 if (condScript != nil) // should always be non-nil, but just in case
    9272             :                                                 {
    9273             :                                                         JSContext                       *JScontext = OOJSAcquireContext();
    9274             :                                                         BOOL OK;
    9275             :                                                         JSBool allow_addition;
    9276             :                                                         jsval result;
    9277             :                                                         jsval args[] = { OOJSValueFromNativeObject(JScontext, equipmentKey) , OOJSValueFromNativeObject(JScontext, testship) , OOJSValueFromNativeObject(JScontext, @"newShip")};
    9278             :                                 
    9279             :                                                         OK = [condScript callMethod:OOJSID("allowAwardEquipment")
    9280             :                                                                                                                                 inContext:JScontext
    9281             :                                                                                                                 withArguments:args count:sizeof args / sizeof *args
    9282             :                                                                                                                                          result:&result];
    9283             : 
    9284             :                                                         if (OK) OK = JS_ValueToBoolean(JScontext, result, &allow_addition);
    9285             :                                 
    9286             :                                                         OOJSRelinquishContext(JScontext);
    9287             : 
    9288             :                                                         if (OK && !allow_addition)
    9289             :                                                         {
    9290             :                                                                 /* if the script exists, the function exists, the function
    9291             :                                                                  * returns a bool, and that bool is false, block
    9292             :                                                                  * addition. Otherwise allow it as default */
    9293             :                                                                 break;
    9294             :                                                         }
    9295             :                                                 }
    9296             :                                         }
    9297             : 
    9298             : 
    9299             :                                         if ([item requiresEquipment] != nil && extras != nil)
    9300             :                                         {
    9301             :                                                 NSEnumerator                            *keyEnum = nil;
    9302             :                                                 id                                                      key = nil;
    9303             :                                                 BOOL                                            missing = NO;
    9304             :                                                 
    9305             :                                                 for (keyEnum = [[item requiresEquipment] objectEnumerator]; (key = [keyEnum nextObject]); )
    9306             :                                                 {
    9307             :                                                         if (![extras containsObject:key])
    9308             :                                                         {
    9309             :                                                                 missing = YES;
    9310             :                                                         }
    9311             :                                                 }
    9312             :                                                 if (missing) break;
    9313             :                                         }
    9314             :                                         
    9315             :                                         if ([item requiresAnyEquipment] != nil && extras != nil)
    9316             :                                         {
    9317             :                                                 NSEnumerator                            *keyEnum = nil;
    9318             :                                                 id                                                      key = nil;
    9319             :                                                 BOOL                                            missing = YES;
    9320             :                                                 
    9321             :                                                 for (keyEnum = [[item requiresAnyEquipment] objectEnumerator]; (key = [keyEnum nextObject]); )
    9322             :                                                 {
    9323             :                                                         if ([extras containsObject:key])
    9324             :                                                         {
    9325             :                                                                 missing = NO;
    9326             :                                                         }
    9327             :                                                 }
    9328             :                                                 if (missing) break;
    9329             :                                         }
    9330             :                                         
    9331             :                                         // Special case, NEU has to be compatible with EEU inside equipment.plist
    9332             :                                         // but we can only have either one or the other on board.
    9333             :                                         if ([equipmentKey isEqualTo:@"EQ_NAVAL_ENERGY_UNIT"])
    9334             :                                         {
    9335             :                                                 if ([extras containsObject:@"EQ_ENERGY_UNIT"])
    9336             :                                                 {
    9337             :                                                         [options removeObject:equipmentKey];
    9338             :                                                         break;
    9339             :                                                 }
    9340             :                                         }
    9341             :                                         
    9342             :                                         if ([equipmentKey hasPrefix:@"EQ_WEAPON"])
    9343             :                                         {
    9344             :                                                 OOWeaponType new_weapon = OOWeaponTypeFromEquipmentIdentifierSloppy(equipmentKey);
    9345             :                                                 //fit best weapon forward
    9346             :                                                 if (availableFacings & WEAPON_FACING_FORWARD && [new_weapon weaponThreatAssessment] > [fwdWeapon weaponThreatAssessment])
    9347             :                                                 {
    9348             :                                                         //again remember to divide price by 10 to get credits from tenths of credit
    9349             :                                                         price -= [self getEquipmentPriceForKey:fwdWeaponString] * 90 / 1000;    // 90% credits
    9350             :                                                         price += eqPrice;
    9351             :                                                         fwdWeaponString = equipmentKey;
    9352             :                                                         fwdWeapon = new_weapon;
    9353             :                                                         [shipDict setObject:fwdWeaponString forKey:KEY_EQUIPMENT_FORWARD_WEAPON];
    9354             :                                                         weaponCustomized = YES;
    9355             :                                                         fwdWeaponDesc = eqShortDesc;
    9356             :                                                 }
    9357             :                                                 else 
    9358             :                                                 {
    9359             :                                                         //if less good than current forward, try fitting is to rear
    9360             :                                                         if (availableFacings & WEAPON_FACING_AFT && (isWeaponNone(aftWeapon) || [new_weapon weaponThreatAssessment] > [aftWeapon weaponThreatAssessment]))
    9361             :                                                         {
    9362             :                                                                 price -= [self getEquipmentPriceForKey:aftWeaponString] * 90 / 1000;    // 90% credits
    9363             :                                                                 price += eqPrice;
    9364             :                                                                 aftWeaponString = equipmentKey;
    9365             :                                                                 aftWeapon = new_weapon;
    9366             :                                                                 [shipDict setObject:aftWeaponString forKey:KEY_EQUIPMENT_AFT_WEAPON];
    9367             :                                                         }
    9368             :                                                         else 
    9369             :                                                         {
    9370             :                                                                 [options removeObject:equipmentKey]; //dont try again
    9371             :                                                         }                               
    9372             :                                                 }
    9373             :                                         
    9374             :                                         }
    9375             :                                         else
    9376             :                                         {
    9377             :                                                 if ([equipmentKey isEqualToString:@"EQ_PASSENGER_BERTH"])
    9378             :                                                 {
    9379             :                                                         if ((maxCargo >= PASSENGER_BERTH_SPACE) && (randf() < chance))
    9380             :                                                         {
    9381             :                                                                 maxCargo -= PASSENGER_BERTH_SPACE;
    9382             :                                                                 price += eqPrice;
    9383             :                                                                 [extras addObject:equipmentKey];
    9384             :                                                                 passengerBerthCount++;
    9385             :                                                                 customised = YES;
    9386             :                                                         }
    9387             :                                                         else
    9388             :                                                         {
    9389             :                                                                 // remove the option if there's no space left
    9390             :                                                                 [options removeObject:equipmentKey];
    9391             :                                                         }
    9392             :                                                 }
    9393             :                                                 else
    9394             :                                                 {
    9395             :                                                         price += eqPrice;
    9396             :                                                         [extras addObject:equipmentKey];
    9397             :                                                         if ([item isVisible])
    9398             :                                                         {
    9399             :                                                                 NSString *item = eqShortDesc;
    9400             :                                                                 [shortShipDescription appendString:OOExpandKey(shortExtrasKey, item)];
    9401             :                                                                 shortExtrasKey = @"shipyard-additional-extra";
    9402             :                                                         }
    9403             :                                                         customised = YES;
    9404             :                                                         [options removeObject:equipmentKey]; //dont add twice
    9405             :                                                 }
    9406             :                                         }
    9407             :                                 }
    9408             :                                 else
    9409             :                                 {
    9410             :                                         [options removeObject:equipmentKey];
    9411             :                                 }
    9412             :                         } // end adding optional equipment
    9413             :                         [testship release];
    9414             :                         // i18n: Some languages require that no conversion to lower case string takes place.
    9415             :                         BOOL lowercaseIgnore = [[self descriptions] oo_boolForKey:@"lowercase_ignore"];
    9416             :                         
    9417             :                         if (passengerBerthCount)
    9418             :                         {
    9419             :                                 NSString* npb = (passengerBerthCount > 1)? [NSString stringWithFormat:@"%d ", passengerBerthCount] : (id)@"";
    9420             :                                 NSString* ppb = DESC_PLURAL(@"passenger-berth", passengerBerthCount);
    9421             :                                 NSString* extraPassengerBerthsDescription = [NSString stringWithFormat:DESC(@"extra-@-@-(passenger-berths)"), npb, ppb];
    9422             :                                 NSString *item = extraPassengerBerthsDescription;
    9423             :                                 [shortShipDescription appendString:OOExpandKey(shortExtrasKey, item)];
    9424             :                                 shortExtrasKey = @"shipyard-additional-extra";
    9425             :                         }
    9426             :                         
    9427             :                         if (!customised)
    9428             :                         {
    9429             :                                 [shortShipDescription appendString:OOExpandKey(@"shipyard-standard-customer-model")];
    9430             :                         }
    9431             :                         
    9432             :                         if (weaponCustomized)
    9433             :                         {
    9434             :                                 NSString *weapon = (lowercaseIgnore ? fwdWeaponDesc : [fwdWeaponDesc lowercaseString]);
    9435             :                                 [shortShipDescription appendString:OOExpandKey(@"shipyard-forward-weapon-upgraded", weapon)];
    9436             :                         }
    9437             :                         if (price > base_price)
    9438             :                         {
    9439             :                                 price = base_price + cunningFee(price - base_price, 0.05);
    9440             :                         }
    9441             :                         
    9442             :                         [shortShipDescription appendString:OOExpandKey(@"shipyard-price", price)];
    9443             :                         
    9444             :                         NSString *shipID = [NSString stringWithFormat:@"%06x-%06x", superRand1, superRand2];
    9445             :                         
    9446             :                         uint16_t personality = RanrotWithSeed(&personalitySeed) & ENTITY_PERSONALITY_MAX;
    9447             :                         
    9448             :                         NSDictionary *ship_info_dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    9449             :                                 shipID,                                                         SHIPYARD_KEY_ID,
    9450             :                                 ship_key,                                                       SHIPYARD_KEY_SHIPDATA_KEY,
    9451             :                                 shipDict,                                                       SHIPYARD_KEY_SHIP,
    9452             :                                 shortShipDescription,                           KEY_SHORT_DESCRIPTION,
    9453             :                                 [NSNumber numberWithUnsignedLongLong:price], SHIPYARD_KEY_PRICE,
    9454             :                                 extras,                                                         KEY_EQUIPMENT_EXTRAS,
    9455             :                                 [NSNumber numberWithUnsignedShort:personality], SHIPYARD_KEY_PERSONALITY,                                                                 
    9456             :                                 NULL];
    9457             :                         
    9458             :                         [resultDictionary setObject:ship_info_dictionary forKey:shipID];        // should order them fairly randomly
    9459             :                 }
    9460             :                 
    9461             :                 // next contract
    9462             :                 rotate_seed(&ship_seed);
    9463             :                 rotate_seed(&ship_seed);
    9464             :                 rotate_seed(&ship_seed);
    9465             :                 rotate_seed(&ship_seed);
    9466             :         }
    9467             :         
    9468             :         NSMutableArray *resultArray = [[[resultDictionary allValues] mutableCopy] autorelease];
    9469             :         [resultArray sortUsingFunction:compareName context:NULL];
    9470             :         
    9471             :         // remove identically priced ships of the same name
    9472             :         i = 1;
    9473             :         
    9474             :         while (i < [resultArray count])
    9475             :         {
    9476             :                 if (compareName([resultArray objectAtIndex:i - 1], [resultArray objectAtIndex:i], nil) == NSOrderedSame )
    9477             :                 {
    9478             :                         [resultArray removeObjectAtIndex: i];
    9479             :                 }
    9480             :                 else
    9481             :                 {
    9482             :                         i++;
    9483             :                 }
    9484             :         }
    9485             :         
    9486             :         RANROTSetFullSeed(saved_seed);
    9487             : 
    9488             :         return [NSArray arrayWithArray:resultArray];
    9489             : }
    9490             : 
    9491             : 
    9492           0 : static OOComparisonResult compareName(id dict1, id dict2, void *context)
    9493             : {
    9494             :         NSDictionary    *ship1 = [(NSDictionary *)dict1 oo_dictionaryForKey:SHIPYARD_KEY_SHIP];
    9495             :         NSDictionary    *ship2 = [(NSDictionary *)dict2 oo_dictionaryForKey:SHIPYARD_KEY_SHIP];
    9496             :         NSString                *name1 = [ship1 oo_stringForKey:KEY_NAME];
    9497             :         NSString                *name2 = [ship2 oo_stringForKey:KEY_NAME];
    9498             :         
    9499             :         NSComparisonResult result = [[name1 lowercaseString] compare:[name2 lowercaseString]];
    9500             :         if (result != NSOrderedSame)
    9501             :                 return result;
    9502             :         else
    9503             :                 return comparePrice(dict1, dict2, context);
    9504             : }
    9505             : 
    9506             : 
    9507           0 : static OOComparisonResult comparePrice(id dict1, id dict2, void *context)
    9508             : {
    9509             :         NSNumber                *price1 = [(NSDictionary *)dict1 objectForKey:SHIPYARD_KEY_PRICE];
    9510             :         NSNumber                *price2 = [(NSDictionary *)dict2 objectForKey:SHIPYARD_KEY_PRICE];
    9511             :         
    9512             :         return [price1 compare:price2];
    9513             : }
    9514             : 
    9515             : 
    9516             : - (OOCreditsQuantity) tradeInValueForCommanderDictionary:(NSDictionary *)dict
    9517             : {
    9518             :         // get basic information about the craft
    9519             :         OOCreditsQuantity       base_price = 0ULL;
    9520             :         NSString                        *ship_desc = [dict oo_stringForKey:@"ship_desc"];
    9521             :         NSDictionary            *shipyard_info = [[OOShipRegistry sharedRegistry] shipyardInfoForKey:ship_desc];
    9522             :         // This checks a rare, but possible case. If the ship for which we are trying to calculate a trade in value
    9523             :         // does not have a shipyard dictionary entry, report it and set its base price to 0 -- Nikos 20090613.
    9524             :         if (shipyard_info == nil)
    9525             :         {
    9526             :                 OOLogERR(@"universe.tradeInValueForCommanderDictionary.valueCalculationError",
    9527             :                         @"Shipyard dictionary entry for ship %@ required for trade in value calculation, but does not exist. Setting ship value to 0.", ship_desc);
    9528             :         }
    9529             :         else
    9530             :         {
    9531             :                 base_price = [shipyard_info oo_unsignedLongLongForKey:SHIPYARD_KEY_PRICE defaultValue:0ULL];
    9532             :         }
    9533             :         
    9534             :         if(base_price == 0ULL) return base_price;
    9535             :         
    9536             :         OOCreditsQuantity       scrap_value = 351; // translates to 250 cr.
    9537             :         
    9538             :         OOWeaponType            ship_fwd_weapon = [OOEquipmentType equipmentTypeWithIdentifier:[dict oo_stringForKey:@"forward_weapon"]];
    9539             :         OOWeaponType            ship_aft_weapon = [OOEquipmentType equipmentTypeWithIdentifier:[dict oo_stringForKey:@"aft_weapon"]];
    9540             :         OOWeaponType            ship_port_weapon = [OOEquipmentType equipmentTypeWithIdentifier:[dict oo_stringForKey:@"port_weapon"]];
    9541             :         OOWeaponType            ship_starboard_weapon = [OOEquipmentType equipmentTypeWithIdentifier:[dict oo_stringForKey:@"starboard_weapon"]];
    9542             :         unsigned                        ship_missiles = [dict oo_unsignedIntForKey:@"missiles"];
    9543             :         unsigned                        ship_max_passengers = [dict oo_unsignedIntForKey:@"max_passengers"];
    9544             :         NSMutableArray          *ship_extra_equipment = [NSMutableArray arrayWithArray:[[dict oo_dictionaryForKey:@"extra_equipment"] allKeys]];
    9545             :         
    9546             :         NSDictionary            *basic_info = [shipyard_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT];
    9547             :         unsigned                        base_missiles = [basic_info oo_unsignedIntForKey:KEY_EQUIPMENT_MISSILES];
    9548             :         OOCreditsQuantity       base_missiles_value = base_missiles * [UNIVERSE getEquipmentPriceForKey:@"EQ_MISSILE"] / 10;
    9549             :         NSString                        *base_weapon_key = [basic_info oo_stringForKey:KEY_EQUIPMENT_FORWARD_WEAPON];
    9550             :         OOCreditsQuantity       base_weapons_value = [UNIVERSE getEquipmentPriceForKey:base_weapon_key] / 10;
    9551             :         NSMutableArray          *base_extra_equipment = [NSMutableArray arrayWithArray:[basic_info oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]];
    9552             :         NSString                        *weapon_key = nil;
    9553             :         
    9554             :         // was aft_weapon defined as standard equipment ?
    9555             :         base_weapon_key = [basic_info oo_stringForKey:KEY_EQUIPMENT_AFT_WEAPON defaultValue:nil];
    9556             :         if (base_weapon_key != nil)
    9557             :                 base_weapons_value += [UNIVERSE getEquipmentPriceForKey:base_weapon_key] / 10;
    9558             :         
    9559             :         OOCreditsQuantity       ship_main_weapons_value = 0;
    9560             :         OOCreditsQuantity       ship_other_weapons_value = 0;
    9561             :         OOCreditsQuantity       ship_missiles_value = 0;
    9562             : 
    9563             :         // calculate the actual value for the missiles present on board.
    9564             :         NSArray *missileRoles = [dict oo_arrayForKey:@"missile_roles"];
    9565             :         if (missileRoles != nil)
    9566             :         {
    9567             :                 unsigned i;
    9568             :                 for (i = 0; i < ship_missiles; i++)
    9569             :                 {
    9570             :                         NSString *missile_desc = [missileRoles oo_stringAtIndex:i];
    9571             :                         if (missile_desc != nil && ![missile_desc isEqualToString:@"NONE"])
    9572             :                         {
    9573             :                                 ship_missiles_value += [UNIVERSE getEquipmentPriceForKey:missile_desc] / 10;
    9574             :                         }
    9575             :                 }
    9576             :         }
    9577             :         else
    9578             :                 ship_missiles_value = ship_missiles * [UNIVERSE getEquipmentPriceForKey:@"EQ_MISSILE"] / 10;
    9579             :         
    9580             :         // needs to be a signed value, we can then subtract from the base price, if less than standard equipment.
    9581             :         long long extra_equipment_value = ship_max_passengers * [UNIVERSE getEquipmentPriceForKey:@"EQ_PASSENGER_BERTH"]/10;
    9582             :         
    9583             :         // add on missile values
    9584             :         extra_equipment_value += ship_missiles_value - base_missiles_value;
    9585             :         
    9586             :         // work out weapon values
    9587             :         if (ship_fwd_weapon)
    9588             :         {
    9589             :                 weapon_key = OOEquipmentIdentifierFromWeaponType(ship_fwd_weapon);
    9590             :                 ship_main_weapons_value = [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10;
    9591             :         }
    9592             :         if (ship_aft_weapon)
    9593             :         {
    9594             :                 weapon_key = OOEquipmentIdentifierFromWeaponType(ship_aft_weapon);
    9595             :                 if (base_weapon_key != nil) // aft weapon was defined as a base weapon
    9596             :                 {
    9597             :                         ship_main_weapons_value += [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10;  //take weapon downgrades into account
    9598             :                 }
    9599             :                 else
    9600             :                 {
    9601             :                         ship_other_weapons_value += [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10;
    9602             :                 }
    9603             :         }
    9604             :         if (ship_port_weapon)
    9605             :         {
    9606             :                 weapon_key = OOEquipmentIdentifierFromWeaponType(ship_port_weapon);
    9607             :                 ship_other_weapons_value += [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10;
    9608             :         }
    9609             :         if (ship_starboard_weapon)
    9610             :         {
    9611             :                 weapon_key = OOEquipmentIdentifierFromWeaponType(ship_starboard_weapon);
    9612             :                 ship_other_weapons_value += [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10;
    9613             :         }
    9614             :         
    9615             :         // add on extra weapons, take away the value of the base weapons
    9616             :         extra_equipment_value += ship_other_weapons_value;
    9617             :         extra_equipment_value += ship_main_weapons_value - base_weapons_value;
    9618             :         
    9619             :         NSInteger i;
    9620             :         NSString *eq_key = nil;
    9621             :         
    9622             :         // shipyard.plist settings might have duplicate keys.
    9623             :         // cull possible duplicates from inside base equipment
    9624             :         for (i = [base_extra_equipment count]-1; i > 0;i--)
    9625             :         {
    9626             :                 eq_key = [base_extra_equipment oo_stringAtIndex:i];
    9627             :                 if ([base_extra_equipment indexOfObject:eq_key inRange:NSMakeRange(0, i-1)] != NSNotFound)
    9628             :                                                                 [base_extra_equipment removeObjectAtIndex:i];
    9629             :         }
    9630             :         
    9631             :         // do we at least have the same equipment as a standard ship? 
    9632             :         for (i = [base_extra_equipment count]-1; i >= 0; i--)
    9633             :         {
    9634             :                 eq_key = [base_extra_equipment oo_stringAtIndex:i];
    9635             :                 if ([ship_extra_equipment containsObject:eq_key])
    9636             :                                 [ship_extra_equipment removeObject:eq_key];
    9637             :                 else // if the ship has less equipment than standard, deduct the missing equipent's price
    9638             :                                 extra_equipment_value -= ([UNIVERSE getEquipmentPriceForKey:eq_key] / 10);
    9639             :         }
    9640             :         
    9641             :         // remove portable equipment from the totals
    9642             :         OOEquipmentType *item = nil;
    9643             :         
    9644             :         for (i = [ship_extra_equipment count]-1; i >= 0; i--)
    9645             :         {
    9646             :                 eq_key = [ship_extra_equipment oo_stringAtIndex:i];
    9647             :                 item = [OOEquipmentType equipmentTypeWithIdentifier:eq_key];
    9648             :                 if ([item isPortableBetweenShips]) [ship_extra_equipment removeObjectAtIndex:i];
    9649             :         }
    9650             :         
    9651             :         // add up what we've got left.
    9652             :         for (i = [ship_extra_equipment count]-1; i >= 0; i--)
    9653             :                 extra_equipment_value += ([UNIVERSE getEquipmentPriceForKey:[ship_extra_equipment oo_stringAtIndex:i]] / 10);           
    9654             :         
    9655             :         // 10% discount for second hand value, steeper reduction if worse than standard.
    9656             :         extra_equipment_value *= extra_equipment_value < 0 ? 1.4 : 0.9;
    9657             :         
    9658             :         // we'll return at least the scrap value
    9659             :         // TODO: calculate scrap value based on the size of the ship.
    9660             :         if ((long long)scrap_value > (long long)base_price + extra_equipment_value) return scrap_value;
    9661             :         
    9662             :         return base_price + extra_equipment_value;
    9663             : }
    9664             : 
    9665             : 
    9666             : - (NSString *) brochureDescriptionWithDictionary:(NSDictionary *)dict standardEquipment:(NSArray *)extras optionalEquipment:(NSArray *)options
    9667             : {
    9668             :         NSMutableArray  *mut_extras = [NSMutableArray arrayWithArray:extras];
    9669             :         NSString                *allOptions = [options componentsJoinedByString:@" "];
    9670             :         
    9671             :         NSMutableString *desc = [NSMutableString stringWithFormat:@"The %@.", [dict oo_stringForKey: KEY_NAME]];
    9672             :         
    9673             :         // cargo capacity and expansion
    9674             :         OOCargoQuantity max_cargo = [dict oo_unsignedIntForKey:@"max_cargo"];
    9675             :         if (max_cargo)
    9676             :         {
    9677             :                 OOCargoQuantity extra_cargo = [dict oo_unsignedIntForKey:@"extra_cargo" defaultValue:15];
    9678             :                 [desc appendFormat:@" Cargo capacity %dt", max_cargo];
    9679             :                 BOOL canExpand = ([allOptions rangeOfString:@"EQ_CARGO_BAY"].location != NSNotFound);
    9680             :                 if (canExpand)
    9681             :                         [desc appendFormat:@" (expandable to %dt at most starports)", max_cargo + extra_cargo];
    9682             :                 [desc appendString:@"."];
    9683             :         }
    9684             :         
    9685             :         // speed
    9686             :         float top_speed = [dict oo_intForKey:@"max_flight_speed"];
    9687             :         [desc appendFormat:@" Top speed %.3fLS.", 0.001 * top_speed];
    9688             :         
    9689             :         // passenger berths
    9690             :         if ([mut_extras count])
    9691             :         {
    9692             :                 unsigned n_berths = 0;
    9693             :                 unsigned i;
    9694             :                 for (i = 0; i < [mut_extras count]; i++)
    9695             :                 {
    9696             :                         NSString* item_key = [mut_extras oo_stringAtIndex:i];
    9697             :                         if ([item_key isEqual:@"EQ_PASSENGER_BERTH"])
    9698             :                         {
    9699             :                                 n_berths++;
    9700             :                                 [mut_extras removeObjectAtIndex:i--];
    9701             :                         }
    9702             :                 }
    9703             :                 if (n_berths)
    9704             :                 {
    9705             :                         if (n_berths == 1)
    9706             :                                 [desc appendString:@" Includes luxury accomodation for a single passenger."];
    9707             :                         else
    9708             :                                 [desc appendFormat:@" Includes luxury accomodation for %d passengers.", n_berths];
    9709             :                 }
    9710             :         }
    9711             :         
    9712             :         // standard fittings
    9713             :         if ([mut_extras count])
    9714             :         {
    9715             :                 [desc appendString:@"\nComes with"];
    9716             :                 unsigned i, j;
    9717             :                 for (i = 0; i < [mut_extras count]; i++)
    9718             :                 {
    9719             :                         NSString* item_key = [mut_extras oo_stringAtIndex:i];
    9720             :                         NSString* item_desc = nil;
    9721             :                         for (j = 0; ((j < [equipmentData count])&&(!item_desc)) ; j++)
    9722             :                         {
    9723             :                                 NSString *eq_type = [[equipmentData oo_arrayAtIndex:j] oo_stringAtIndex:EQUIPMENT_KEY_INDEX];
    9724             :                                 if ([eq_type isEqual:item_key])
    9725             :                                         item_desc = [[equipmentData oo_arrayAtIndex:j] oo_stringAtIndex:EQUIPMENT_SHORT_DESC_INDEX];
    9726             :                         }
    9727             :                         if (item_desc)
    9728             :                         {
    9729             :                                 switch ([mut_extras count] - i)
    9730             :                                 {
    9731             :                                         case 1:
    9732             :                                                 [desc appendFormat:@" %@ fitted as standard.", item_desc];
    9733             :                                                 break;
    9734             :                                         case 2:
    9735             :                                                 [desc appendFormat:@" %@ and", item_desc];
    9736             :                                                 break;
    9737             :                                         default:
    9738             :                                                 [desc appendFormat:@" %@,", item_desc];
    9739             :                                                 break;
    9740             :                                 }
    9741             :                         }
    9742             :                 }
    9743             :         }
    9744             :         
    9745             :         // optional fittings
    9746             :         if ([options count])
    9747             :         {
    9748             :                 [desc appendString:@"\nCan additionally be outfitted with"];
    9749             :                 unsigned i, j;
    9750             :                 for (i = 0; i < [options count]; i++)
    9751             :                 {
    9752             :                         NSString* item_key = [options oo_stringAtIndex:i];
    9753             :                         NSString* item_desc = nil;
    9754             :                         for (j = 0; ((j < [equipmentData count])&&(!item_desc)) ; j++)
    9755             :                         {
    9756             :                                 NSString *eq_type = [[equipmentData oo_arrayAtIndex:j] oo_stringAtIndex:EQUIPMENT_KEY_INDEX];
    9757             :                                 if ([eq_type isEqual:item_key])
    9758             :                                         item_desc = [[equipmentData oo_arrayAtIndex:j] oo_stringAtIndex:EQUIPMENT_SHORT_DESC_INDEX];
    9759             :                         }
    9760             :                         if (item_desc)
    9761             :                         {
    9762             :                                 switch ([options count] - i)
    9763             :                                 {
    9764             :                                         case 1:
    9765             :                                                 [desc appendFormat:@" %@ at suitably equipped starports.", item_desc];
    9766             :                                                 break;
    9767             :                                         case 2:
    9768             :                                                 [desc appendFormat:@" %@ and/or", item_desc];
    9769             :                                                 break;
    9770             :                                         default:
    9771             :                                                 [desc appendFormat:@" %@,", item_desc];
    9772             :                                                 break;
    9773             :                                 }
    9774             :                         }
    9775             :                 }
    9776             :         }
    9777             :         
    9778             :         return desc;
    9779             : }
    9780             : 
    9781             : 
    9782             : - (HPVector) getWitchspaceExitPosition
    9783             : {
    9784             :         return kZeroHPVector;
    9785             : }
    9786             : 
    9787             : 
    9788             : - (Quaternion) getWitchspaceExitRotation
    9789             : {
    9790             :         // this should be fairly close to {0,0,0,1}
    9791             :         Quaternion q_result;
    9792             : 
    9793             : // CIM: seems to be no reason why this should be a per-system constant
    9794             : //  - trying it without resetting the RNG for now
    9795             : //      seed_RNG_only_for_planet_description(system_seed);
    9796             :         
    9797             :         q_result.x = (gen_rnd_number() - 128)/1024.0;
    9798             :         q_result.y = (gen_rnd_number() - 128)/1024.0;
    9799             :         q_result.z = (gen_rnd_number() - 128)/1024.0;
    9800             :         q_result.w = 1.0;
    9801             :         quaternion_normalize(&q_result);
    9802             :         
    9803             :         return q_result;
    9804             : }
    9805             : 
    9806             : // FIXME: should use vector functions
    9807             : - (HPVector) getSunSkimStartPositionForShip:(ShipEntity*) ship
    9808             : {
    9809             :         if (!ship)
    9810             :         {
    9811             :                 OOLog(kOOLogParameterError, @"%@", @"***** No ship set in Universe getSunSkimStartPositionForShip:");
    9812             :                 return kZeroHPVector;
    9813             :         }
    9814             :         OOSunEntity* the_sun = [self sun];
    9815             :         // get vector from sun position to ship
    9816             :         if (!the_sun)
    9817             :         {
    9818             :                 OOLog(kOOLogInconsistentState, @"%@", @"***** No sun set in Universe getSunSkimStartPositionForShip:");
    9819             :                 return kZeroHPVector;
    9820             :         }
    9821             :         HPVector v0 = the_sun->position;
    9822             :         HPVector v1 = ship->position;
    9823             :         v1.x -= v0.x;   v1.y -= v0.y;   v1.z -= v0.z;   // vector from sun to ship
    9824             :         if (v1.x||v1.y||v1.z)
    9825             :                 v1 = HPvector_normal(v1);
    9826             :         else
    9827             :                 v1.z = 1.0;
    9828             :         double radius = SUN_SKIM_RADIUS_FACTOR * the_sun->collision_radius - 250.0; // 250 m inside the skim radius
    9829             :         v1.x *= radius; v1.y *= radius; v1.z *= radius;
    9830             :         v1.x += v0.x;   v1.y += v0.y;   v1.z += v0.z;
    9831             :         
    9832             :         return v1;
    9833             : }
    9834             : 
    9835             : // FIXME: should use vector functions
    9836             : - (HPVector) getSunSkimEndPositionForShip:(ShipEntity*) ship
    9837             : {
    9838             :         OOSunEntity* the_sun = [self sun];
    9839             :         if (!ship)
    9840             :         {
    9841             :                 OOLog(kOOLogParameterError, @"%@", @"***** No ship set in Universe getSunSkimEndPositionForShip:");
    9842             :                 return kZeroHPVector;
    9843             :         }
    9844             :         // get vector from sun position to ship
    9845             :         if (!the_sun)
    9846             :         {
    9847             :                 OOLog(kOOLogInconsistentState, @"%@", @"***** No sun set in Universe getSunSkimEndPositionForShip:");
    9848             :                 return kZeroHPVector;
    9849             :         }
    9850             :         HPVector v0 = the_sun->position;
    9851             :         HPVector v1 = ship->position;
    9852             :         v1.x -= v0.x;   v1.y -= v0.y;   v1.z -= v0.z;
    9853             :         if (v1.x||v1.y||v1.z)
    9854             :                 v1 = HPvector_normal(v1);
    9855             :         else
    9856             :                 v1.z = 1.0;
    9857             :         HPVector v2 = make_HPvector(randf()-0.5, randf()-0.5, randf()-0.5);     // random vector
    9858             :         if (v2.x||v2.y||v2.z)
    9859             :                 v2 = HPvector_normal(v2);
    9860             :         else
    9861             :                 v2.x = 1.0;
    9862             :         HPVector v3 = HPcross_product(v1, v2);  // random vector at 90 degrees to v1 and v2 (random Vector)
    9863             :         if (v3.x||v3.y||v3.z)
    9864             :                 v3 = HPvector_normal(v3);
    9865             :         else
    9866             :                 v3.y = 1.0;
    9867             :         double radius = SUN_SKIM_RADIUS_FACTOR * the_sun->collision_radius - 250.0; // 250 m inside the skim radius
    9868             :         v1.x *= radius; v1.y *= radius; v1.z *= radius;
    9869             :         v1.x += v0.x;   v1.y += v0.y;   v1.z += v0.z;
    9870             :         v1.x += 15000 * v3.x;   v1.y += 15000 * v3.y;   v1.z += 15000 * v3.z;   // point 15000m at a tangent to sun from v1
    9871             :         v1.x -= v0.x;   v1.y -= v0.y;   v1.z -= v0.z;
    9872             :         if (v1.x||v1.y||v1.z)
    9873             :                 v1 = HPvector_normal(v1);
    9874             :         else
    9875             :                 v1.z = 1.0;
    9876             :         v1.x *= radius; v1.y *= radius; v1.z *= radius;
    9877             :         v1.x += v0.x;   v1.y += v0.y;   v1.z += v0.z;
    9878             :         
    9879             :         return v1;
    9880             : }
    9881             : 
    9882             : 
    9883             : - (NSArray *) listBeaconsWithCode:(NSString *)code
    9884             : {
    9885             :         NSMutableArray  *result = [NSMutableArray array];
    9886             :         Entity <OOBeaconEntity>           *beacon = [self firstBeacon];
    9887             :         
    9888             :         while (beacon != nil)
    9889             :         {
    9890             :                 NSString *beaconCode = [beacon beaconCode];
    9891             :                 if ([beaconCode rangeOfString:code options: NSCaseInsensitiveSearch].location != NSNotFound)
    9892             :                 {
    9893             :                         [result addObject:beacon];
    9894             :                 }
    9895             :                 beacon = [beacon nextBeacon];
    9896             :         }
    9897             :         
    9898             :         return [result sortedArrayUsingSelector:@selector(compareBeaconCodeWith:)];
    9899             : }
    9900             : 
    9901             : 
    9902             : - (void) allShipsDoScriptEvent:(jsid)event andReactToAIMessage:(NSString *)message
    9903             : {
    9904             :         int i;
    9905             :         int ent_count = n_entities;
    9906             :         int ship_count = 0;
    9907             :         ShipEntity* my_ships[ent_count];
    9908             :         for (i = 0; i < ent_count; i++)
    9909             :         {
    9910             :                 if (sortedEntities[i]->isShip)
    9911             :                 {
    9912             :                         my_ships[ship_count++] = [(ShipEntity *)sortedEntities[i] retain];      // retained
    9913             :                 }
    9914             :         }
    9915             :         
    9916             :         for (i = 0; i < ship_count; i++)
    9917             :         {
    9918             :                 ShipEntity* se = my_ships[i];
    9919             :                 [se doScriptEvent:event];
    9920             :                 if (message != nil)  [[se getAI] reactToMessage:message context:@"global message"];
    9921             :                 [se release]; //        released
    9922             :         }
    9923             : }
    9924             : 
    9925             : ///////////////////////////////////////
    9926             : 
    9927             : - (GuiDisplayGen *) gui
    9928             : {
    9929             :         return gui;
    9930             : }
    9931             : 
    9932             : 
    9933             : - (GuiDisplayGen *) commLogGUI
    9934             : {
    9935             :         return comm_log_gui;
    9936             : }
    9937             : 
    9938             : 
    9939             : - (GuiDisplayGen *) messageGUI
    9940             : {
    9941             :         return message_gui;
    9942             : }
    9943             : 
    9944             : 
    9945             : - (void) clearGUIs
    9946             : {
    9947             :         [gui clear];
    9948             :         [message_gui clear];
    9949             :         [comm_log_gui clear];
    9950             :         [comm_log_gui printLongText:DESC(@"communications-log-string")
    9951             :                                                   align:GUI_ALIGN_CENTER color:[OOColor yellowColor] fadeTime:0 key:nil addToArray:nil];
    9952             : }
    9953             : 
    9954             : 
    9955             : - (void) resetCommsLogColor
    9956             : {
    9957             :         [comm_log_gui setTextColor:[OOColor whiteColor]];
    9958             : }
    9959             : 
    9960             : 
    9961             : - (void) setDisplayText:(BOOL) value
    9962             : {
    9963             :         displayGUI = !!value;
    9964             : }
    9965             : 
    9966             : 
    9967             : - (BOOL) displayGUI
    9968             : {
    9969             :         return displayGUI;
    9970             : }
    9971             : 
    9972             : 
    9973             : - (void) setDisplayFPS:(BOOL) value
    9974             : {
    9975             :         displayFPS = !!value;
    9976             : }
    9977             : 
    9978             : 
    9979             : - (BOOL) displayFPS
    9980             : {
    9981             :         return displayFPS;
    9982             : }
    9983             : 
    9984             : 
    9985             : - (void) setAutoSave:(BOOL) value
    9986             : {
    9987             :         autoSave = !!value;
    9988             :         [[NSUserDefaults standardUserDefaults] setBool:autoSave forKey:@"autosave"];
    9989             : }
    9990             : 
    9991             : 
    9992             : - (BOOL) autoSave
    9993             : {
    9994             :         return autoSave;
    9995             : }
    9996             : 
    9997             : 
    9998             : - (void) setAutoSaveNow:(BOOL) value
    9999             : {
   10000             :         autoSaveNow = !!value;
   10001             : }
   10002             : 
   10003             : 
   10004             : - (BOOL) autoSaveNow
   10005             : {
   10006             :         return autoSaveNow;
   10007             : }
   10008             : 
   10009             : 
   10010             : - (void) setWireframeGraphics:(BOOL) value
   10011             : {
   10012             :         wireframeGraphics = !!value;
   10013             :         [[NSUserDefaults standardUserDefaults] setBool:wireframeGraphics forKey:@"wireframe-graphics"];
   10014             : }
   10015             : 
   10016             : 
   10017             : - (BOOL) wireframeGraphics
   10018             : {
   10019             :         return wireframeGraphics;
   10020             : }
   10021             : 
   10022             : 
   10023             : - (BOOL) reducedDetail
   10024             : {
   10025             :         return detailLevel == DETAIL_LEVEL_MINIMUM;
   10026             : }
   10027             : 
   10028             : 
   10029             : /* Only to be called directly at initialisation */
   10030           0 : - (void) setDetailLevelDirectly:(OOGraphicsDetail)value
   10031             : {
   10032             :         if (value >= DETAIL_LEVEL_MAXIMUM)
   10033             :         {
   10034             :                 value = DETAIL_LEVEL_MAXIMUM;
   10035             :         }
   10036             :         else if (value <= DETAIL_LEVEL_MINIMUM)
   10037             :         {
   10038             :                 value = DETAIL_LEVEL_MINIMUM;
   10039             :         }
   10040             :         if (![[OOOpenGLExtensionManager sharedManager] shadersSupported])
   10041             :         {
   10042             :                 value = DETAIL_LEVEL_MINIMUM;
   10043             :         }
   10044             :         detailLevel = value;
   10045             : }
   10046             : 
   10047             : 
   10048             : - (void) setDetailLevel:(OOGraphicsDetail)value
   10049             : {
   10050             :         OOGraphicsDetail old = detailLevel;
   10051             :         [self setDetailLevelDirectly:value];
   10052             :         [[NSUserDefaults standardUserDefaults] setInteger:detailLevel forKey:@"detailLevel"];
   10053             :         // if changed then reset graphics state
   10054             :         // (some items now require this even if shader on/off mode unchanged)
   10055             :         if (old != detailLevel)
   10056             :         {
   10057             :                 OOLog(@"rendering.detail-level", @"Detail level set to %@.", OOStringFromGraphicsDetail(detailLevel));
   10058             :                 [[OOGraphicsResetManager sharedManager] resetGraphicsState];
   10059             :         }
   10060             : 
   10061             : }
   10062             : 
   10063             : - (OOGraphicsDetail) detailLevel
   10064             : {
   10065             :         return detailLevel;
   10066             : }
   10067             : 
   10068             : 
   10069             : - (BOOL) useShaders
   10070             : {
   10071             :         return detailLevel >= DETAIL_LEVEL_SHADERS;
   10072             : }
   10073             : 
   10074             : 
   10075             : - (void) handleOoliteException:(NSException *)exception
   10076             : {
   10077             :         if (exception != nil)
   10078             :         {
   10079             :                 if ([[exception name] isEqual:OOLITE_EXCEPTION_FATAL])
   10080             :                 {
   10081             :                         PlayerEntity *player = PLAYER;
   10082             :                         [player setStatus:STATUS_HANDLING_ERROR];
   10083             :                         
   10084             :                         OOLog(kOOLogException, @"***** Handling Fatal : %@ : %@ *****",[exception name], [exception reason]);
   10085             :                         NSString* exception_msg = [NSString stringWithFormat:@"Exception : %@ : %@ Please take a screenshot and/or press esc or Q to quit.", [exception name], [exception reason]];
   10086             :                         [self addMessage:exception_msg forCount:30.0];
   10087             :                         [[self gameController] setGamePaused:YES];
   10088             :                 }
   10089             :                 else
   10090             :                 {
   10091             :                         OOLog(kOOLogException, @"***** Handling Non-fatal : %@ : %@ *****",[exception name], [exception reason]);
   10092             :                 }
   10093             :         }
   10094             : }
   10095             : 
   10096             : 
   10097             : - (GLfloat)airResistanceFactor
   10098             : {
   10099             :         return airResistanceFactor;
   10100             : }
   10101             : 
   10102             : 
   10103             : - (void) setAirResistanceFactor:(GLfloat)newFactor
   10104             : {
   10105             :         airResistanceFactor = OOClamp_0_1_f(newFactor);
   10106             : }
   10107             : 
   10108             : 
   10109             : // speech routines
   10110             : #if OOLITE_MAC_OS_X
   10111             : 
   10112             : - (void) startSpeakingString:(NSString *) text
   10113             : {
   10114             :         [speechSynthesizer startSpeakingString:[NSString stringWithFormat:@"[[volm %.3f]]%@", 0.3333333f * [OOSound masterVolume], text]];
   10115             : }
   10116             : 
   10117             : 
   10118             : - (void) stopSpeaking
   10119             : {
   10120             :         if ([speechSynthesizer respondsToSelector:@selector(stopSpeakingAtBoundary:)])
   10121             :         {
   10122             :                 [speechSynthesizer stopSpeakingAtBoundary:NSSpeechWordBoundary];
   10123             :         }
   10124             :         else
   10125             :         {
   10126             :                 [speechSynthesizer stopSpeaking];
   10127             :         }
   10128             : }
   10129             : 
   10130             : 
   10131             : - (BOOL) isSpeaking
   10132             : {
   10133             :         return [speechSynthesizer isSpeaking];
   10134             : }
   10135             : 
   10136             : #elif OOLITE_ESPEAK
   10137             : 
   10138             : - (void) startSpeakingString:(NSString *) text
   10139             : {
   10140             :         NSData *utf8 = [text dataUsingEncoding:NSUTF8StringEncoding];
   10141             :         
   10142             :         if (utf8 != nil)        // we have a valid UTF-8 string
   10143             :         {
   10144             :                 const char *stringToSay = [text UTF8String];
   10145             :                 espeak_Synth(stringToSay, strlen(stringToSay) + 1 /* inc. NULL */, 0, POS_CHARACTER, 0, espeakCHARS_UTF8 | espeakPHONEMES | espeakENDPAUSE, NULL, NULL);
   10146             :         }
   10147             : }
   10148             : 
   10149             : 
   10150             : - (void) stopSpeaking
   10151             : {
   10152             :         espeak_Cancel();
   10153             : }
   10154             : 
   10155             : 
   10156             : - (BOOL) isSpeaking
   10157             : {
   10158             :         return espeak_IsPlaying();
   10159             : }
   10160             : 
   10161             : 
   10162             : - (NSString *) voiceName:(unsigned int) index
   10163             : {
   10164             :         if (index >= espeak_voice_count)
   10165             :                 return @"-";
   10166             :         return [NSString stringWithCString: espeak_voices[index]->name];
   10167             : }
   10168             : 
   10169             : 
   10170             : - (unsigned int) voiceNumber:(NSString *) name
   10171             : {
   10172             :         if (name == nil)
   10173             :                 return UINT_MAX;
   10174             :         
   10175             :         const char *const label = [name UTF8String];
   10176             :         if (!label)
   10177             :                 return UINT_MAX;
   10178             :         
   10179             :         unsigned int index = -1;
   10180             :         while (espeak_voices[++index] && strcmp (espeak_voices[index]->name, label))
   10181             :                         /**/;
   10182             :         return (index < espeak_voice_count) ? index : UINT_MAX;
   10183             : }
   10184             : 
   10185             : 
   10186             : - (unsigned int) nextVoice:(unsigned int) index
   10187             : {
   10188             :         if (++index >= espeak_voice_count)
   10189             :                 index = 0;
   10190             :         return index;
   10191             : }
   10192             : 
   10193             : 
   10194             : - (unsigned int) prevVoice:(unsigned int) index
   10195             : {
   10196             :         if (--index >= espeak_voice_count)
   10197             :                 index = espeak_voice_count - 1;
   10198             :         return index;
   10199             : }
   10200             : 
   10201             : 
   10202             : - (unsigned int) setVoice:(unsigned int) index withGenderM:(BOOL) isMale
   10203             : {
   10204             :         if (index == UINT_MAX)
   10205             :                 index = [self voiceNumber:DESC(@"espeak-default-voice")];
   10206             :         
   10207             :         if (index < espeak_voice_count)
   10208             :         {
   10209             :                 espeak_VOICE voice = { espeak_voices[index]->name, NULL, NULL, isMale ? 1 : 2 };
   10210             :                 espeak_SetVoiceByProperties (&voice);
   10211             :         }
   10212             :         
   10213             :         return index;
   10214             : }
   10215             : 
   10216             : #else
   10217             : 
   10218             : - (void) startSpeakingString:(NSString *) text  {}
   10219             : 
   10220             : - (void) stopSpeaking {}
   10221             : 
   10222             : - (BOOL) isSpeaking
   10223             : {
   10224             :         return NO;
   10225             : }
   10226             : #endif
   10227             : 
   10228             : 
   10229             : - (BOOL) pauseMessageVisible
   10230             : {
   10231             :         return _pauseMessage;
   10232             : }
   10233             : 
   10234             : 
   10235             : - (void) setPauseMessageVisible:(BOOL)value
   10236             : {
   10237             :         _pauseMessage = value;
   10238             : }
   10239             : 
   10240             : 
   10241             : - (BOOL) permanentMessageLog
   10242             : {
   10243             :         return _permanentMessageLog;
   10244             : }
   10245             : 
   10246             : 
   10247             : - (void) setPermanentMessageLog:(BOOL)value
   10248             : {
   10249             :         _permanentMessageLog = value;
   10250             : }
   10251             : 
   10252             : 
   10253             : - (BOOL) autoMessageLogBg
   10254             : {
   10255             :         return _autoMessageLogBg;
   10256             : }
   10257             : 
   10258             : 
   10259             : - (void) setAutoMessageLogBg:(BOOL)value
   10260             : {
   10261             :         _autoMessageLogBg = !!value;
   10262             : }
   10263             : 
   10264             : 
   10265             : - (BOOL) permanentCommLog
   10266             : {
   10267             :         return _permanentCommLog;
   10268             : }
   10269             : 
   10270             : 
   10271             : - (void) setPermanentCommLog:(BOOL)value
   10272             : {
   10273             :         _permanentCommLog = value;
   10274             : }
   10275             : 
   10276             : 
   10277             : - (void) setAutoCommLog:(BOOL)value
   10278             : {
   10279             :         _autoCommLog = value;
   10280             : }
   10281             : 
   10282             : 
   10283             : - (BOOL) blockJSPlayerShipProps
   10284             : {
   10285             :         return gOOJSPlayerIfStale != nil;
   10286             : }
   10287             : 
   10288             : 
   10289             : - (void) setBlockJSPlayerShipProps:(BOOL)value
   10290             : {
   10291             :         if (value)
   10292             :         {
   10293             :                 gOOJSPlayerIfStale = PLAYER;
   10294             :         }
   10295             :         else
   10296             :         {
   10297             :                 gOOJSPlayerIfStale = nil;
   10298             :         }
   10299             : }
   10300             : 
   10301             : 
   10302             : - (void) setUpSettings
   10303             : {
   10304             :         [self resetBeacons];
   10305             :         
   10306             :         next_universal_id = 100;        // start arbitrarily above zero
   10307             :         memset(entity_for_uid, 0, sizeof entity_for_uid);
   10308             :         
   10309             :         [self setMainLightPosition:kZeroVector];
   10310             : 
   10311             :         [gui autorelease];
   10312             :         gui = [[GuiDisplayGen alloc] init];
   10313             :         [gui setTextColor:[OOColor colorWithDescription:[[gui userSettings] objectForKey:kGuiDefaultTextColor]]];
   10314             : 
   10315             :         // message_gui and comm_log_gui defaults are set up inside [hud resetGuis:] ( via [player deferredInit], called from the code that calls this method). 
   10316             :         [message_gui autorelease];
   10317             :         message_gui = [[GuiDisplayGen alloc]
   10318             :                                         initWithPixelSize:NSMakeSize(480, 160)
   10319             :                                                           columns:1
   10320             :                                                                  rows:9
   10321             :                                                         rowHeight:19
   10322             :                                                          rowStart:20
   10323             :                                                                 title:nil];
   10324             :         
   10325             :         [comm_log_gui autorelease];
   10326             :         comm_log_gui = [[GuiDisplayGen alloc]
   10327             :                                         initWithPixelSize:NSMakeSize(360, 120)
   10328             :                                                           columns:1
   10329             :                                                                  rows:10
   10330             :                                                         rowHeight:12
   10331             :                                                          rowStart:12
   10332             :                                                                 title:nil];
   10333             :         
   10334             :         //
   10335             :         
   10336             :         time_delta = 0.0;
   10337             : #ifndef NDEBUG
   10338             :         [self setTimeAccelerationFactor:TIME_ACCELERATION_FACTOR_DEFAULT];
   10339             : #endif
   10340             :         universal_time = 0.0;
   10341             :         messageRepeatTime = 0.0;
   10342             :         countdown_messageRepeatTime = 0.0;
   10343             :         
   10344             : #if OOLITE_SPEECH_SYNTH
   10345             :         [speechArray autorelease];
   10346             :         speechArray = [[ResourceManager arrayFromFilesNamed:@"speech_pronunciation_guide.plist" inFolder:@"Config" andMerge:YES] retain];
   10347             : #endif
   10348             :         
   10349             :         [commodities autorelease];
   10350             :         commodities = [[OOCommodities alloc] init];
   10351             : 
   10352             :         
   10353             :         [self loadDescriptions];
   10354             :         
   10355             :         [characters autorelease];
   10356             :         characters = [[ResourceManager dictionaryFromFilesNamed:@"characters.plist" inFolder:@"Config" andMerge:YES] retain];
   10357             :         
   10358             :         [customSounds autorelease];
   10359             :         customSounds = [[ResourceManager dictionaryFromFilesNamed:@"customsounds.plist" inFolder:@"Config" andMerge:YES] retain];
   10360             :         
   10361             :         [globalSettings autorelease];
   10362             :         globalSettings = [[ResourceManager dictionaryFromFilesNamed:@"global-settings.plist" inFolder:@"Config" mergeMode:MERGE_SMART cache:YES] retain];
   10363             : 
   10364             :         
   10365             :         [systemManager autorelease];
   10366             :         systemManager = [[ResourceManager systemDescriptionManager] retain];
   10367             : 
   10368             :         [screenBackgrounds autorelease];
   10369             :         screenBackgrounds = [[ResourceManager dictionaryFromFilesNamed:@"screenbackgrounds.plist" inFolder:@"Config" andMerge:YES] retain];
   10370             : 
   10371             :         // role-categories.plist and pirate-victim-roles.plist
   10372             :         [roleCategories autorelease];
   10373             :         roleCategories = [[ResourceManager roleCategoriesDictionary] retain];
   10374             :         
   10375             :         [autoAIMap autorelease];
   10376             :         autoAIMap = [[ResourceManager dictionaryFromFilesNamed:@"autoAImap.plist" inFolder:@"Config" andMerge:YES] retain];
   10377             :         
   10378             :         [equipmentData autorelease];
   10379             :         [equipmentDataOutfitting autorelease];
   10380             :         NSArray *equipmentTemp = [ResourceManager arrayFromFilesNamed:@"equipment.plist" inFolder:@"Config" andMerge:YES];
   10381             :         equipmentData = [[equipmentTemp sortedArrayUsingFunction:equipmentSort context:NULL] retain];
   10382             :         equipmentDataOutfitting = [[equipmentTemp sortedArrayUsingFunction:equipmentSortOutfitting context:NULL] retain];
   10383             :         
   10384             :         [OOEquipmentType loadEquipment];
   10385             : 
   10386             :         [explosionSettings autorelease];
   10387             :         explosionSettings = [[ResourceManager dictionaryFromFilesNamed:@"explosions.plist" inFolder:@"Config" andMerge:YES] retain];
   10388             : 
   10389             : }
   10390             : 
   10391             : 
   10392           0 : - (void) setUpCargoPods
   10393             : {
   10394             :         NSMutableDictionary *tmp = [[NSMutableDictionary alloc] initWithCapacity:[commodities count]];
   10395             :         OOCommodityType type = nil;
   10396             :         foreach (type, [commodities goods])
   10397             :         {
   10398             :                 ShipEntity *container = [self newShipWithRole:@"oolite-template-cargopod"];
   10399             :                 [container setScanClass:CLASS_CARGO];
   10400             :                 [container setCommodity:type andAmount:1];
   10401             :                 [tmp setObject:container forKey:type];
   10402             :                 [container release];
   10403             :         }
   10404             :         [cargoPods release];
   10405             :         cargoPods = [[NSDictionary alloc] initWithDictionary:tmp];
   10406             :         [tmp release];
   10407             : }
   10408             : 
   10409           0 : - (void) verifyEntitySessionIDs
   10410             : {
   10411             : #ifndef NDEBUG
   10412             :         NSMutableArray *badEntities = nil;
   10413             :         Entity *entity = nil;
   10414             :         
   10415             :         unsigned i;
   10416             :         for (i = 0; i < n_entities; i++)
   10417             :         {
   10418             :                 entity = sortedEntities[i];
   10419             :                 if ([entity sessionID] != _sessionID)
   10420             :                 {
   10421             :                         OOLogERR(@"universe.sessionIDs.verify.failed", @"Invalid entity %@ (came from session %lu, current session is %lu).", [entity shortDescription], [entity sessionID], _sessionID);
   10422             :                         if (badEntities == nil)  badEntities = [NSMutableArray array];
   10423             :                         [badEntities addObject:entity];
   10424             :                 }
   10425             :         }
   10426             :         
   10427             :         foreach (entity, badEntities)
   10428             :         {
   10429             :                 [self removeEntity:entity];
   10430             :         }
   10431             : #endif
   10432             : }
   10433             : 
   10434             : 
   10435             : // FIXME: needs less redundancy?
   10436             : - (BOOL) reinitAndShowDemo:(BOOL) showDemo
   10437             : {
   10438             :         no_update = YES;
   10439             :         PlayerEntity* player = PLAYER;
   10440             :         assert(player != nil);
   10441             :         
   10442             :         if (JSResetFlags != 0)  // JS reset failed, remember previous settings 
   10443             :         {
   10444             :                 showDemo = (JSResetFlags & 2) > 0;       // binary 10, a.k.a. 1 << 1
   10445             :         }
   10446             :         else
   10447             :         {
   10448             :                 JSResetFlags = (showDemo << 1);
   10449             :         }
   10450             :         
   10451             :         [self removeAllEntitiesExceptPlayer];
   10452             :         [OOTexture clearCache];
   10453             :         
   10454             :         _sessionID++;   // Must be after removing old entities and before adding new ones.
   10455             :         
   10456             :         [ResourceManager setUseAddOns:useAddOns];       // also logs the paths
   10457             :         //[ResourceManager loadScripts]; // initialised inside [player setUp]!
   10458             :         
   10459             :         // NOTE: Anything in the sharedCache is now trashed and must be
   10460             :         //       reloaded. Ideally anything using the sharedCache should
   10461             :         //       be aware of cache flushes so it can automatically
   10462             :         //       reinitialize itself - mwerle 20081107.
   10463             :         [OOShipRegistry reload];
   10464             :         [[self gameController] setGamePaused:NO];
   10465             :         [[self gameController] setMouseInteractionModeForUIWithMouseInteraction:NO];
   10466             :         [PLAYER setSpeed:0.0];
   10467             :         
   10468             :         [self loadDescriptions];
   10469             :         [self loadScenarios];
   10470             :         
   10471             :         [missiontext autorelease];
   10472             :         missiontext = [[ResourceManager dictionaryFromFilesNamed:@"missiontext.plist" inFolder:@"Config" andMerge:YES] retain];
   10473             :         
   10474             :         
   10475             :         if(showDemo)
   10476             :         {
   10477             :                 [demo_ships release];
   10478             :                 demo_ships = [[[OOShipRegistry sharedRegistry] demoShipKeys] retain];
   10479             :                 demo_ship_index = 0;
   10480             :                 demo_ship_subindex = 0;
   10481             :         }
   10482             :         
   10483             :         breakPatternCounter = 0;
   10484             :         
   10485             :         cachedSun = nil;
   10486             :         cachedPlanet = nil;
   10487             :         cachedStation = nil;
   10488             :         
   10489             :         [self setUpSettings];
   10490             : 
   10491             :         // reset these in case OXP set has changed
   10492             : 
   10493             :         // set up cargopod templates
   10494             :         [self setUpCargoPods];
   10495             :         
   10496             :         if (![player setUpAndConfirmOK:YES]) 
   10497             :         {
   10498             :                 // reinitAndShowDemo rescheduled inside setUpAndConfirmOK...
   10499             :                 return NO;      // Abort!
   10500             :         }
   10501             :         
   10502             :         // we can forget the previous settings now.
   10503             :         JSResetFlags = 0;
   10504             :         
   10505             :         [self addEntity:player];
   10506             :         demo_ship = nil;
   10507             :         [[self gameController] setPlayerFileToLoad:nil];                // reset Quicksave
   10508             :         
   10509             :         [self setUpInitialUniverse];
   10510             :         autoSaveNow = NO;       // don't autosave immediately after restarting a game
   10511             :         
   10512             :         [[self station] initialiseLocalMarket];
   10513             :         
   10514             :         if(showDemo)
   10515             :         {
   10516             :                 [player setStatus:STATUS_START_GAME];
   10517             :                 // re-read keyconfig.plist just in case we've loaded a keyboard
   10518             :                 // configuration expansion
   10519             :                 [player initControls];
   10520             :         }
   10521             :         else
   10522             :         {
   10523             :                 [player setDockedAtMainStation];
   10524             :         }
   10525             :         
   10526             :         [player completeSetUp];
   10527             :         if(showDemo)
   10528             :         {
   10529             :                 [player setGuiToIntroFirstGo:YES];
   10530             :         }
   10531             :         else
   10532             :         {
   10533             :                 // no need to do these if showing the demo as the only way out
   10534             :                 // now is to load a game
   10535             :                 [self populateNormalSpace];
   10536             : 
   10537             :                 [player startUpComplete];
   10538             :         }
   10539             : 
   10540             :         if(!showDemo)
   10541             :         {
   10542             :                 [player setGuiToStatusScreen];
   10543             :                 [player doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")];
   10544             :         }
   10545             :         
   10546             :         [self verifyEntitySessionIDs];
   10547             : 
   10548             :         no_update = NO;
   10549             :         return YES;
   10550             : }
   10551             : 
   10552             : 
   10553           0 : - (void) setUpInitialUniverse
   10554             : {
   10555             :         PlayerEntity* player = PLAYER;
   10556             :         
   10557             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"Wormhole and character reset");
   10558             :         if (activeWormholes) [activeWormholes autorelease];
   10559             :         activeWormholes = [[NSMutableArray arrayWithCapacity:16] retain];
   10560             :         if (characterPool) [characterPool autorelease];
   10561             :         characterPool = [[NSMutableArray arrayWithCapacity:256] retain];
   10562             :         OO_DEBUG_POP_PROGRESS();
   10563             :         
   10564             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"Galaxy reset");
   10565             :         [self setGalaxyTo: [player galaxyNumber] andReinit:YES];
   10566             :         systemID = [player systemID];
   10567             :         OO_DEBUG_POP_PROGRESS();
   10568             :         
   10569             :         OO_DEBUG_PUSH_PROGRESS(@"%@", @"Player init: setUpShipFromDictionary");
   10570             :         [player setUpShipFromDictionary:[[OOShipRegistry sharedRegistry] shipInfoForKey:[player shipDataKey]]]; // the standard cobra at this point
   10571             :         [player baseMass]; // bootstrap the base mass used in all fuel charge calculations.
   10572             :         OO_DEBUG_POP_PROGRESS();
   10573             :         
   10574             :         // Player init above finishes initialising all standard player ship properties. Now that the base mass is set, we can run setUpSpace! 
   10575             :         [self setUpSpace];
   10576             :         
   10577             :         [self setDockingClearanceProtocolActive:
   10578             :                           [[self currentSystemData]     oo_boolForKey:@"stations_require_docking_clearance" defaultValue:YES]];
   10579             : 
   10580             :         [self enterGUIViewModeWithMouseInteraction:NO];
   10581             :         [player setPosition:[[self station] position]];
   10582             :         [player setOrientation:kIdentityQuaternion];
   10583             : }
   10584             : 
   10585             : 
   10586           0 : - (float) randomDistanceWithinScanner
   10587             : {
   10588             :         return SCANNER_MAX_RANGE * ((Ranrot() & 255) / 256.0 - 0.5);
   10589             : }
   10590             : 
   10591             : 
   10592           0 : - (Vector) randomPlaceWithinScannerFrom:(Vector)pos alongRoute:(Vector)route withOffset:(double)offset
   10593             : {
   10594             :         pos.x += offset * route.x + [self randomDistanceWithinScanner];
   10595             :         pos.y += offset * route.y + [self randomDistanceWithinScanner];
   10596             :         pos.z += offset * route.z + [self randomDistanceWithinScanner];
   10597             :         
   10598             :         return pos;
   10599             : }
   10600             : 
   10601             : 
   10602           0 : - (HPVector) fractionalPositionFrom:(HPVector)point0 to:(HPVector)point1 withFraction:(double)routeFraction
   10603             : {
   10604             :         if (routeFraction == NSNotFound) routeFraction = randf();
   10605             :         
   10606             :         point1 = OOHPVectorInterpolate(point0, point1, routeFraction);
   10607             :         
   10608             :         point1.x += 2 * SCANNER_MAX_RANGE * (randf() - 0.5);
   10609             :         point1.y += 2 * SCANNER_MAX_RANGE * (randf() - 0.5);
   10610             :         point1.z += 2 * SCANNER_MAX_RANGE * (randf() - 0.5);
   10611             :         
   10612             :         return point1;
   10613             : }
   10614             : 
   10615             : 
   10616           0 : - (BOOL)doRemoveEntity:(Entity *)entity
   10617             : {
   10618             :         // remove reference to entity in linked lists
   10619             :         if ([entity canCollide])        // filter only collidables disappearing
   10620             :         {
   10621             :                 doLinkedListMaintenanceThisUpdate = YES;
   10622             :         }
   10623             :         
   10624             :         [entity removeFromLinkedLists];
   10625             :         
   10626             :         // moved forward ^^
   10627             :         // remove from the reference dictionary
   10628             :         int old_id = [entity universalID];
   10629             :         entity_for_uid[old_id] = nil;
   10630             :         [entity setUniversalID:NO_TARGET];
   10631             :         [entity wasRemovedFromUniverse];
   10632             :         
   10633             :         // maintain sorted lists
   10634             :         int index = entity->zero_index;
   10635             :         
   10636             :         int n = 1;
   10637             :         if (index >= 0)
   10638             :         {
   10639             :                 if (sortedEntities[index] != entity)
   10640             :                 {
   10641             :                         OOLog(kOOLogInconsistentState, @"DEBUG: Universe removeEntity:%@ ENTITY IS NOT IN THE RIGHT PLACE IN THE ZERO_DISTANCE SORTED LIST -- FIXING...", entity);
   10642             :                         unsigned i;
   10643             :                         index = -1;
   10644             :                         for (i = 0; (i < n_entities)&&(index == -1); i++)
   10645             :                                 if (sortedEntities[i] == entity)
   10646             :                                         index = i;
   10647             :                         if (index == -1)
   10648             :                                  OOLog(kOOLogInconsistentState, @"DEBUG: Universe removeEntity:%@ ENTITY IS NOT IN THE ZERO_DISTANCE SORTED LIST -- CONTINUING...", entity);
   10649             :                 }
   10650             :                 if (index != -1)
   10651             :                 {
   10652             :                         while ((unsigned)index < n_entities)
   10653             :                         {
   10654             :                                 while (((unsigned)index + n < n_entities)&&(sortedEntities[index + n] == entity))
   10655             :                                 {
   10656             :                                         n++;    // ie there's a duplicate entry for this entity
   10657             :                                 }
   10658             :                                 
   10659             :                                 /*
   10660             :                                         BUG: when n_entities == UNIVERSE_MAX_ENTITIES, this read
   10661             :                                         off the end of the array and copied (Entity *)n_entities =
   10662             :                                         0x800 into the list. The subsequent update of zero_index
   10663             :                                         derferenced 0x800 and crashed.
   10664             :                                         FIX: add an extra unused slot to sortedEntities, which is
   10665             :                                         always nil.
   10666             :                                         EFFICIENCY CONCERNS: this could have been an alignment
   10667             :                                         issue since UNIVERSE_MAX_ENTITIES == 2048, but it isn't
   10668             :                                         really. sortedEntities is part of the object, not malloced,
   10669             :                                         it isn't aligned, and the end of it is only live in
   10670             :                                         degenerate cases.
   10671             :                                         -- Ahruman 2012-07-11
   10672             :                                 */
   10673             :                                 sortedEntities[index] = sortedEntities[index + n];      // copy entity[index + n] -> entity[index] (preserves sort order)
   10674             :                                 if (sortedEntities[index])
   10675             :                                 {
   10676             :                                         sortedEntities[index]->zero_index = index;                           // give it its correct position
   10677             :                                 }
   10678             :                                 index++;
   10679             :                         }
   10680             :                         if (n > 1)
   10681             :                                  OOLog(kOOLogInconsistentState, @"DEBUG: Universe removeEntity: REMOVED %d EXTRA COPIES OF %@ FROM THE ZERO_DISTANCE SORTED LIST", n - 1, entity);
   10682             :                         while (n--)
   10683             :                         {
   10684             :                                 n_entities--;
   10685             :                                 sortedEntities[n_entities] = nil;
   10686             :                         }
   10687             :                 }
   10688             :                 entity->zero_index = -1;     // it's GONE!
   10689             :         }
   10690             :         
   10691             :         // remove from the definitive list
   10692             :         if ([entities containsObject:entity])
   10693             :         {
   10694             :                 // FIXME: better approach needed for core break patterns - CIM
   10695             :                 if ([entity isBreakPattern] && ![entity isVisualEffect])
   10696             :                 {
   10697             :                         breakPatternCounter--;
   10698             :                 }
   10699             :                 
   10700             :                 if ([entity isShip])
   10701             :                 {
   10702             :                         ShipEntity *se = (ShipEntity*)entity;
   10703             :                         [self clearBeacon:se];
   10704             :                 }
   10705             :                 if ([entity isWaypoint])
   10706             :                 {
   10707             :                         OOWaypointEntity *wp = (OOWaypointEntity*)entity;
   10708             :                         [self clearBeacon:wp];
   10709             :                 }
   10710             :                 if ([entity isVisualEffect])
   10711             :                 {
   10712             :                         OOVisualEffectEntity *ve = (OOVisualEffectEntity*)entity;
   10713             :                         [self clearBeacon:ve];
   10714             :                 }
   10715             :                 
   10716             :                 if ([entity isWormhole])
   10717             :                 {
   10718             :                         [activeWormholes removeObject:entity];
   10719             :                 }
   10720             :                 else if ([entity isPlanet])
   10721             :                 {
   10722             :                         [allPlanets removeObject:entity];
   10723             :                 }
   10724             :                 
   10725             :                 [entities removeObject:entity];
   10726             :                 return YES;
   10727             :         }
   10728             :         
   10729             :         return NO;
   10730             : }
   10731             : 
   10732             : 
   10733           0 : static void PreloadOneSound(NSString *soundName)
   10734             : {
   10735             :         if (![soundName hasPrefix:@"["] && ![soundName hasSuffix:@"]"])
   10736             :         {
   10737             :                 [ResourceManager ooSoundNamed:soundName inFolder:@"Sounds"];
   10738             :         }
   10739             : }
   10740             : 
   10741             : 
   10742             : - (void) preloadSounds
   10743             : {
   10744             :         // Preload sounds to avoid loading stutter.
   10745             :         NSString *key = nil;
   10746             :         foreachkey (key, customSounds)
   10747             :         {
   10748             :                 id object = [customSounds objectForKey:key];
   10749             :                 if([object isKindOfClass:[NSString class]])
   10750             :                 {
   10751             :                         PreloadOneSound(object);
   10752             :                 }
   10753             :                 else if([object isKindOfClass:[NSArray class]] && [object count] > 0)
   10754             :                 {
   10755             :                         NSString *soundName = nil;
   10756             :                         foreach (soundName, object)
   10757             :                         {
   10758             :                                 if ([soundName isKindOfClass:[NSString class]])
   10759             :                                 {
   10760             :                                         PreloadOneSound(soundName);
   10761             :                                 }
   10762             :                         }
   10763             :                 }
   10764             :         }
   10765             :         
   10766             :         // Afterburner sound doesn't go through customsounds.plist.
   10767             :         PreloadOneSound(@"afterburner1.ogg");
   10768             : }
   10769             : 
   10770             : 
   10771           0 : - (void) populateSpaceFromActiveWormholes
   10772             : {
   10773             :         NSAutoreleasePool       *pool = nil;
   10774             :         
   10775             :         while ([activeWormholes count])
   10776             :         {
   10777             :                 pool = [[NSAutoreleasePool alloc] init];
   10778             :                 @try
   10779             :                 {
   10780             :                         WormholeEntity* whole = [activeWormholes objectAtIndex:0];              
   10781             :                         // If the wormhole has been scanned by the player then the
   10782             :                         // PlayerEntity will take care of it
   10783             :                         if (![whole isScanned] &&
   10784             :                                 NSEqualPoints([PLAYER galaxy_coordinates], [whole destinationCoordinates]) )
   10785             :                         {
   10786             :                                 // this is a wormhole to this system
   10787             :                                 [whole disgorgeShips];
   10788             :                         }
   10789             :                         [activeWormholes removeObjectAtIndex:0];        // empty it out
   10790             :                 }
   10791             :                 @catch (NSException *exception)
   10792             :                 {
   10793             :                         OOLog(kOOLogException, @"Squashing exception during wormhole unpickling (%@: %@).", [exception name], [exception reason]);
   10794             :                 }
   10795             :                 [pool release];
   10796             :         }
   10797             : }
   10798             : 
   10799             : 
   10800           0 : - (NSString *)chooseStringForKey:(NSString *)key inDictionary:(NSDictionary *)dictionary
   10801             : {
   10802             :         id object = [dictionary objectForKey:key];
   10803             :         if ([object isKindOfClass:[NSString class]])  return object;
   10804             :         else if ([object isKindOfClass:[NSArray class]] && [object count] > 0)  return [object oo_stringAtIndex:Ranrot() % [object count]];
   10805             :         return nil;
   10806             : }
   10807             : 
   10808             : 
   10809             : #if OO_LOCALIZATION_TOOLS
   10810             : 
   10811             : #if DEBUG_GRAPHVIZ
   10812             : - (void) dumpDebugGraphViz
   10813             : {
   10814             :         if ([[NSUserDefaults standardUserDefaults] boolForKey:@"universe-dump-debug-graphviz"])
   10815             :         {
   10816             :                 [self dumpSystemDescriptionGraphViz];
   10817             :         }
   10818             : }
   10819             : 
   10820             : 
   10821             : - (void) dumpSystemDescriptionGraphViz
   10822             : {
   10823             :         NSMutableString                         *graphViz = nil;
   10824             :         NSArray                                         *systemDescriptions = nil;
   10825             :         NSArray                                         *thisDesc = nil;
   10826             :         NSUInteger                                      i, count, j, subCount;
   10827             :         NSString                                        *descLine = nil;
   10828             :         NSArray                                         *curses = nil;
   10829             :         NSString                                        *label = nil;
   10830             :         NSDictionary                            *keyMap = nil;
   10831             :         
   10832             :         keyMap = [ResourceManager dictionaryFromFilesNamed:@"sysdesc_key_table.plist"
   10833             :                                                                                           inFolder:@"Config"
   10834             :                                                                                           andMerge:NO];
   10835             :         
   10836             :         graphViz = [NSMutableString stringWithString:
   10837             :                                 @"// System description grammar:\n\n"
   10838             :                                 "digraph system_descriptions\n"
   10839             :                                 "{\n"
   10840             :                                 "\tgraph [charset=\"UTF-8\", label=\"System description grammar\", labelloc=t, labeljust=l rankdir=LR compound=true nodesep=0.02 ranksep=1.5 concentrate=true fontname=Helvetica]\n"
   10841             :                                 "\tedge [arrowhead=dot]\n"
   10842             :                                 "\tnode [shape=none height=0.2 width=3 fontname=Helvetica]\n\t\n"];
   10843             :         
   10844             :         systemDescriptions = [[self descriptions] oo_arrayForKey:@"system_description"];
   10845             :         count = [systemDescriptions count];
   10846             :         
   10847             :         // Add system-description-string as special node (it's the one thing that ties [14] to everything else).
   10848             :         descLine = DESC(@"system-description-string");
   10849             :         label = OOStringifySystemDescriptionLine(descLine, keyMap, NO);
   10850             :         [graphViz appendFormat:@"\tsystem_description_string [label=\"%@\" shape=ellipse]\n", EscapedGraphVizString(label)];
   10851             :         [self addNumericRefsInString:descLine
   10852             :                                           toGraphViz:graphViz
   10853             :                                                 fromNode:@"system_description_string"
   10854             :                                            nodeCount:count];
   10855             :         [graphViz appendString:@"\t\n"];
   10856             :         
   10857             :         // Add special nodes for formatting codes
   10858             :         [graphViz appendString:
   10859             :          @"\tpercent_I [label=\"%I\\nInhabitants\" shape=diamond]\n"
   10860             :          "\tpercent_H [label=\"%H\\nSystem name\" shape=diamond]\n"
   10861             :          "\tpercent_RN [label=\"%R/%N\\nRandom name\" shape=diamond]\n"
   10862             :          "\tpercent_J [label=\"%J\\nNumbered system name\" shape=diamond]\n"
   10863             :          "\tpercent_G [label=\"%G\\nNumbered system name in chart number\" shape=diamond]\n\t\n"];
   10864             :         
   10865             :         // Toss in the Thargoid curses, too
   10866             :         [graphViz appendString:@"\tsubgraph cluster_thargoid_curses\n\t{\n\t\tlabel = \"Thargoid curses\"\n"];
   10867             :         curses = [[self descriptions] oo_arrayForKey:@"thargoid_curses"];
   10868             :         subCount = [curses count];
   10869             :         for (j = 0; j < subCount; ++j)
   10870             :         {
   10871             :                 label = OOStringifySystemDescriptionLine([curses oo_stringAtIndex:j], keyMap, NO);
   10872             :                 [graphViz appendFormat:@"\t\tthargoid_curse_%lu [label=\"%@\"]\n", j, EscapedGraphVizString(label)];
   10873             :         }
   10874             :         [graphViz appendString:@"\t}\n"];
   10875             :         for (j = 0; j < subCount; ++j)
   10876             :         {
   10877             :                 [self addNumericRefsInString:[curses oo_stringAtIndex:j]
   10878             :                                                   toGraphViz:graphViz
   10879             :                                                         fromNode:[NSString stringWithFormat:@"thargoid_curse_%lu", j]
   10880             :                                                    nodeCount:count];
   10881             :         }
   10882             :         [graphViz appendString:@"\t\n"];
   10883             :         
   10884             :         // The main show: the bits of systemDescriptions itself.
   10885             :         // Define the nodes
   10886             :         for (i = 0; i < count; ++i)
   10887             :         {
   10888             :                 // Build label, using sysdesc_key_table.plist if available
   10889             :                 label = [keyMap objectForKey:[NSString stringWithFormat:@"%lu", i]];
   10890             :                 if (label == nil)  label = [NSString stringWithFormat:@"[%lu]", i];
   10891             :                 else  label = [NSString stringWithFormat:@"[%lu] (%@)", i, label];
   10892             :                 
   10893             :                 [graphViz appendFormat:@"\tsubgraph cluster_%lu\n\t{\n\t\tlabel=\"%@\"\n", i, EscapedGraphVizString(label)];
   10894             :                 
   10895             :                 thisDesc = [systemDescriptions oo_arrayAtIndex:i];
   10896             :                 subCount = [thisDesc count];
   10897             :                 for (j = 0; j < subCount; ++j)
   10898             :                 {
   10899             :                         label = OOStringifySystemDescriptionLine([thisDesc oo_stringAtIndex:j], keyMap, NO);
   10900             :                         [graphViz appendFormat:@"\t\tn%lu_%lu [label=\"\\\"%@\\\"\"]\n", i, j, EscapedGraphVizString(label)];
   10901             :                 }
   10902             :                 
   10903             :                 [graphViz appendString:@"\t}\n"];
   10904             :         }
   10905             :         [graphViz appendString:@"\t\n"];
   10906             :         
   10907             :         // Define the edges
   10908             :         for (i = 0; i != count; ++i)
   10909             :         {
   10910             :                 thisDesc = [systemDescriptions oo_arrayAtIndex:i];
   10911             :                 subCount = [thisDesc count];
   10912             :                 for (j = 0; j != subCount; ++j)
   10913             :                 {
   10914             :                         descLine = [thisDesc oo_stringAtIndex:j];
   10915             :                         [self addNumericRefsInString:descLine
   10916             :                                                           toGraphViz:graphViz
   10917             :                                                                 fromNode:[NSString stringWithFormat:@"n%lu_%lu", i, j]
   10918             :                                                            nodeCount:count];
   10919             :                 }
   10920             :         }
   10921             :         
   10922             :         // Write file
   10923             :         [graphViz appendString:@"\t}\n"];
   10924             :         [ResourceManager writeDiagnosticData:[graphViz dataUsingEncoding:NSUTF8StringEncoding] toFileNamed:@"SystemDescription.dot"];
   10925             : }
   10926             : #endif  // DEBUG_GRAPHVIZ
   10927             : 
   10928             : 
   10929           0 : - (void) addNumericRefsInString:(NSString *)string toGraphViz:(NSMutableString *)graphViz fromNode:(NSString *)fromNode nodeCount:(NSUInteger)nodeCount
   10930             : {
   10931             :         NSString                                        *index = nil;
   10932             :         NSInteger                                       start, end;
   10933             :         NSRange                                         remaining, subRange;
   10934             :         unsigned                                        i;
   10935             :         
   10936             :         remaining = NSMakeRange(0, [string length]);
   10937             :         
   10938             :         for (;;)
   10939             :         {
   10940             :                 subRange = [string rangeOfString:@"[" options:NSLiteralSearch range:remaining];
   10941             :                 if (subRange.location == NSNotFound)  break;
   10942             :                 start = subRange.location + subRange.length;
   10943             :                 remaining.length -= start - remaining.location;
   10944             :                 remaining.location = start;
   10945             :                 
   10946             :                 subRange = [string rangeOfString:@"]" options:NSLiteralSearch range:remaining];
   10947             :                 if (subRange.location == NSNotFound)  break;
   10948             :                 end = subRange.location;
   10949             :                 remaining.length -= end - remaining.location;
   10950             :                 remaining.location = end;
   10951             :                 
   10952             :                 index = [string substringWithRange:NSMakeRange(start, end - start)];
   10953             :                 i = [index intValue];
   10954             :                 
   10955             :                 // Each node gets a colour for its incoming edges. The multiplication and mod shuffle them to avoid adjacent nodes having similar colours.
   10956             :                 [graphViz appendFormat:@"\t%@ -> n%u_0 [color=\"%f,0.75,0.8\" lhead=cluster_%u]\n", fromNode, i, ((float)(i * 511 % nodeCount)) / ((float)nodeCount), i];
   10957             :         }
   10958             :         
   10959             :         if ([string rangeOfString:@"%I"].location != NSNotFound)
   10960             :         {
   10961             :                 [graphViz appendFormat:@"\t%@ -> percent_I [color=\"0,0,0.25\"]\n", fromNode];
   10962             :         }
   10963             :         if ([string rangeOfString:@"%H"].location != NSNotFound)
   10964             :         {
   10965             :                 [graphViz appendFormat:@"\t%@ -> percent_H [color=\"0,0,0.45\"]\n", fromNode];
   10966             :         }
   10967             :         if ([string rangeOfString:@"%R"].location != NSNotFound || [string rangeOfString:@"%N"].location != NSNotFound)
   10968             :         {
   10969             :                 [graphViz appendFormat:@"\t%@ -> percent_RN [color=\"0,0,0.65\"]\n", fromNode];
   10970             :         }
   10971             :         
   10972             :         // TODO: test graphViz output for @"%Jxxx" and @"%Gxxxxxx"
   10973             :         if ([string rangeOfString:@"%J"].location != NSNotFound)
   10974             :         {
   10975             :                 [graphViz appendFormat:@"\t%@ -> percent_J [color=\"0,0,0.75\"]\n", fromNode];
   10976             :         }
   10977             :         
   10978             :         if ([string rangeOfString:@"%G"].location != NSNotFound)
   10979             :         {
   10980             :                 [graphViz appendFormat:@"\t%@ -> percent_G [color=\"0,0,0.85\"]\n", fromNode];
   10981             :         }
   10982             : }
   10983             : 
   10984             : - (void) runLocalizationTools
   10985             : {
   10986             :         // Handle command line options to transform system_description array for easier localization
   10987             :         
   10988             :         NSArray                         *arguments = nil;
   10989             :         NSEnumerator            *argEnum = nil;
   10990             :         NSString                        *arg = nil;
   10991             :         BOOL                            compileSysDesc = NO, exportSysDesc = NO, xml = NO;
   10992             :         
   10993             :         arguments = [[NSProcessInfo processInfo] arguments];
   10994             :         
   10995             :         for (argEnum = [arguments objectEnumerator]; (arg = [argEnum nextObject]); )
   10996             :         {
   10997             :                 if ([arg isEqual:@"--compile-sysdesc"])  compileSysDesc = YES;
   10998             :                 else if ([arg isEqual:@"--export-sysdesc"])  exportSysDesc = YES;
   10999             :                 else if ([arg isEqual:@"--xml"])  xml = YES;
   11000             :                 else if ([arg isEqual:@"--openstep"])  xml = NO;
   11001             :         }
   11002             :         
   11003             :         if (compileSysDesc)  CompileSystemDescriptions(xml);
   11004             :         if (exportSysDesc)  ExportSystemDescriptions(xml);
   11005             : }
   11006             : #endif
   11007             : 
   11008             : 
   11009             : #if NEW_PLANETS
   11010             : // See notes at preloadPlanetTexturesForSystem:.
   11011             : - (void) prunePreloadingPlanetMaterials
   11012             : {
   11013             :         [[OOAsyncWorkManager sharedAsyncWorkManager] completePendingTasks];
   11014             :         
   11015             :         NSUInteger i = [_preloadingPlanetMaterials count];
   11016             :         while (i--)
   11017             :         {
   11018             :                 if ([[_preloadingPlanetMaterials objectAtIndex:i] isFinishedLoading])
   11019             :                 {
   11020             :                         [_preloadingPlanetMaterials removeObjectAtIndex:i];
   11021             :                 }
   11022             :         }
   11023             : }
   11024             : #endif
   11025             : 
   11026             : 
   11027             : 
   11028             : - (void) loadConditionScripts
   11029             : {
   11030             :         [conditionScripts autorelease];
   11031             :         conditionScripts = [[NSMutableDictionary alloc] init];
   11032             :         // get list of names from cache manager 
   11033             :         [self addConditionScripts:[[[OOCacheManager sharedCache] objectForKey:@"equipment conditions" inCache:@"condition scripts"] objectEnumerator]];
   11034             :         
   11035             :         [self addConditionScripts:[[[OOCacheManager sharedCache] objectForKey:@"ship conditions" inCache:@"condition scripts"] objectEnumerator]];
   11036             : 
   11037             :         [self addConditionScripts:[[[OOCacheManager sharedCache] objectForKey:@"demoship conditions" inCache:@"condition scripts"] objectEnumerator]];
   11038             : }
   11039             : 
   11040             : 
   11041             : - (void) addConditionScripts:(NSEnumerator *)scripts
   11042             : {
   11043             :         NSString *scriptname = nil;
   11044             :         while ((scriptname = [scripts nextObject]))
   11045             :         {
   11046             :                 if ([conditionScripts objectForKey:scriptname] == nil)
   11047             :                 {
   11048             :                         OOJSScript *script = [OOScript jsScriptFromFileNamed:scriptname properties:nil];
   11049             :                         if (script != nil)
   11050             :                         {
   11051             :                                 [conditionScripts setObject:script forKey:scriptname];
   11052             :                         }
   11053             :                 }
   11054             :         }
   11055             : }
   11056             : 
   11057             : 
   11058             : - (OOJSScript*) getConditionScript:(NSString *)scriptname
   11059             : {
   11060             :         return [conditionScripts objectForKey:scriptname];
   11061             : }
   11062             : 
   11063             : @end
   11064             : 
   11065             : 
   11066             : @implementation OOSound (OOCustomSounds)
   11067             : 
   11068             : + (id) soundWithCustomSoundKey:(NSString *)key
   11069             : {
   11070             :         NSString *fileName = [UNIVERSE soundNameForCustomSoundKey:key];
   11071             :         if (fileName == nil)  return nil;
   11072             :         return [ResourceManager ooSoundNamed:fileName inFolder:@"Sounds"];
   11073             : }
   11074             : 
   11075             : 
   11076             : - (id) initWithCustomSoundKey:(NSString *)key
   11077             : {
   11078             :         [self release];
   11079             :         return [[OOSound soundWithCustomSoundKey:key] retain];
   11080             : }
   11081             : 
   11082             : @end
   11083             : 
   11084             : 
   11085             : @implementation OOSoundSource (OOCustomSounds)
   11086             : 
   11087             : + (id) sourceWithCustomSoundKey:(NSString *)key
   11088             : {
   11089             :         return [[[self alloc] initWithCustomSoundKey:key] autorelease];
   11090             : }
   11091             : 
   11092             : 
   11093             : - (id) initWithCustomSoundKey:(NSString *)key
   11094             : {
   11095             :         OOSound *theSound = [OOSound soundWithCustomSoundKey:key];
   11096             :         if (theSound != nil)
   11097             :         {
   11098             :                 self = [self initWithSound:theSound];
   11099             :         }
   11100             :         else
   11101             :         {
   11102             :                 [self release];
   11103             :                 self = nil;
   11104             :         }
   11105             :         return self;
   11106             : }
   11107             : 
   11108             : 
   11109             : - (void) playCustomSoundWithKey:(NSString *)key
   11110             : {
   11111             :         OOSound *theSound = [OOSound soundWithCustomSoundKey:key];
   11112             :         if (theSound != nil)  [self playSound:theSound];
   11113             : }
   11114             : 
   11115             : @end
   11116             : 
   11117           0 : NSComparisonResult populatorPrioritySort(id a, id b, void *context)
   11118             : {
   11119             :         NSDictionary *one = (NSDictionary *)a;
   11120             :         NSDictionary *two = (NSDictionary *)b;
   11121             :         int pri_one = [one oo_intForKey:@"priority" defaultValue:100];
   11122             :         int pri_two = [two oo_intForKey:@"priority" defaultValue:100];
   11123             :         if (pri_one < pri_two) return NSOrderedAscending;
   11124             :         if (pri_one > pri_two) return NSOrderedDescending;
   11125             :         return NSOrderedSame;
   11126             : }
   11127             : 
   11128             : 
   11129           0 : NSComparisonResult equipmentSort(id a, id b, void *context)
   11130             : {
   11131             :         NSArray *one = (NSArray *)a;
   11132             :         NSArray *two = (NSArray *)b;
   11133             : 
   11134             :         /* Sort by explicit sort_order, then tech level, then price */
   11135             : 
   11136             :         OOCreditsQuantity comp1 = [[one oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] oo_unsignedLongLongForKey:@"sort_order" defaultValue:1000];
   11137             :         OOCreditsQuantity comp2 = [[two oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] oo_unsignedLongLongForKey:@"sort_order" defaultValue:1000];
   11138             :         if (comp1 < comp2) return NSOrderedAscending;
   11139             :         if (comp1 > comp2) return NSOrderedDescending;
   11140             : 
   11141             :         comp1 = [one oo_unsignedLongLongAtIndex:EQUIPMENT_TECH_LEVEL_INDEX];
   11142             :         comp2 = [two oo_unsignedLongLongAtIndex:EQUIPMENT_TECH_LEVEL_INDEX];
   11143             :         if (comp1 < comp2) return NSOrderedAscending;
   11144             :         if (comp1 > comp2) return NSOrderedDescending;
   11145             : 
   11146             :         comp1 = [one oo_unsignedLongLongAtIndex:EQUIPMENT_PRICE_INDEX];
   11147             :         comp2 = [two oo_unsignedLongLongAtIndex:EQUIPMENT_PRICE_INDEX];
   11148             :         if (comp1 < comp2) return NSOrderedAscending;
   11149             :         if (comp1 > comp2) return NSOrderedDescending;
   11150             : 
   11151             :         return NSOrderedSame;
   11152             : }
   11153             : 
   11154             : 
   11155           0 : NSComparisonResult equipmentSortOutfitting(id a, id b, void *context)
   11156             : {
   11157             :         NSArray *one = (NSArray *)a;
   11158             :         NSArray *two = (NSArray *)b;
   11159             : 
   11160             :         /* Sort by explicit sort_order, then tech level, then price */
   11161             : 
   11162             :         OOCreditsQuantity comp1 = [[one oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] oo_unsignedLongLongForKey:@"purchase_sort_order" defaultValue:[[one oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] oo_unsignedLongLongForKey:@"sort_order" defaultValue:1000]];
   11163             :         OOCreditsQuantity comp2 = [[two oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] oo_unsignedLongLongForKey:@"purchase_sort_order" defaultValue:[[two oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] oo_unsignedLongLongForKey:@"sort_order" defaultValue:1000]];
   11164             :         if (comp1 < comp2) return NSOrderedAscending;
   11165             :         if (comp1 > comp2) return NSOrderedDescending;
   11166             : 
   11167             :         comp1 = [one oo_unsignedLongLongAtIndex:EQUIPMENT_TECH_LEVEL_INDEX];
   11168             :         comp2 = [two oo_unsignedLongLongAtIndex:EQUIPMENT_TECH_LEVEL_INDEX];
   11169             :         if (comp1 < comp2) return NSOrderedAscending;
   11170             :         if (comp1 > comp2) return NSOrderedDescending;
   11171             : 
   11172             :         comp1 = [one oo_unsignedLongLongAtIndex:EQUIPMENT_PRICE_INDEX];
   11173             :         comp2 = [two oo_unsignedLongLongAtIndex:EQUIPMENT_PRICE_INDEX];
   11174             :         if (comp1 < comp2) return NSOrderedAscending;
   11175             :         if (comp1 > comp2) return NSOrderedDescending;
   11176             : 
   11177             :         return NSOrderedSame;
   11178             : }
   11179             : 
   11180             : 
   11181           0 : NSString *OOLookUpDescriptionPRIV(NSString *key)
   11182             : {
   11183             :         NSString *result = [UNIVERSE descriptionForKey:key];
   11184             :         if (result == nil)  result = key;
   11185             :         return result;
   11186             : }
   11187             : 
   11188             : 
   11189             : // There's a hint of gettext about this...
   11190           0 : NSString *OOLookUpPluralDescriptionPRIV(NSString *key, NSInteger count)
   11191             : {
   11192             :         NSArray *conditions = [[UNIVERSE descriptions] oo_arrayForKey:@"plural-rules"];
   11193             :         
   11194             :         // are we using an older descriptions.plist (1.72.x) ?
   11195             :         NSString *tmp = [UNIVERSE descriptionForKey:key];
   11196             :         if (tmp != nil)
   11197             :         {
   11198             :                 static NSMutableSet *warned = nil;
   11199             :                 
   11200             :                 if (![warned containsObject:tmp])
   11201             :                 {
   11202             :                         OOLogWARN(@"localization.plurals", @"'%@' found in descriptions.plist, should be '%@%%0'. Localization data needs updating.",key,key);
   11203             :                         if (warned == nil)  warned = [[NSMutableSet alloc] init];
   11204             :                         [warned addObject:tmp];
   11205             :                 }
   11206             :         }
   11207             :         
   11208             :         if (conditions == nil)
   11209             :         {
   11210             :                 if (tmp == nil) // this should mean that descriptions.plist is from 1.73 or above.
   11211             :                         return OOLookUpDescriptionPRIV([NSString stringWithFormat:@"%@%%%d", key, count != 1]);
   11212             :                 // still using an older descriptions.plist
   11213             :                 return tmp;
   11214             :         }
   11215             :         int unsigned i;
   11216             :         long int index;
   11217             :         
   11218             :         for (index = i = 0; i < [conditions count]; ++index, ++i)
   11219             :         {
   11220             :                 const char *cond = [[conditions oo_stringAtIndex:i] UTF8String];
   11221             :                 if (!cond)
   11222             :                         break;
   11223             :                 
   11224             :                 long int input = count;
   11225             :                 BOOL flag = NO; // we XOR test results with this
   11226             :                 
   11227             :                 while (isspace (*cond))
   11228             :                         ++cond;
   11229             :                 
   11230             :                 for (;;)
   11231             :                 {
   11232             :                         while (isspace (*cond))
   11233             :                                 ++cond;
   11234             :                         
   11235             :                         char command = *cond++;
   11236             :                         
   11237             :                         switch (command)
   11238             :                         {
   11239             :                                 case 0:
   11240             :                                         goto passed; // end of string
   11241             :                                         
   11242             :                                 case '~':
   11243             :                                         flag = !flag;
   11244             :                                         continue;
   11245             :                         }
   11246             :                         
   11247             :                         long int param = strtol(cond, (char **)&cond, 10);
   11248             :                         
   11249             :                         switch (command)
   11250             :                         {
   11251             :                                 case '#':
   11252             :                                         index = param;
   11253             :                                         continue;
   11254             :                                         
   11255             :                                 case '%':
   11256             :                                         if (param < 2)
   11257             :                                                 break; // ouch - fail this!
   11258             :                                         input %= param;
   11259             :                                         continue;
   11260             :                                         
   11261             :                                 case '=':
   11262             :                                         if (flag ^ (input == param))
   11263             :                                                 continue;
   11264             :                                         break;
   11265             :                                 case '!':
   11266             :                                         if (flag ^ (input != param))
   11267             :                                                 continue;
   11268             :                                         break;
   11269             :                                         
   11270             :                                 case '<':
   11271             :                                         if (flag ^ (input < param))
   11272             :                                                 continue;
   11273             :                                         break;
   11274             :                                 case '>':
   11275             :                                         if (flag ^ (input > param))
   11276             :                                                 continue;
   11277             :                                         break;
   11278             :                         }
   11279             :                         // if we arrive here, we have an unknown test or a test has failed
   11280             :                         break;
   11281             :                 }
   11282             :         }
   11283             :         
   11284             : passed:
   11285             :         return OOLookUpDescriptionPRIV([NSString stringWithFormat:@"%@%%%ld", key, index]);
   11286             : }

Generated by: LCOV version 1.14