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-05-28 07:50:54 Functions: 0 0 -

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

Generated by: LCOV version 1.14