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