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