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