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