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

Generated by: LCOV version 1.14