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