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:¶m
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:¶m
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:¶m
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:¶m
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 : }
|