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